Suggest a similarly-spelled identifier on undefined-identifier errors (#9124)#11624
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds ChangesDid-you-mean suggestion for undefined identifiers
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: be90e543-fe80-46cc-b35a-b525afab9baf
📒 Files selected for processing (8)
source/core/slang-string-util.cppsource/core/slang-string-util.hsource/slang/slang-check-expr.cppsource/slang/slang-diagnostics.luatests/diagnostics/autodiff-custom-diff-unresolved.slangtests/diagnostics/did-you-mean-identifier.slangtests/diagnostics/transitive-namespace-import.slang/test.slangtools/slang-unit-test/unit-test-string-util.cpp
There was a problem hiding this comment.
Pull request overview
This PR adds “did you mean ‘…’?” suggestions to the existing undefined identifier diagnostic (E30015) by scanning in-scope names, using a conservative Levenshtein-distance heuristic to avoid noisy suggestions, and validating the behavior with new unit + diagnostic tests.
Changes:
- Add
StringUtil::calcLevenshteinDistance()(rolling-row DP) plus unit tests. - Extend E30015 (
undefined-identifier) to optionally emit a note:did you mean '…'?. - Implement scope-walk + nearest-name selection in
visitVarExpr()and add regression diagnostics tests.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tools/slang-unit-test/unit-test-string-util.cpp | Adds unit tests for StringUtil::calcLevenshteinDistance(). |
| tests/diagnostics/transitive-namespace-import.slang/test.slang | Updates expectations to include the new “did you mean …?” note where applicable. |
| tests/diagnostics/did-you-mean-identifier.slang | New regression test for E30015 suggestion note (plus a “no suggestion” case). |
| tests/diagnostics/autodiff-custom-diff-unresolved.slang | Updates expectations to include the new suggestion note. |
| source/slang/slang-diagnostics.lua | Extends E30015 definition with an optional note for suggestions. |
| source/slang/slang-check-expr.cpp | Implements closest-name search on unresolved VarExpr and attaches suggestion fields to E30015. |
| source/core/slang-string-util.h | Declares StringUtil::calcLevenshteinDistance(). |
| source/core/slang-string-util.cpp | Implements Levenshtein distance (but currently has a compilation issue; see comment). |
26711a7 to
2580892
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 50489565-9f6e-4b1d-afdd-f28b91a9fac0
📒 Files selected for processing (8)
source/core/slang-string-util.cppsource/core/slang-string-util.hsource/slang/slang-check-expr.cppsource/slang/slang-diagnostics.luatests/diagnostics/autodiff-custom-diff-unresolved.slangtests/diagnostics/did-you-mean-identifier.slangtests/diagnostics/transitive-namespace-import.slang/test.slangtools/slang-unit-test/unit-test-string-util.cpp
2580892 to
909a49a
Compare
909a49a to
06ef209
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8033077f-f62b-4b40-b1e1-a97322416b01
📒 Files selected for processing (8)
source/core/slang-string-util.cppsource/core/slang-string-util.hsource/slang/slang-check-expr.cppsource/slang/slang-diagnostics.luatests/diagnostics/autodiff-custom-diff-unresolved.slangtests/diagnostics/did-you-mean-identifier.slangtests/diagnostics/transitive-namespace-import.slang/test.slangtools/slang-unit-test/unit-test-string-util.cpp
06ef209 to
44349dd
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 15fcaf5a-fc6d-4a8b-9cbb-bb5d5f9593ca
📒 Files selected for processing (8)
source/core/slang-string-util.cppsource/core/slang-string-util.hsource/slang/slang-check-expr.cppsource/slang/slang-diagnostics.luatests/diagnostics/autodiff-custom-diff-unresolved.slangtests/diagnostics/did-you-mean-identifier.slangtests/diagnostics/transitive-namespace-import.slang/test.slangtools/slang-unit-test/unit-test-string-util.cpp
44349dd to
9c1920b
Compare
9c1920b to
224971d
Compare
224971d to
6679035
Compare
6679035 to
fcdb946
Compare
Pin that a >256-char typo gets no "did you mean" suggestion (the upper-bound guard against an O(N*M) Levenshtein DP on adversarial identifiers). Uses a FileCheck CHECK-NOT directive rather than DIAGNOSTIC_TEST to avoid 260-column caret annotations.
There was a problem hiding this comment.
Verdict: 🟡 Has issues — 0 bug(s), 2 gap(s)
PR adds a "did you mean ''?" note to E30015 (undefined identifier) by introducing StringUtil::calcLevenshteinDistance and a findClosestInScopeName helper that walks the lexical scope chain, applies length-scaled distance thresholds (3..256 char target, edit distance ≤ min(3, max(1, len/3))), filters via isFromCoreModule and isDeclVisibleFromScope, and suppresses on tie. The two non-suggesting Diagnostics::UndefinedIdentifier call sites (getTypeForDeclRef, __target_intrinsic scrutinee) are documented in comments. Findings are test-coverage gaps around behaviors the PR explicitly designs for but does not pin under test.
Changes Overview
Levenshtein utility (source/core/slang-string-util.cpp, source/core/slang-string-util.h, tools/slang-unit-test/unit-test-string-util.cpp)
- Adds case-sensitive
calcLevenshteinDistanceand case-insensitivecalcLevenshteinDistanceCaseInsensitiveoverUnownedStringSlice. Shared_calcLevenshteinDistancecore with acaseInsensitiveflag and a two-rolling-row DP (O(lenB) working set). Unit tests cover identical/empty/insertion/deletion/substitution/kitten→sitting/symmetry/case-folding.
Suggestion at visitVarExpr (source/slang/slang-check-expr.cpp, source/slang/slang-diagnostics.lua)
- Adds
findClosestInScopeName(static helper) and calls it fromvisitVarExpron lookup failure. The luaundefined-identifierdiagnostic is extended with an optionalnotespan keyed on asuggestionLocationfield; an invalidSourceLoc{}suppresses the note.
Non-suggesting sites documented (source/slang/slang-check-decl.cpp, source/slang/slang-check-modifier.cpp)
- Adds comments at the two other
Diagnostics::UndefinedIdentifiercall sites (getTypeForDeclRefand the__target_intrinsicscrutinee incheckModifier) explaining why no suggestion is attached there.
Tests (tests/diagnostics/did-you-mean-identifier.slang, tests/diagnostics/did-you-mean-identifier-too-long.slang, tests/diagnostics/autodiff-custom-diff-unresolved.slang, tests/diagnostics/transitive-namespace-import.slang/*)
- New
DIAGNOSTIC_TESTexhaustive-mode test exercises close-match, no-match, min-length on/off, case-insensitive, distance-cap on/over, core-module exclusion, tie-break, same-name overload dedup, and struct-member self lookup. SeparateSIMPLE filechecktest exercises the >256 char guard viaCHECK-NOT: did you mean. Cross-module test pins suggesting the publicf_band NOT suggesting the non-publicf_privatefrom another module.
Findings (2 total)
| Severity | Location | Finding |
|---|---|---|
| 🟡 Gap | tests/diagnostics/did-you-mean-identifier.slang |
No end-to-end test for the getDeclToExcludeFromLookup() self-exclusion path (int x = x;-style typo). |
| 🟡 Gap | source/slang/slang-check-modifier.cpp:1972 |
The "no suggestion at __target_intrinsic" (and the parallel getTypeForDeclRef) decision is documented only in a comment, not pinned by CHECK-NOT in a test. |
| //CHECK: ^^^^^^^^^^ undefined identifier 'lengthFiel'. | ||
| //CHECK: ^^^^^^^^^^ did you mean 'lengthField'? | ||
| } | ||
| } |
There was a problem hiding this comment.
🟡 Gap: no regression test for the getDeclToExcludeFromLookup() exclusion
The PR explicitly threads getDeclToExcludeFromLookup() through to the suggestion search (in findClosestInScopeName: if (candidateDecl == declToExclude) continue;) and the function comment says this "mirrors the getDeclToExcludeFromLookup() exclusion that real lookup applies." But none of the new cases here exercises the self-initialization typo case, where the declared variable itself is the closest-named candidate.
Without that test, a future change that drops the declToExclude argument or silently passes nullptr would still produce a diagnostic with a suggestion that names the variable being declared — turning int longIdentifier = longIdentifie; into "did you mean 'longIdentifier'?" — and the suite would not catch it.
Suggestion: add one more case to did-you-mean-identifier.slang:
float selfInit()
{
float longIdentifier = longIdentifie;
//CHECK: ^^^^^^^^^^^^^ undefined identifier
//CHECK: ^^^^^^^^^^^^^ undefined identifier 'longIdentifie'.
return longIdentifier;
}Exhaustive-mode (DIAGNOSTIC_TEST) means a spurious "did you mean 'longIdentifier'?" would be an unannotated diagnostic and fail the test, pinning the contract.
| if (!scrutineeResults.isValid()) | ||
| { | ||
| // No "did you mean ...?" suggestion here (unlike the | ||
| // `VarExpr` path): a `__target_intrinsic` scrutinee names a |
There was a problem hiding this comment.
🟡 Gap: the "no suggestion at __target_intrinsic" decision is documented in a comment but not pinned by a test
The PR adds an explanatory comment here (and a similar one in slang-check-decl.cpp:1791) justifying why Diagnostics::UndefinedIdentifier at this site deliberately omits the .suggestion/.suggestionLocation fields. A future contributor refactoring this site (e.g. extracting a shared helper that always populates the suggestion) would silently change behavior, and nothing in the suite would catch it — the comment is the only safeguard.
Suggestion: add a small DIAGNOSTIC_TEST under tests/diagnostics/ that triggers a __target_intrinsic scrutinee with a typo of an in-scope identifier, and use CHECK-NOT: did you mean to pin the intentional absence. Same goes for the contextual-keyword path in slang-check-decl.cpp getTypeForDeclRef.
Adds a "did you mean ''?" note to the undefined-identifier error (E30015) when a close in-scope name exists. The match is conservative to avoid noise: the allowed edit distance scales with the identifier length (capped at 3, minimum length 3) and core-module builtins are excluded. Adds
StringUtil::calcLevenshteinDistancewith unit tests, and attaches the suggestion as an optional note on the existing diagnostic rather than a detached standalone note.Fixes #9124.