Skip to content

Preserve type aliases in match | null refinement#19745

Open
T-Gro wants to merge 3 commits into
mainfrom
fix/nullness-dropping-alias
Open

Preserve type aliases in match | null refinement#19745
T-Gro wants to merge 3 commits into
mainfrom
fix/nullness-dropping-alias

Conversation

@T-Gro
Copy link
Copy Markdown
Member

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

Fixes #19646

After match x with | null -> … | s -> …, the binding s was losing its type alias (e.g. string became System.String). Now we try to clear nullness on the original type first, preserving the alias; only fall back to stripping abbreviations when nullness is embedded in the abbreviation RHS.

T-Gro and others added 2 commits May 14, 2026 16:57
…ttern)

After 'match x with | null -> ... | s -> ...', the type of s loses its
alias (e.g. 'string' becomes 'System.String'). These tests lock in the
desired behavior; they fail on main and will pass once the fix lands.

Refs #19646

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The removeNull helper in TcMatchClause was unconditionally calling
stripTyEqns, which expanded transparent abbreviations like 'string' to
'System.String'. As a result, in:

    let test (x: string | null) =
        match x with
        | null -> null
        | s -> s.Replace("a", "b")

the inferred type of 's' was displayed as 'String' instead of 'string'.

Fix: try the cheap path first (replaceNullnessOfTy KnownWithoutNull),
which preserves the abbreviation. If the resulting effective nullness is
already WithoutNull (transparent aliases like 'string' / 'MyStr'), use
that. Otherwise (abbreviations that encode nullness in their RHS, like
'type objnull = obj | null'), fall back to the full stripTyEqns path
introduced by #18852 to keep that regression fixed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 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

@T-Gro T-Gro requested a review from abonie May 15, 2026 12:26
@T-Gro T-Gro added AI-Auto-Resolve-Conflicts Opt-in: LabelOps merges main into this PR and resolves conflicts every 3h AI-Auto-Resolve-CI Opt-in: LabelOps triages CI failures on this PR every 3h labels May 15, 2026
Per review feedback: parameterize the three alias preservation cases
(string, user alias, Uri alias) into a single Theory with InlineData,
and drop the redundant negative test (the case-sensitive substring
match on 'string' already rules out 'String').

Refs #19646

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro force-pushed the fix/nullness-dropping-alias branch from a34dcfc to 716d47d Compare May 15, 2026 12:26
@github-actions github-actions Bot added the AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed label May 15, 2026
@github-actions github-actions Bot mentioned this pull request May 16, 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.

Review (self-check)

Clean and well-reasoned fix.

Logic: The two-phase approach (optimistic alias-preserving path → validated via nullnessOfTy → fallback to strip) is correct. When nullness is only in the outer annotation (e.g. string | null), clearing it preserves the alias. When it's baked into the abbreviation RHS (e.g. type objnull = obj | null), the TryEvaluate() check correctly detects the residual nullness and falls back to the prior stripping behavior.

Edge cases covered:

  • Direct BCL-alias types (string → preserved, not expanded to System.String)
  • User-defined aliases of BCL types (type MyStr = string → preserved)
  • User-defined aliases of reference types (type MyUri = Uri → preserved)
  • Abbreviations with baked-in nullness (type objnull = obj | null) → correctly falls back (protected by #18488 regression tests)

Test: Using a Theory with InlineData to collapse cases is clean. The test design—intentionally returning s where int is expected to trigger a type-mismatch error that mentions the alias name—is a good indirect way to verify alias preservation.

No concerns on performance (this path runs once per match clause with a null pattern) or correctness.

@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label May 18, 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 - String should be inferred as string

1 participant