Skip to content

Fix NullReferenceException in Entity on unlinked placeholder entities - #20274

Merged
T-Gro merged 3 commits into
dotnet:mainfrom
xperiandri:fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities
Sep 21, 2026
Merged

T-Gro merged 3 commits into
dotnet:mainfrom
xperiandri:fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities

Conversation

@xperiandri

@xperiandri xperiandri commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20269

A broken, mid-edit file could leave an unlinked placeholder entity in the typed tree. Reading it during semantic classification threw a NullReferenceException that discarded every colour in the file instead of just the one unresolved symbol, and the metadata writer would silently serialize such an entity instead of refusing it.

@github-actions

github-actions Bot commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

✅ Release notes checked


✅ 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

@xperiandri xperiandri changed the title Fix NRE on unlinked placeholder entities and add release notes Fix NullReferenceException in Entity on unlinked placeholder entities Aug 17, 2026
@github-actions github-actions Bot added the AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files label Aug 17, 2026
Comment thread src/Compiler/Utilities/lib.fs
Comment thread src/Compiler/TypedTree/TypedTree.fs

@T-Gro T-Gro left a comment

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.

Thanks for digging into this — resilient partial classification during mid-edit is a real, worthwhile goal, and the guarded remapTyconAug / entity_modul_type remap in TypedTreeOps.Remapping.fs are the right shape. A few blocking items before this can go in:

It doesn't build. Marking entity_modul_type and entity_tycon_tcaug as | null in TypedTree.fsi propagates nullability to consumers that weren't updated, and nullness warnings are errors here (outside Proto). A local -c Release build fails with FS3261 at:

  • TypedTreeOps.Transforms.fs(991) — d.entity_modul_type.Value is the same unguarded deref you fixed in Remapping.fs, missed here.
  • TypedTreePickle.fs(2818) — p_maybe_lazy p_modul_typ x.entity_modul_type passes a nullable to a non-nullable target.
  • TypedTreePickle.fs(2837–2852) — eight TyconAugmentation | null incompatibilities in the tcaug pickle/unpickle path.

That's why every Build_And_Test_* leg is red. If you make these fields nullable you have to sweep all consumers, including the pickler.

~1360 lines of the TypedTree.fs diff are trailing-whitespace churn — only ~20 lines are real change. This trips CheckCodeFormatting, buries the actual fix, and will conflict with everything. Please revert the whitespace-only edits (and the lone one in lib.fs) so the diff is just the guards.

Scope of the type change. These fields were already effectively null for NewUnlinked() placeholders (Unchecked.defaultof<_>), so | null documents an existing latent invariant — but flipping two core Entity fields to nullable forces null-checks everywhere and is a big hammer for an IDE-resilience fix. Worth considering whether the dangling unlinked entity reaching the classification path is itself the bug to fix, rather than making the field type nullable tree-wide.

NewUnlinked() value changes are unnecessary and risky. Switching the placeholder to entity_logical_name = "<unknown>", entity_tycon_repr = TNoRepr, entity_typars = NotLazy [] changes observable pre-Link state on the metadata-unpickling hot path. IsLinked keys off entity_attribs (still defaultof), so linkage detection survives, but none of this is needed for the NRE fix — suggest reverting to Unchecked.defaultof<_>.

No regression test. The linked issue has a clean repro (break a type decl mid-edit); please encode it so this can't silently come back.

lib.fs WeakMap.TryAdd behind #if NETSTANDARD2_0 is correct and a nice cleanup, but it's unrelated to the NRE — fine to keep, just calling it out.

One more: the issue body references files/functions not in this diff (vsintegration/src/FSharp.Editor/*, NewModified*, FreeVars, a NullRef-TypedTree-Classification-Fix.md). Reconciling the description with the actual change will help reviewers follow along.

@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling Aug 17, 2026
@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Aug 17, 2026
@T-Gro
T-Gro self-requested a review August 17, 2026 08:41
@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from d9c55d0 to 026148d Compare August 17, 2026 11:26

@T-Gro T-Gro left a comment

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.

One note on the null-fallback design (complements the root-cause point).

Comment thread src/Compiler/TypedTree/TypedTree.fs Outdated
@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch 2 times, most recently from a9fc80b to 24f2fc9 Compare August 19, 2026 22:13
@github-actions github-actions Bot added ⚠️ Affects-Bootstrap Tooling check: PR touches compiler bootstrap chain ⚠️ Affects-Build-Infra Tooling check: PR touches build infrastructure ⚠️ Scope-Review-Needed Tooling check: PR scope exceeds title/description labels Aug 19, 2026
@github-actions

This comment has been minimized.

xperiandri added a commit to xperiandri/fsharp that referenced this pull request Aug 20, 2026
Always return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
Comment thread FSharpBuild.Directory.Build.props
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Aug 20, 2026
Always return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from c3d9be5 to 4a4d62e Compare August 20, 2026 00:56
@xperiandri
xperiandri requested a review from T-Gro August 20, 2026 08:46
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Aug 20, 2026
Always return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from 4a4d62e to ba36e07 Compare August 20, 2026 08:51
T-Gro added a commit that referenced this pull request Aug 20, 2026
The previous run was SIGKILL'd by the OOM-killer mid-suite
(0 real test failures; 229 tests never ran). Empty commit to re-run
the pipeline. Same exit-137 flake also hit unrelated PRs #20274 and
#20235 at the same time.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Aug 20, 2026
Always return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from ba36e07 to 3f31b99 Compare August 20, 2026 12:57
@T-Gro
T-Gro enabled auto-merge (squash) September 16, 2026 10:26
auto-merge was automatically disabled September 17, 2026 23:04

Head branch was pushed to by a user without write access

@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from 913f953 to 61d2a01 Compare September 17, 2026 23:04
@xperiandri
xperiandri requested a review from T-Gro September 17, 2026 23:06

@T-Gro T-Gro left a comment

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.

🤖🕵️ Scope review.

Comment thread vsintegration/src/FSharp.Editor/Common/Extensions.fs Outdated
@T-Gro

T-Gro commented Sep 18, 2026

Copy link
Copy Markdown
Member

Description correction: the old pickle writer dereferenced the unlinked entity and failed before completing serialization. It did not silently serialize the entity.

@T-Gro

T-Gro commented Sep 18, 2026

Copy link
Copy Markdown
Member

Current head 61d2a0185c has no merge conflict (MERGEABLE). CI is red in WindowsCompressedMetadata_Desktop Batch1: 5683 passed, 0 failed, then the process exited because foreground threads remained. Please rerun that exact job; this run establishes neither PR attribution nor a code-independent failure.

@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from 61d2a01 to 7febdef Compare September 18, 2026 14:14
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Sep 18, 2026
@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from 7febdef to 8e7f1ae Compare September 18, 2026 14:17
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Sep 18, 2026
@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from 2cefc33 to 4080eed Compare September 18, 2026 15:13
@xperiandri
xperiandri requested a review from T-Gro September 18, 2026 15:14
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Bootstrap, Affects-Compiler-Output, Affects-Design-Time
Affects-Bootstrap: Typed-tree pickle changes participate in compiler self-hosting.
Affects-Compiler-Output: Typed-tree serialization now rejects unlinked entities.
Affects-Design-Time: Semantic classification recovery changes IDE execution.

Generated by PR Tooling Safety Check · gpt56 228.8K · ◷

xperiandri and others added 3 commits September 20, 2026 22:56
GetSemanticClassification runs under DiagnosticsScope.Protect, whose
recovery returns an empty array for the whole file. One resolution
naming a never-linked placeholder (from dotnet#20269) cost the file all of
its colouring. Classify each resolution under its own
RecoverableException handler so one bad symbol costs one token.
OperationCanceledException still propagates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch from 594fc44 to 6705591 Compare September 20, 2026 20:57
@T-Gro
T-Gro merged commit 2710309 into dotnet:main Sep 21, 2026
54 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in F# Compiler and Tooling Sep 21, 2026
@xperiandri
xperiandri deleted the fix-NullReferenceException-breaks-IDE-syntax-coloring-on-unlinked-placeholder-entities branch September 21, 2026 09:53
T-Gro pushed a commit that referenced this pull request Sep 30, 2026
* Keep single-file project options in a named record

`FSharpProjectOptionsReactor.singleFileCache` stored each script or
single-file entry as a 5-tuple `Project * VersionStamp *
FSharpParsingOptions * FSharpProjectOptions * ConnectionPointSubscription`,
destructured positionally at every consumer. It is now a private record,
`SingleFileCacheEntry`, so the consumers name the fields they use and
`addToCacheAndSubscribe` is a copy-and-update of the incoming entry.

A reference record was chosen deliberately: a struct entry saves one heap
object per cache write but, on .NET Framework where FSharp.Editor runs,
copies 48 bytes out of the dictionary on every hit and forces
`ConcurrentDictionary` to allocate a new node on update. The benchmark in
the PR shows the reference record matching the reference tuple in time and
allocations on both runtimes.

Follows up on #20413 and the discussion in #20274.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit b19bedc)

* Make ConnectionPointSubscription and the text-view lookups voption

`ConnectionPointSubscription`, `subscribeToTextViewEvents`,
`Document.TryGetIVsTextView`/`TryGetTextViewAndCaretPos`, and every
`SingleFileCacheEntry.Subscription` touch point in
`FSharpProjectOptionsManager.fs` used `option`; none of these values
escape the editor's VS-interop layer. Switched to `voption`: the
`Some` wrapper around the subscribed `IDisposable` is elided on every
cache write, at the cost of copying one more field on `Hit`.

The benchmark in the PR body has both readings for the reference
record: with `option` and with `voption`, on a real subscription
rather than the always-`None` path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ddd0acb160452a6b84a1128f07d8aec4e988e0ba)

* Add release note for #20455

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
bartelink pushed a commit to bartelink/fsharp that referenced this pull request Oct 7, 2026
…ties (dotnet#20274)

* Recover per resolution in GetSemanticClassification

GetSemanticClassification runs under DiagnosticsScope.Protect, whose
recovery returns an empty array for the whole file. One resolution
naming a never-linked placeholder (from dotnet#20269) cost the file all of
its colouring. Classify each resolution under its own
RecoverableException handler so one bad symbol costs one token.
OperationCanceledException still propagates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix dotnet#20274 (comment)

* Revert `p_tcaug` improvement

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
bartelink pushed a commit to bartelink/fsharp that referenced this pull request Oct 7, 2026
* Keep single-file project options in a named record

`FSharpProjectOptionsReactor.singleFileCache` stored each script or
single-file entry as a 5-tuple `Project * VersionStamp *
FSharpParsingOptions * FSharpProjectOptions * ConnectionPointSubscription`,
destructured positionally at every consumer. It is now a private record,
`SingleFileCacheEntry`, so the consumers name the fields they use and
`addToCacheAndSubscribe` is a copy-and-update of the incoming entry.

A reference record was chosen deliberately: a struct entry saves one heap
object per cache write but, on .NET Framework where FSharp.Editor runs,
copies 48 bytes out of the dictionary on every hit and forces
`ConcurrentDictionary` to allocate a new node on update. The benchmark in
the PR shows the reference record matching the reference tuple in time and
allocations on both runtimes.

Follows up on dotnet#20413 and the discussion in dotnet#20274.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit b19bedc)

* Make ConnectionPointSubscription and the text-view lookups voption

`ConnectionPointSubscription`, `subscribeToTextViewEvents`,
`Document.TryGetIVsTextView`/`TryGetTextViewAndCaretPos`, and every
`SingleFileCacheEntry.Subscription` touch point in
`FSharpProjectOptionsManager.fs` used `option`; none of these values
escape the editor's VS-interop layer. Switched to `voption`: the
`Some` wrapper around the subscribed `IDisposable` is elided on every
cache write, at the cost of copying one more field on `Hit`.

The benchmark in the PR body has both readings for the reference
record: with `option` and with `voption`, on a real subscription
rather than the always-`None` path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ddd0acb160452a6b84a1128f07d8aec4e988e0ba)

* Add release note for dotnet#20455

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Bootstrap Tooling check: PR touches compiler bootstrap chain ⚠️ Affects-Build-Infra Tooling check: PR touches build infrastructure ⚠️ Affects-Compiler-Output Tooling check: PR touches IL emission or codegen ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager AI-reviewed PR reviewed by AI review council AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files ⚠️ Scope-Review-Needed Tooling check: PR scope exceeds title/description

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

NullReferenceException breaks IDE syntax coloring on unlinked placeholder entities (e.g. broken code mid-edit)

2 participants