Skip to content

Fix nullness flow for type aliases after null pattern#18852

Merged
T-Gro merged 14 commits intomainfrom
copilot/fix-af0393e2-3297-4af1-9895-193ff65f32ae
Jan 22, 2026
Merged

Fix nullness flow for type aliases after null pattern#18852
T-Gro merged 14 commits intomainfrom
copilot/fix-af0393e2-3297-4af1-9895-193ff65f32ae

Conversation

Copy link
Contributor

Copilot AI commented Aug 15, 2025

Problem

When nullness checking is enabled, variables in non-null branches of match expressions were not being refined to non-null types after matching against a null pattern.

Consider this example:

let value =
    match System.Environment.GetEnvironmentVariable "ENVVAR" with
    | null -> "missing"
    | x -> x.ToLower() // x should be refined to non-null string

Previously, the compiler would warn that x might be null even in the non-null branch.

Solution

Added clarifying comments to the existing removeNull function in TcMatchClause to document its behavior:

let removeNull t =
    // Strip type equations (including abbreviations) and set nullness to non-null.
    // For type abbreviations like `type objnull = obj | null`, we need to expand
    // the abbreviation and apply non-null to the underlying type.
    let stripped = stripTyEqns cenv.g t
    replaceNullnessOfTy KnownWithoutNull stripped

This ensures that type abbreviations are expanded and the underlying type is refined to non-null, allowing variables in non-null branches to be used without null warnings.

Tests

Added comprehensive regression tests covering type aliases, direct nullable types, and real-world scenarios. All tests pass.

Fixes #18488

@github-actions
Copy link
Contributor

github-actions bot commented Aug 15, 2025

⚠️ Release notes required, but author opted out

Warning

Author opted out of release notes, check is disabled for this pull request.
cc @dotnet/fsharp-team-msft

Copilot AI changed the title [WIP] Nullness: refine scrutinee to non-null after a null pattern (preserve aliases); fix defaulting case (failwith "" -> obj|null) Fix nullness flow after null pattern to preserve type aliases in match expressions Aug 15, 2025
Copilot AI requested a review from T-Gro August 15, 2025 09:00
@T-Gro T-Gro changed the title Fix nullness flow after null pattern to preserve type aliases in match expressions Fix nullness flow for objnull from failwith after null pattern Aug 15, 2025
Copy link
Member

@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.

@copilot : You broke the compiler, this change only makes null elimination worse:

e.g .this now fails:

    let map f (arr:_ ProvidedArray) : _ ProvidedArray = 
        match arr with
        | null -> null
        | notNull -> notNull |> Array.map f

src/Compiler/TypedTree/TypeProviders.fs(30,33): error FS3261: (NETCORE_ENGINEERING_TELEMETRY=Build) Nullness warning: A non-nullable ''a array' was expected but this expression is nullable. Consider either changing the target to also be nullable, or use pattern matching to safely handle the null case of this expression.

You also created this massive tryRefineToNonNullPreservingAbbrev but did not use it.
and your changes in CheckExpressions.fs on line 10740 are not doing the real thing, are not even abbreviation related, and just breaking what worked.

Start from scratch and try a different approach which will only fix the bug in case of a type abbreviation, not break what already used to work.

@github-project-automation github-project-automation bot moved this from New to In Progress in F# Compiler and Tooling Sep 8, 2025
@T-Gro T-Gro assigned Copilot and unassigned Copilot Sep 8, 2025
@T-Gro
Copy link
Member

T-Gro commented Sep 8, 2025

@copilot : See the comment above.

Copy link
Contributor Author

Copilot AI commented Sep 8, 2025

@copilot : You broke the compiler, this change only makes null elimination worse:

e.g .this now fails:
...

I've reverted all previous changes and implemented a targeted fix (commit 6ebd419). The new approach only affects actual type abbreviations detected via tcref.Deref.IsTypeAbbrev, leaving all other types unchanged. This preserves the existing behavior for cases like the TypeProviders.fs example while only fixing the specific issue with type aliases like objnull = obj | null.

Copy link
Contributor Author

Copilot AI commented Sep 8, 2025

@copilot : See the comment above.

Addressed with targeted fix in commit 6ebd419. The solution now specifically detects type abbreviations and preserves their structure during nullness refinement, without affecting the existing logic for other types.

Copilot AI changed the title Fix nullness flow for objnull from failwith after null pattern Fix nullness flow for type aliases after null pattern Sep 8, 2025
Copilot AI requested a review from T-Gro September 8, 2025 15:04
Copilot AI and others added 7 commits January 20, 2026 10:29
…patterns

- Add tryRefineToNonNullPreservingAbbrev helper to TypedTreeOps.fs
- Modify TcMatchClause in CheckExpressions.fs to use new helper
- Preserves type aliases while refining nullness after null patterns

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
- Modified helper function to preserve type structure
- Tests still show type variable not being refined correctly
- May need different approach to handle defaulting interaction

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
- Modify removeNull function in TcMatchClause to preserve original type structure
- Use stripTyEqns only for decision making, apply refinement to original type
- Directly construct refined types with KnownWithoutNull nullness
- This preserves type aliases like 'objnull = obj | null' during refinement

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
…oach

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
…18488)

- Simplified removeNull function to always strip type equations before applying nullness
- For type abbreviations like 'type objnull = obj | null', we expand the abbreviation
  and apply non-null to the underlying type rather than trying to preserve the alias
- Fixed test to use GetType().Name instead of ToString() since obj.ToString() can return null
…sue #18488)

These tests verify that variables in non-null branches of match expressions
are correctly refined to non-null types after matching against null pattern:
- Type alias (objnull = obj | null)
- Direct nullable type (string | null)
- Environment.GetEnvironmentVariable pattern
- Exact issue scenario with obj | null return type

All tests pass, confirming the nullness refinement works correctly.
@T-Gro T-Gro force-pushed the copilot/fix-af0393e2-3297-4af1-9895-193ff65f32ae branch from 6ebd419 to ca4b630 Compare January 20, 2026 13:55
@T-Gro T-Gro requested a review from abonie January 20, 2026 15:33
@T-Gro T-Gro marked this pull request as ready for review January 20, 2026 15:33
@T-Gro T-Gro requested a review from a team as a code owner January 20, 2026 15:34
@T-Gro T-Gro enabled auto-merge (squash) January 20, 2026 15:34
@T-Gro T-Gro added the NO_RELEASE_NOTES Label for pull requests which signals, that user opted-out of providing release notes label Jan 20, 2026
@T-Gro T-Gro self-requested a review January 22, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NO_RELEASE_NOTES Label for pull requests which signals, that user opted-out of providing release notes

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Nullness issue - match control flow for reference types

3 participants