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.

@github-actions
Copy link
Copy Markdown
Contributor


Note

The pull request was not created — a fallback review issue was created instead due to protected file changes: #19777

🤖 LabelOps — Conflict Resolution.

Merged main into this branch. The merge resolved cleanly (auto-merge by git, no manual conflict resolution needed). Build verified successfully.

Auto-merged files:

  • docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
  • src/Compiler/Checking/Expressions/CheckExpressions.fs

Generated by LabelOps — PR Maintenance · ● 27.1M ·

@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling May 21, 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: In Progress

Development

Successfully merging this pull request may close these issues.

Nullness issue - String should be inferred as string

2 participants