Skip to content

Comments

Return insertion point from List::binarySearch() misses#10048

Open
cmarcelo wants to merge 2 commits intoshader-slang:masterfrom
cmarcelo:binary-search-return-insertion-point-on-misses
Open

Return insertion point from List::binarySearch() misses#10048
cmarcelo wants to merge 2 commits intoshader-slang:masterfrom
cmarcelo:binary-search-return-insertion-point-on-misses

Conversation

@cmarcelo
Copy link
Contributor

@cmarcelo cmarcelo commented Feb 16, 2026

Change binarySearch() to return the bitwise negation of the insertion
index when the element is not found, instead of always returning -1.
Update call-site that was assuming -1 as the error value.

On top of #10047

Compiler Impact

This PR updates an internal container utility and its call sites; it affects internal semantic checks and language-server formatting logic but does not change lexer, parser, IR, or codegen.

  • Compiler stages affected:

    • Semantic analysis: source/slang/slang-check-decl.cpp updated to use the new List::binarySearch() semantics (some call-sites switched to indexOf or otherwise adjusted) when computing declaration/member indices for checks (e.g., generic constraint comparisons).
    • Tooling / language-server formatting: source/slang/slang-language-server-auto-format.cpp updated to treat non-found binarySearch results correctly (changed check from != -1 to >= 0).
    • No changes to lexer, parser, IR, or code generation stages.
  • Target backends impacted:

    • None. SPIR-V, HLSL, GLSL, Metal, CUDA, WGSL backends are unaffected; changes are internal.
  • Public API headers:

    • None changed. No modifications to public headers (e.g., include/slang.h, slang-com-helper.h).

Changes Summary

  1. source/core/slang-list.h

    • List::binarySearch() now returns the bitwise negation of the insertion index (~imin) when an element is not found, instead of -1. Comments updated to document the behavior.
  2. source/slang/slang-check-decl.cpp

    • Call sites adjusted (some uses switched to indexOf or updated to handle the new return convention) so computed indices/differences remain correct.
  3. source/slang/slang-language-server-auto-format.cpp

    • Updated exclusion-range check from exclusionRangeId != -1 to exclusionRangeId >= 0 to correctly detect matches under the new convention.
  4. tools/slang-unit-test/unit-test-list.cpp

    • New unit tests validating List::binarySearch() for found results and insertion-point semantics (including custom comparers, empty/single/duplicate-element cases) and verifying insertion behavior using the ~result pattern.

Notes

  • No exported/public API surface changes.
  • This is a correctness/robustness improvement to an internal container API and its callers; downstream code generation and target backends are unaffected.

@cmarcelo cmarcelo requested a review from a team as a code owner February 16, 2026 22:14
Copilot AI review requested due to automatic review settings February 16, 2026 22:14
@CLAassistant
Copy link

CLAassistant commented Feb 16, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

List::binarySearch now returns the bitwise negation of the insertion index (~imin) when not found. Call sites updated to use non-negative index semantics (indexOf or >= 0) where appropriate. A new unit test file exercises List::binarySearch and insertion behavior across multiple scenarios.

Changes

Cohort / File(s) Summary
List API change
source/core/slang-list.h
List::binarySearch now returns ~imin when not found (bitwise negation of insertion index); comment updated.
Call sites / usage updates
source/slang/slang-check-decl.cpp
Replaced previous binarySearch-based sentinel handling with indexOf to obtain ancestor indices, aligning with non-negative index conventions.
Language server check
source/slang/slang-language-server-auto-format.cpp
Changed exclusion-range match condition from exclusionRangeId != -1 to exclusionRangeId >= 0.
Unit tests added
tools/slang-unit-test/unit-test-list.cpp
Adds SLANG_UNIT_TEST(list) covering List::binarySearch and insertion semantics (empty/single/duplicates/custom comparer), validating use of ~result to compute insertion indices.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: changing List::binarySearch() to return insertion point on misses instead of -1.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates List::binarySearch() to return a useful insertion-point encoding on misses (bitwise negation of insertion index), aligns an existing caller with the new semantics, and adds coverage to prevent regressions.

Changes:

  • Change List::binarySearch() miss behavior from always -1 to ~insertionIndex.
  • Update language-server formatting logic to treat “found” as >= 0 instead of != -1.
  • Add unit tests that validate both “found” indices and insertion-point results for default and custom comparers.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.

File Description
source/core/slang-list.h Implements the new “miss returns ~insertionIndex” binarySearch() contract.
source/slang/slang-language-server-auto-format.cpp Fixes a caller that previously assumed “not found” meant -1.
tools/slang-unit-test/unit-test-list.cpp Adds regression tests covering found/miss behavior and insertion usage.
source/slang/slang-check-decl.cpp Uses _compareDeclsInCommonParentByOrderOfDeclaration() for stable decl ordering in the updated area.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
source/slang/slang-check-decl.cpp (1)

12293-12379: ⚠️ Potential issue | 🟠 Major

Guard against ancestor/descendant inputs before asserting parentage.

The early return path in findDeclsLowestCommonAncestor() (line 12277) modifies one of its by-reference parameters without ensuring both parameters remain direct children of the returned ancestor. Specifically, when one declaration is a direct child of another, the function returns the grandparent but leaves one parameter pointing to the child—violating the precondition that both lhs and rhs are direct children of commonParent. This trips the assertions at lines 12303–12304, or causes UNREACHABLE at line 12378.

Add a guard after the LCA call to handle cases where the result fails this precondition:

Defensive guard
 static int _compareDeclsInCommonParentByOrderOfDeclaration(
     ContainerDecl* commonParent,
     Decl* lhs,
     Decl* rhs)
 {
     if (lhs == rhs)
         return 0;

     SLANG_ASSERT(commonParent);
     SLANG_ASSERT(lhs);
     SLANG_ASSERT(rhs);
-    SLANG_ASSERT(lhs->parentDecl == commonParent);
-    SLANG_ASSERT(rhs->parentDecl == commonParent);
+    if (lhs->parentDecl != commonParent || rhs->parentDecl != commonParent)
+    {
+        // One decl is an ancestor of the other or not directly under commonParent.
+        // Fall back to deterministic comparison by depth and name.
+        auto depthToParent = [&](Decl* d)
+        {
+            Index depth = 0;
+            while (d && d->parentDecl && d->parentDecl != commonParent)
+            {
+                d = d->parentDecl;
+                ++depth;
+            }
+            return depth;
+        };
+        int res = compareThreeWays(depthToParent(lhs), depthToParent(rhs));
+        if (res)
+            return res;
+        return comparePtrs(
+            lhs->getName(),
+            rhs->getName(),
+            [](Name const& lName, Name const& rName)
+            { return strcmp(lName.text.begin(), rName.text.begin()); });
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-check-decl.cpp` around lines 12293 - 12379,
findDeclsLowestCommonAncestor can return a commonParent while one of the by-ref
parameters (lhs/rhs) still refers to a descendant rather than a direct child,
which breaks the precondition in _compareDeclsInCommonParentByOrderOfDeclaration
that both have parentDecl == commonParent; after calling
findDeclsLowestCommonAncestor, defend by walking any descendant argument up to
its immediate child of commonParent (i.e. while(arg->parentDecl != commonParent)
arg = arg->parentDecl;) or otherwise normalize lhs/rhs so that lhs->parentDecl
and rhs->parentDecl match commonParent before asserting/entering
_compareDeclsInCommonParentByOrderOfDeclaration; ensure you also guard against
null and infinite loops by checking parentDecl is non-null.
🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@source/slang/slang-check-decl.cpp`:
- Around line 12293-12379: findDeclsLowestCommonAncestor can return a
commonParent while one of the by-ref parameters (lhs/rhs) still refers to a
descendant rather than a direct child, which breaks the precondition in
_compareDeclsInCommonParentByOrderOfDeclaration that both have parentDecl ==
commonParent; after calling findDeclsLowestCommonAncestor, defend by walking any
descendant argument up to its immediate child of commonParent (i.e.
while(arg->parentDecl != commonParent) arg = arg->parentDecl;) or otherwise
normalize lhs/rhs so that lhs->parentDecl and rhs->parentDecl match commonParent
before asserting/entering _compareDeclsInCommonParentByOrderOfDeclaration;
ensure you also guard against null and infinite loops by checking parentDecl is
non-null.

@cmarcelo cmarcelo force-pushed the binary-search-return-insertion-point-on-misses branch from a0f96e6 to fa99047 Compare February 17, 2026 01:02
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/slang/slang-check-decl.cpp`:
- Around line 3581-3582: The code computes subIndex and supIndex via
ancestor->getMembers().indexOf(subAncestor/supAncestor) but doesn’t handle
indexOf returning -1; if either is <0 the subtraction yields bogus ordering and
triggers the canonical order diagnostic incorrectly. Update the logic around the
indexOf calls (variables subIndex and supIndex) to assert presence or,
preferably, check for <0 and in that case fall back to calling compareDecls(…)
(or otherwise skip the ordering subtraction) so you only use the index
subtraction when both indices are >= 0; ensure the canonical-order diagnostic
path uses the fallback when a member isn’t found.

In `@tools/slang-unit-test/unit-test-list.cpp`:
- Around line 8-89: Add explicit edge-case tests in the SLANG_UNIT_TEST(list)
block to cover empty-list, single-element, and duplicate-value behavior: call
List<T>::binarySearch on an empty List<int> and assert it returns ~0 using
SLANG_CHECK; create a single-element List<int> and verify binarySearch finds the
existing value (>=0) and returns ~0 for a missing value, then insert and
re-check; create a List<int> with duplicate values and verify binarySearch finds
an index within the duplicates and that insert/insertIndex semantics (using
values.insert and ~searchResult) keep the list sorted. Reference the existing
List, binarySearch, insert, and SLANG_CHECK usages to mirror style and
assertions.

Comment on lines +3581 to +3582
auto subIndex = ancestor->getMembers().indexOf(subAncestor);
auto supIndex = ancestor->getMembers().indexOf(supAncestor);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against indexOf misses before subtracting.

indexOf returns -1 if either ancestor isn’t present in the member list; that collapses ordering (or yields a bogus diff) and can trigger the “canonical order” diagnostic incorrectly. Consider asserting presence or falling back to compareDecls when either index is < 0.

💡 Suggested defensive fallback
-        auto subIndex = ancestor->getMembers().indexOf(subAncestor);
-        auto supIndex = ancestor->getMembers().indexOf(supAncestor);
+        auto subIndex = ancestor->getMembers().indexOf(subAncestor);
+        auto supIndex = ancestor->getMembers().indexOf(supAncestor);
+        if (subIndex < 0 || supIndex < 0)
+        {
+            return compareDecls(subAncestor, supAncestor);
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto subIndex = ancestor->getMembers().indexOf(subAncestor);
auto supIndex = ancestor->getMembers().indexOf(supAncestor);
auto subIndex = ancestor->getMembers().indexOf(subAncestor);
auto supIndex = ancestor->getMembers().indexOf(supAncestor);
if (subIndex < 0 || supIndex < 0)
{
return compareDecls(subAncestor, supAncestor);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/slang/slang-check-decl.cpp` around lines 3581 - 3582, The code
computes subIndex and supIndex via
ancestor->getMembers().indexOf(subAncestor/supAncestor) but doesn’t handle
indexOf returning -1; if either is <0 the subtraction yields bogus ordering and
triggers the canonical order diagnostic incorrectly. Update the logic around the
indexOf calls (variables subIndex and supIndex) to assert presence or,
preferably, check for <0 and in that case fall back to calling compareDecls(…)
(or otherwise skip the ordering subtraction) so you only use the index
subtraction when both indices are >= 0; ensure the canonical-order diagnostic
path uses the fallback when a member isn’t found.

@cmarcelo cmarcelo force-pushed the binary-search-return-insertion-point-on-misses branch from fa99047 to f169b79 Compare February 17, 2026 01:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/slang-unit-test/unit-test-list.cpp`:
- Around line 119-122: The inline comment is wrong: it says "Insert another 20"
but the test actually inserts 15; update the comment to accurately describe the
operation (e.g., "Insert another 15 using the standard insert pattern") so
readers match the intent to the code around Index searchResult =
values.binarySearch(15); and values.insert(~searchResult, 15);.

@cmarcelo cmarcelo force-pushed the binary-search-return-insertion-point-on-misses branch 2 times, most recently from 09e78cc to dd7bfae Compare February 17, 2026 17:26
@szihs szihs requested a review from csyonghe February 19, 2026 10:56
The binarySearch() was comparing pointer addresses on the member list
that is ordered by the declaration position.  That may not necessarily
work unless is guaranteed that the allocator produces pointers in
declaration order and they are not reordered.  Use indexOf() instead.

See bfae49d ("Mediate access to ContainerDecl members (shader-slang#7242)") for
context.
Change binarySearch() to return the bitwise negation of the insertion
index when the element is not found, instead of always returning -1.
Update call-site that was assuming -1 as the error value.
@cmarcelo cmarcelo force-pushed the binary-search-return-insertion-point-on-misses branch from dd7bfae to 273966b Compare February 22, 2026 18:00
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