Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 5, 2026

String Empty Pattern Match Optimization

This PR optimizes empty string pattern matching to use null-safe .Length == 0 checks instead of string equality comparisons for better performance.

Implementation

The optimization is implemented purely as a code generation optimization in BuildSwitch (PatternMatchCompilation.fs), where Const (String "") patterns are detected and compiled to emit:

  1. A null check (brfalse) - skipped if we're in a null-filtered context
  2. A length check (callvirt get_Length() followed by comparison with zero)

Key Features

  • Pattern discrimination: Empty strings use standard Const (String "") discriminator - no special handling
  • Code generation: Optimized null-safe length check instead of String.Equals
  • Null-safe context tracking: isNullFiltered parameter tracks when we're in a null-filtered context (after an IsNull test), allowing redundant null checks to be skipped
  • No pickle changes: Since we don't use a special discriminator, pickled data remains unchanged and backward compatible
  • Pattern matching: All patterns work correctly:
    • | "" -> ... (optimized with null check + length check)
    • | null -> ... | "" -> ... (both cases handled separately)
    • | null | "" -> ... (bundled clauses - single null check from IsNull, then length check without additional null test)

Changes Made

  1. BuildSwitch: Added isNullFiltered boolean parameter to track null-safe contexts
  2. IsNull/IsInst handling: Sets nullFiltered=true after IsNull test and passes to recursive calls
  3. Empty string optimization: Uses .Length == 0 check, conditionally skipping null test when isNullFiltered=true
  4. Array length optimization: Also benefits from null-filtered context tracking
  5. TypedTreeOps.fsi: Exported mkGetStringLength to support the optimization

Testing

  • IL emission test: Verifies optimized IL code generation in TypeTestsInPatternMatching.fs
  • Inlining test: EmptyStringPattern.fs with inline functions and --optimize+ flag
    • Basic empty string patterns
    • Patterns with both null and empty string cases
    • Bundled clauses (| null | "" -> ... and | "" | null -> ...)
    • Usage functions demonstrating inlining in action

Generated IL Example

Before (string equality):

IL_0000:  ldarg.0
IL_0001:  ldstr      ""
IL_0006:  call       bool String::Equals(string, string)

After (null-safe length check):

IL_0000:  ldarg.0
IL_0001:  brfalse.s  default_case    // Null check
IL_0003:  ldarg.0
IL_0004:  callvirt   int32 String::get_Length()
IL_0009:  brtrue.s   default_case    // Length != 0 check

The changes are minimal and surgical, affecting only the code generation path for empty string constants without any changes to pattern analysis, type definitions, or data structures.

Original prompt

Motivation

Replace F# pattern match empty string equality with a length check for performance, but preserve null safety. The optimization must ensure that match s with "" pattern NEVER crashes if s is null, even if the programmer did not add a null case.

Implementation Steps

  1. Extend DecisionTreeTest with: | StringLengthZero of TType
  2. In getDiscrimOfPattern, emit DecisionTreeTest.StringLengthZero for TPat_const (Const.String "").
  3. Update discrimsEq and discrimWithinSimultaneousClass to support the new case.
  4. In isDiscrimSubsumedBy, ensure null subsumes empty string (avoids emitting a second null check).
  5. In BuildSwitch, emit code that guards .Length checks with a null test unless a previous pattern already filtered null. Never access .Length if value might be null.

Testing Steps

  • Add/modify tests for typechecking, IL, and runtime under:
    • tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs
    • tests/FSharp.Compiler.ComponentTests/EmittedIL/PatternMatching/PatternMatchingTests.fs
    • tests/FSharp.Compiler.ComponentTests/Language/Execution/ExecutionTests.fs
  • Edge case: Programmer omits "null" pattern, but the value is null.
  • Edge case: Both null and "" handled—confirm only one null check is emitted in generated IL.
  • Rerun and compare existing IL tests to confirm no regressions or unsafe changes.
  • Compile-and-run tests for ("", null, non-empty) must never crash and yield correct output.

Safety

  • Never call .Length if the value could be null. Always cover missing null case in tests.

Example Test:

let f (x: string) = match x with | "" -> "empty" | _ -> "other"
// Should not crash for null, must print: "empty,other,other" for ("", null, "x")

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Copilot AI and others added 2 commits January 5, 2026 16:14
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Copilot AI changed the title [WIP] Optimize F# pattern match for empty string equality Optimize empty string pattern matching with null-safe length checks Jan 5, 2026
Copilot AI requested a review from T-Gro January 5, 2026 16:22
…ion in BuildSwitch only

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

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
…, move release notes to 10.0.200

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

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
@T-Gro
Copy link
Member

T-Gro commented Jan 13, 2026

/run ilverify

@github-actions
Copy link
Contributor

🔧 CLI Command Report

  • Command: /run ilverify
  • Outcome: success

✅ Patch applied:
- Files changed: 4
- Lines changed: 126

@T-Gro T-Gro marked this pull request as ready for review January 14, 2026 14:07
@T-Gro T-Gro requested a review from a team as a code owner January 14, 2026 14:07
@T-Gro T-Gro requested a review from abonie January 14, 2026 14:07
@T-Gro T-Gro enabled auto-merge (squash) January 14, 2026 14:44
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
auto-merge was automatically disabled January 20, 2026 09:02

Head branch was pushed to by a user without write access

@T-Gro T-Gro enabled auto-merge (squash) January 22, 2026 12:00
@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 22, 2026
@T-Gro
Copy link
Member

T-Gro commented Jan 22, 2026

/run ilverify

@github-actions
Copy link
Contributor

🔧 CLI Command Report

  • Command: /run ilverify
  • Outcome: success

✅ Patch applied:
- Files changed: 4
- Lines changed: 52

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.

5 participants