Skip to content

Drop unused IEquatable from range wrappers and fix FoldingReference#2302

Open
andyleejordan wants to merge 2 commits into
mainfrom
andyleejordan/fix-tech-debt
Open

Drop unused IEquatable from range wrappers and fix FoldingReference#2302
andyleejordan wants to merge 2 commits into
mainfrom
andyleejordan/fix-tech-debt

Conversation

@andyleejordan

Copy link
Copy Markdown
Member

What

Resolves the long-silenced CA1067 equality-contract debt, fixes a latent NullReferenceException in FoldingReference, and promotes CA1067 to error so it can't regress.

Details

  • Range wrapper structs (OmnisharpLspPosition, OmnisharpLspRange, BufferFilePosition, BufferFileRange in EditorFileRanges.cs): these are internal, only ever surfaced through their interfaces (ILspFilePosition/IFileRange/etc.), and never compared anywhere — so their IEquatable<T> (added in Close over public APIs not designed for exposure #1183) was dead code. The parallel public classes (FilePosition/LspFilePosition/…) never implemented it. Rather than completing the contract with Equals(object)/GetHashCode/operators, this drops IEquatable<T> and the typed Equals at the root (net code deletion), which removes the CA1067 obligation entirely. OmniSharp can't reach these types either — they're internal (unnameable for any generic equality), InternalsVisibleTo lists only first-party assemblies, and the ToOmnisharp* conversions hand back OmniSharp's own types — so nothing relied on the equality.
  • FoldingReference is the one type that genuinely participates in comparison (Array.Sort via SafeAdd, and xUnit Assert.Equal in the folding tests), so it keeps IEquatable/IComparable and gets the real fix:
    • Equals(FoldingReference other) was => CompareTo(other) == 0, and CompareTo dereferences its argument, so Equals(null)/CompareTo(null) threw a NullReferenceException instead of returning false/1. Both are now null-safe.
    • Added Equals(object) and a netstandard2.0-compatible GetHashCode (manual XOR, since System.HashCode is unavailable on that target).
  • .editorconfig: CA1067 promoted from silent to error.

Tests

New FoldingReferenceEqualityTests (6 tests): null-safety, value equality, hash-code consistency, and different-type handling. The main library builds clean (0 warnings / 0 errors) with CA1067 enforced, and 13/13 relevant net8.0 tests pass (6 new + 7 TokenOperationsTests).


🤖 Drafted by Copilot (Claude Opus 4.8) on behalf of @andyleejordan.

We had silenced CA1067 (types implementing `IEquatable<T>` without overriding
`object.Equals`/`GetHashCode`) instead of fixing it. Promote it to `error` and
resolve the offenders at the root.

The four wrapper structs in `EditorFileRanges.cs` — `OmnisharpLspPosition`,
`OmnisharpLspRange`, `BufferFilePosition`, and `BufferFileRange` — are
`internal`, only ever surfaced through their interfaces
(`ILspFilePosition`/`IFileRange`/etc.), and never compared anywhere. Their
`IEquatable<T>` (added reflexively in #1183) was dead code, and the parallel
public classes (`FilePosition`/`LspFilePosition`/...) never implemented it. So
rather than completing the contract with `Equals(object)`/`GetHashCode`/
operators, I dropped `IEquatable<T>` and the typed `Equals` entirely — less
code, and CA1067 no longer applies to them. OmniSharp can't reach them either:
they're `internal` (unnameable for any generic equality), `InternalsVisibleTo`
lists only first-party assemblies, and the `ToOmnisharp*` conversions hand back
OmniSharp's own types, so an instance never flows back into the library.

`FoldingReference` is the one type that genuinely participates in comparison
(`Array.Sort` via `SafeAdd`, and xUnit `Assert.Equal` in the folding tests), so
it keeps `IEquatable`/`IComparable` and gets the real fix:

- `Equals(FoldingReference other)` was `=> CompareTo(other) == 0`, and
  `CompareTo` dereferences its argument, so `Equals(null)` and `CompareTo(null)`
  threw a `NullReferenceException` instead of returning `false`/`1`. Both are
  now null-safe.
- Added `Equals(object)` and a netstandard2.0-compatible `GetHashCode` (manual
  XOR, since `System.HashCode` is unavailable on that target).

Added focused tests covering the `FoldingReference` null-safety, value equality,
and hash-code consistency.

Drafted by Copilot (Claude Opus 4.8).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 12, 2026 19:39
@andyleejordan andyleejordan enabled auto-merge (squash) June 12, 2026 19:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens equality semantics and analyzer enforcement by removing unused IEquatable<T> implementations from internal “range wrapper” structs, fixing FoldingReference’s null-unsafe equality/comparison behavior, and promoting CA1067 to an error to prevent regressions.

Changes:

  • Removed IEquatable<T> and typed Equals(T) from internal range-wrapper structs in EditorFileRanges.cs to eliminate CA1067 obligations where equality isn’t used.
  • Made FoldingReference equality/comparison null-safe and added Equals(object)/GetHashCode() to satisfy CA1067.
  • Elevated dotnet_diagnostic.CA1067.severity from silent to error and added dedicated equality tests for FoldingReference.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
test/PowerShellEditorServices.Test/Language/FoldingReferenceEqualityTests.cs Adds unit coverage for FoldingReference null-safety, equality, and hash-code behavior.
src/PowerShellEditorServices/Services/TextDocument/FoldingReference.cs Fixes null handling in CompareTo/Equals, and implements Equals(object) + GetHashCode.
src/PowerShellEditorServices/Extensions/EditorFileRanges.cs Removes unused IEquatable<T> implementations from internal range wrapper structs.
.editorconfig Promotes CA1067 to error to prevent future equality-contract regressions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/PowerShellEditorServices/Services/TextDocument/FoldingReference.cs Outdated
The Copilot PR reviewer noticed that when two references share the same
range but have different non-null `Kind`s, `CompareTo` fell through to
`return -1` for both `a.CompareTo(b)` and `b.CompareTo(a)`. That violates
the `IComparable` antisymmetry contract and can yield unstable ordering in
`Array.Sort`.

We now order differing non-null kinds deterministically with
`string.CompareOrdinal` over their string representations (stable LSP
values like `comment` and `region`), so the two directions always have
opposite signs. The null-vs-kind cases are unchanged: a reference without
a kind still sorts after one that has a kind.

Added a test asserting the comparison is antisymmetric for differing kinds.

Drafted by Copilot (Claude Opus 4.8).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants