Skip to content

Commit 5186901

Browse files
Fix FoldingReference.CompareTo antisymmetry for differing kinds
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>
1 parent a3f21b4 commit 5186901

2 files changed

Lines changed: 29 additions & 14 deletions

File tree

src/PowerShellEditorServices/Services/TextDocument/FoldingReference.cs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,18 @@ public int CompareTo(FoldingReference that)
6060
if (EndCharacter < that.EndCharacter) { return -1; }
6161
if (EndCharacter > that.EndCharacter) { return 1; }
6262

63-
// They're the same range, but what about kind
64-
if (Kind == that.Kind)
65-
{
66-
return 0;
67-
}
68-
69-
// That has a kind but this doesn't.
70-
if (Kind is null && that.Kind is not null)
71-
{
72-
return 1;
73-
}
74-
75-
// This has a kind but that doesn't.
76-
return -1;
63+
// They're the same range, so now consider the kind. Equal kinds
64+
// (including both null) compare as equal.
65+
if (Kind == that.Kind) { return 0; }
66+
67+
// A reference without a kind sorts after one that has a kind.
68+
if (Kind is null) { return 1; }
69+
if (that.Kind is null) { return -1; }
70+
71+
// Both have a kind but they differ: order deterministically by their
72+
// string representation so the comparison stays antisymmetric (i.e.
73+
// a.CompareTo(b) and b.CompareTo(a) have opposite signs).
74+
return string.CompareOrdinal(Kind.Value.ToString(), that.Kind.Value.ToString());
7775
}
7876

7977
public bool Equals(FoldingReference other) => other is not null && CompareTo(other) == 0;

test/PowerShellEditorServices.Test/Language/FoldingReferenceEqualityTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,22 @@ public void EqualsDifferentTypeReturnsFalse()
6767
FoldingReference reference = CreateFoldingReference();
6868
Assert.False(reference.Equals("not a folding reference"));
6969
}
70+
71+
[Fact]
72+
public void CompareToWithDifferingKindsIsAntisymmetric()
73+
{
74+
FoldingReference comment = CreateFoldingReference();
75+
comment.Kind = FoldingRangeKind.Comment;
76+
FoldingReference region = CreateFoldingReference();
77+
region.Kind = FoldingRangeKind.Region;
78+
79+
// Same range but different (non-null) kinds must order
80+
// deterministically: the comparisons must have opposite signs so
81+
// Array.Sort stays stable. Previously both returned -1.
82+
int forward = comment.CompareTo(region);
83+
int backward = region.CompareTo(comment);
84+
Assert.NotEqual(0, forward);
85+
Assert.Equal(-System.Math.Sign(forward), System.Math.Sign(backward));
86+
}
7087
}
7188
}

0 commit comments

Comments
 (0)