[Repo Assist] fix: emit Keyword semantic token for null in nullable type annotations#1466
Conversation
Fixes #1381 - the 'null' keyword in F# 9 nullable type annotations (e.g. 'string | null') was not receiving a semantic token classification, causing editors to display it without keyword highlighting. Root cause: FCS GetSemanticClassification does not emit tokens for the 'null' keyword in SynType.WithNull positions. Fix: Walk the untyped parse tree for SynType.WithNull nodes and emit Keyword tokens for each 'null' occurrence. These are appended to the FCS-provided tokens in handleSemanticTokens. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/repo-assist add a test that validates that the invariant about Null being the last 4 chars in the range is held, so that we are protected if the FCS behavior changes |
…ith 'null' Adds a new FcsInvariantTests module to CoreUtilsTests.fs that validates the invariant relied upon by NullableTypes.collectNullKeywordRanges: that the FCS SynType.WithNull fullRange ends exactly at the 'null' keyword (i.e. the last 4 characters of the range cover the literal 'null'). Four test cases cover: - single nullable parameter annotation: let f (x: string | null) = () - multiple nullable annotations on one line - nullable annotation on a non-first line (multiline function signature) - nullable return-type annotation: let f () : string | null = null If FCS ever changes the SynType.WithNull fullRange invariant these tests will fail loudly rather than silently emitting wrong semantic tokens. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
|
🤖 This is an automated response from Repo Assist. Done! I've added a new What the tests doRather than directly subclassing This is the right level to test the invariant: if FCS ever changes Four test cases
All 4 pass locally:
|
🤖 This PR was created by Repo Assist, an automated AI assistant.
Closes #1381
Problem
The
nullkeyword in F# 9 nullable type annotations (e.g.string | null) was not receiving a semantic token classification. This caused editors to displaynullwithout keyword highlighting in these positions, even thoughnullelsewhere in code does receive keyword highlighting.Root Cause
FCS.GetSemanticClassificationdoes not emit tokens for thenullkeyword inSynType.WithNullpositions — it handles the| nullportion of the type through the type-checking phase differently, and the resulting keyword token is simply absent from the emitted classification list.Fix
Walk the untyped parse tree (
ParseTree) forSynType.WithNullnodes and emitKeywordsemantic tokens for eachnulloccurrence found. These are collected inUntypedAstUtils.NullableTypes.collectNullKeywordRangesand appended to the FCS-provided tokens insidehandleSemanticTokensinAdaptiveFSharpLspServer.fs.Implementation details:
module NullableTypesinUntypedAstUtils.fswith aSyntaxCollectorBasesubclass that walksSynType.WithNullnodes and computes thenullkeyword range from the full type range (thenullkeyword is always exactly the last 4 characters of theWithNullfull range).UntypedAstUtils.fsi.handleSemanticTokenscallscollectNullKeywordRangesand appends(lspRange, Keyword, [])entries; if a range filter is active, only tokens within that range are included.Trade-offs
nullis computed as the last 4 characters of theSynType.WithNullfull range. This is reliable because F# syntax requiresnullto be spelled exactly (no aliases), but it does depend on the FCSSynType.WithNullfull range including thenullkeyword and no trailing whitespace — which is the current behaviour.Test Status
✅ Build:
dotnet build— succeeded with 0 warnings, 0 errors.✅ Tests:
dotnet test -f net8.0 ./test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj --filter Highlighting— 16/16 passed (including the new(28u, 26u) Keywordassertion fornullinlet withNull (x: string | null) = ()).