Skip to content

Fix #19657: MeasureAnnotatedAbbreviation over ref type + | null#19775

Open
T-Gro wants to merge 4 commits into
mainfrom
fix/nullness-umx
Open

Fix #19657: MeasureAnnotatedAbbreviation over ref type + | null#19775
T-Gro wants to merge 4 commits into
mainfrom
fix/nullness-umx

Conversation

@T-Gro
Copy link
Copy Markdown
Member

@T-Gro T-Gro commented May 20, 2026

Fixes #19657

SolveTypeIsReferenceType did not strip measure equations before calling isRefTy, unlike its siblings (SolveTypeIsNonNullableValueType, SolveTypeRequiresDefaultConstructor). This caused [<MeasureAnnotatedAbbreviation>] types over reference types (e.g. FSharp.UMX string<'m>) to be rejected by | null and 'T : not struct constraints.

T-Gro and others added 4 commits May 15, 2026 14:43
…eference type with | null

Add four tests in NullableReferenceTypesTests.fs covering:
- MeasureAnnotatedAbbreviation over string accepts | null in let bindings,
  parameters, returns, type abbreviations, and inline instantiations (fails today).
- MeasureAnnotatedAbbreviation over user-defined reference class accepts | null
  (fails today).
- MeasureAnnotatedAbbreviation over value types still rejects | null
  (non-regression, passes today).
- Nullness flow and not-null constraints work through the abbreviation
  (fails today; compilation does not reach the flow assertions).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MeasureAnnotatedAbbreviation tycons over a reference type (e.g. UMX-style
`type string<[<Measure>] 'm> = string`) previously failed the reference-type
constraint check used by the `| null` syntax, producing FS0043. The surface
type is a TType_app over a MeasureableReprTycon, which isRefTy does not
recognise. Strip measure equations before testing reference semantics so the
underlying erased representation is consulted, consistent with TypeNullNever
and IsReferenceTyparTy. Value-type erasure still correctly fails FS0043.

Also corrects a Sprint 1 test expectation that listed a phantom diagnostic
which the compiler does not emit (the matched-on null case is fully covered).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename strippedTy -> underlyingTy for consistency with SolveTypeIsNonNullableValueType
- Condense comment to single line
- Add test: not-struct constraint satisfied by MeasureAnnotatedAbbreviation
- Add test: MAA over obj, interface, and chained abbreviation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro requested a review from a team as a code owner May 20, 2026 12:47
@T-Gro T-Gro added AI-Auto-Resolve-CI Opt-in: LabelOps triages CI failures on this PR every 3h AI-Auto-Resolve-Conflicts Opt-in: LabelOps merges main into this PR and resolves conflicts every 3h labels May 20, 2026
@T-Gro T-Gro requested a review from abonie May 20, 2026 12:47
@github-actions
Copy link
Copy Markdown
Contributor

❗ Release notes required


✅ Found changes and release notes in following paths:

Warning

No PR link found in some release notes, please consider adding it.

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md No current pull request URL (#19775) found, please consider adding it

@github-actions github-actions Bot added the AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed label May 20, 2026
@github-actions github-actions Bot mentioned this pull request May 20, 2026
Copy link
Copy Markdown
Member Author

@T-Gro T-Gro left a comment

Choose a reason for hiding this comment

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

Expert Review: PR #19775 — Fix #19657: MeasureAnnotatedAbbreviation + | null

Overall Assessment: ✅ LGTM

Clean, minimal, correct bug fix with excellent test coverage.


Type System Correctness ✅

The fix is sound. isRefTy does not strip measure equations internally — it checks structural forms via stripTyEqns, but not measure equations. A [<MeasureAnnotatedAbbreviation>] type like type string<[<Measure>] 'm> = string stores its equation as a measure equation, so isRefTy couldn't see through it.

The fix correctly applies stripTyEqnsAndMeasureEqns before the isRefTy check, exactly as the sister functions SolveTypeIsNonNullableValueType (line 2932) and SolveTypeRequiresDefaultConstructor (line 3003) already do. The error message correctly preserves the original ty for user display.

Test Coverage — Excellent ✅

Six tests covering:

  • Positive: string, custom reference types, obj, interfaces, chained abbreviations
  • Negative: value types (int, DateTime) correctly rejected
  • Nullness flow: widening/narrowing semantics preserved, null-check narrowing works
  • Constraint interaction: not struct constraint satisfaction verified

Struct Awareness — Correctly Preserved ✅

The negative test verifies int<kg> | null and DateTime<s> | null still produce errors (both Error 3260 and Error 43). The fix doesn't break the value-type path because isRefTy g underlyingTy returns false for structs.

Feature Gating — Not Required ✅

Pure bug fix — | null and MeasureAnnotatedAbbreviation are both existing features. No language version gate needed.

Backward Compatibility — Safe ✅

Previously-rejected code now compiles. No previously-accepted code is rejected. No binary format or public API changes.

Minor Note (informational)

isRefTy could arguably be enhanced to internally call stripTyEqnsAndMeasureEqns to prevent this class of bug at other call sites — but that's a broader refactoring beyond this PR's scope.

@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-Auto-Resolve-CI Opt-in: LabelOps triages CI failures on this PR every 3h AI-Auto-Resolve-Conflicts Opt-in: LabelOps merges main into this PR and resolves conflicts every 3h AI-reviewed PR reviewed by AI review council 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.

Nullness issue - units of measure don't work with nullables

1 participant