Uh oh!
There was an error while loading. Please reload this page.
Fix NullReferenceException in Entity on unlinked placeholder entities - #20274
Conversation
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev Caution No release notes found for the changed paths (see table below). Please make sure to add an entry with an informative description of the change as well as link to this pull request, issue and language suggestion if applicable. Release notes for this repository are based on Keep A Changelog format. The following format is recommended for this repository: `* . (PR #XXXXX)`
If you believe that release notes are not necessary for this PR, please add NO_RELEASE_NOTES label to the pull request.
Warning No PR link found in some release notes, please consider adding it.
|
NullReferenceException in Entity on unlinked placeholder entitiesThere was a problem hiding this comment.
@T-Gro this is another fix (I can move to another PR if you want)
There was a problem hiding this comment.
The Trailing Whitespace extension removed all trailing whitespaces.
I hope it is not an issue.
There was a problem hiding this comment.
So if you hide whitespaces you will see the real several lines changed
T-Gro
left a comment
There was a problem hiding this comment.
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.Valueis the same unguarded deref you fixed inRemapping.fs, missed here.TypedTreePickle.fs(2818)—p_maybe_lazy p_modul_typ x.entity_modul_typepasses a nullable to a non-nullable target.TypedTreePickle.fs(2837–2852)— eightTyconAugmentation | nullincompatibilities 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.fsWeakMap.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.
d9c55d0 to
026148dCompare
T-Gro
left a comment
There was a problem hiding this comment.
One note on the null-fallback design (complements the root-cause point).
| member x.TypeContents = x.entity_tycon_tcaug | ||
| member x.TypeContents = | ||
| match x.entity_tycon_tcaug with | ||
| | null -> TyconAugmentation.Create() |
There was a problem hiding this comment.
🤖🕵️ On the null branch TypeContents returns a fresh TyconAugmentation.Create() each access, but callers mutate the result in place (tcaug_super <-, tcaug_abstract <-, tcaug_closed <-). If an unlinked placeholder ever reaches such a path, those writes land on a throwaway object and are silently lost — turning a fail-fast NRE into silent typed-tree corruption.
|null-> TyconAugmentation.Create()ModuleOrNamespaceType (line 884) has the same shape. A shared sentinel, or keeping unlinked entities out of these paths (the root-cause fix), is safer.
There was a problem hiding this comment.
So do you think we must save TyconAugmentation.Create() on empty Entity creation?
There was a problem hiding this comment.
Fixed in the latest commit
There was a problem hiding this comment.
Also, should we then initialize these objects on empty entity creation instead of doing that lazily?
a9fc80b to
24f2fc9Compare
This comment has been minimized.
This comment has been minimized.
Always return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
Always return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
c3d9be5 to
4a4d62eCompareAlways return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
4a4d62e to
ba36e07CompareAlways return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
ba36e07 to
3f31b99Compare🔍 Tooling Safety Check — Affects-Bootstrap, Affects-Build-Infra, Affects-Compiler-Output, Scope-Review-Needed
|
* Used safe `ModuleOrNamespaceType` property instead of `entity_modul_type`. * Used safe `TypeContents` property instead of `entity_tycon_tcaug`. * Optimized `TextViewEventsHandler` memory usage with `voption`
Always return initialized value for `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`.
45d3fb2 to
c199150Comparexperiandri
commented
Aug 21, 2026
@T-Gro is my approach correct now? |
Summary
Fixes#20269 by making typed-tree remapping and classification resilient to placeholder/unlinked entities created while the file is in a broken mid-edit state. Instead of allowing a single null placeholder to crash the entire IDE classification pipeline, the compiler now treats absent module-type augmentation data as null and skips the remap/guard path safely.
Changes
TyconAugmentationandentity_modul_typevalues.TyconAugmentationshape.WeakMapcache update compatible across target frameworks while preserving existing behavior.NullReferenceExceptionbreaks IDE syntax coloring on unlinked placeholder entities (e.g. broken code mid-edit) #20269.