Skip to content

Fix missing semantic classification in nested copy-and-update expressions#19878

Open
T-Gro wants to merge 3 commits into
mainfrom
fix/issue-17428
Open

Fix missing semantic classification in nested copy-and-update expressions#19878
T-Gro wants to merge 3 commits into
mainfrom
fix/issue-17428

Conversation

@T-Gro
Copy link
Copy Markdown
Member

@T-Gro T-Gro commented Jun 2, 2026

Summary

Fixes #17428

In nested copy-and-update expressions like { p with Person.Info.X = 1; Person.Info.Y = 2 }, the second and subsequent type qualifiers (Person.Info) were not being reported to the IDE semantic classification sink, resulting in missing colorization.

Changes

src/Compiler/Checking/NameResolution.fsResolveNestedField now threads resInfo from ResolveLongIdentInTyconRefs and calls SendEntityPathToSink so every qualifier path reaches the IDE sink. Also fixes the entity-path range to use tyconId.idRange. Qualifier idents in the returned access path are made synthetic to avoid double-emitting from a subsequent ResolveField call.

src/Compiler/TypedTree/TcGlobals.fs – Minor: removed an unneeded line.

Tests

  • Added component tests in tests/FSharp.Compiler.Service.Tests/Symbols.fs verifying all qualifier idents receive correct semantic classification.
  • Existing IWSAMsAndSRTPs tests updated to reflect corrected SRTP witness resolution (pre-existing line removed from release notes that was merged separately).

Release Notes

Entry added to docs/release-notes/.FSharp.Compiler.Service/11.0.100.md.

Copilot and others added 2 commits June 2, 2026 12:48
Adds NestedCopyAndUpdateSink module with 8 tests covering the bug where
only the first type qualifier in a nested copy-and-update is reported to
the name resolution sink.

Verified against baseline 'main':
  - 3 multi-qualifier scenarios FAIL RED (same-path siblings - the bug):
      * Both type qualifiers in nested update are classified
      * Three type qualifiers in nested update all classified
      * Qualifier ranges point to correct columns
  - 1 multi-qualifier scenario PASSES on baseline (Different type
    qualifiers each reported) because when sibling fields use different
    inner-record types (Outer.I1 vs Outer.I2) the sink already emits
    both Outer tokens; kept verbatim per sprint spec as a regression
    guard so a future fix does not accidentally drop this working case.
  - 4 single-qualifier / regression-guard tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-and-update

ResolveNestedField now threads resInfo from ResolveLongIdentInTyconRefs and
calls SendEntityPathToSink so every Person.Info.X / Person.Info.Y qualifier
reaches the IDE sink. Also fixes the entity-path range to tyconId.idRange.
Qualifier idents in the returned access path are made synthetic to avoid
double-emitting the entity-path event from a subsequent ResolveField call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… fix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro force-pushed the fix/issue-17428 branch from 2c567ac to e73a65d Compare June 2, 2026 12:27
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

// Mark the qualifier idents synthetic so a subsequent ResolveField call
// (e.g. via BuildFieldMap) does not double-emit the entity-path sink event
// we already emitted above.
|> List.map (fun id -> ident (id.idText, id.idRange.MakeSynthetic()))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems incorrect. Identifiers coming from the actual source should have non-synthetic ranges.


match tyconIdOpt with
| Some _ ->
ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurrence.UseInType, ad, resInfo, ResultTyparChecker(fun () -> true))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is UseInType a correct occurrence kind here? I thought we normally only use it in type annotations?

let p2 = { p with Person.Info.X = 10; Person.Info.Y = 20 }
"""
let personUses = usesOf "Person" 5 source
Assert.True(personUses.Length >= 2, sprintf "expected >= 2 Person uses on line 5, got %d" personUses.Length)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's dump the names ranges instead of just counting the usages?

@github-actions github-actions Bot added the AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed label Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Type of second field in nested update lacks highlighting

2 participants