Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions EXECUTIVE_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# FSharpPlus curryN Regression Fix - Executive Summary

## The Bug

FSharpPlus `curryN` pattern worked in F# SDK 8.0 but failed in SDK 9.0+ with error FS0030 (value restriction):

```fsharp
let _x1 = curryN f1 100 // SDK 8: OK | SDK 9+: FS0030 error
```

## Root Cause

In `CheckDeclarations.fs`, the `ApplyDefaults` function processes unsolved type variables at the end of type checking. The existing code only solved typars with `StaticReq <> None`:

```fsharp
if (tp.StaticReq <> TyparStaticReq.None) then
ChooseTyparSolutionAndSolve cenv.css denvAtEnd tp
```

**The problem**: Some SRTP (Statically Resolved Type Parameter) typars have `MayResolveMember` constraints but `StaticReq=None`. These typars were being skipped, leaving them unsolved. When `CheckValueRestriction` ran next, it found unsolved typars and reported FS0030.

## Why This Is a Bug

The `ApplyDefaults` code checks `StaticReq <> None` to identify SRTP typars that need solving. However, a typar may participate in an SRTP constraint (having a `MayResolveMember` constraint) without having `StaticReq` set. This can happen when:

1. The typar is the **result type** of an SRTP method call, not the head type
2. The typar is constrained through SRTP constraints but isn't directly marked with `^`

**Key insight from instrumentation:**
```
- ? (StaticReq=None, Constraints=[MayResolveMember, CoercesTo]) ← HAS SRTP CONSTRAINT!
[ApplyDefaults] After processing: 17 still unsolved ← SRTP typar SKIPPED because StaticReq=None
```

The condition `tp.StaticReq <> None` was too narrow - it missed typars that have SRTP constraints but no explicit static requirement.

## Regression Analysis - Git Blame Evidence

**Root Cause: PR #15181 (commit `b73be1584`) - Nullness Checking Feature**

The regression was introduced by `FreshenTypar` added in PR #15181:

```fsharp
// src/Compiler/Checking/NameResolution.fs:1600-1604
let FreshenTypar (g: TcGlobals) rigid (tp: Typar) =
let clearStaticReq = g.langVersion.SupportsFeature LanguageFeature.InterfacesWithAbstractStaticMembers
let staticReq = if clearStaticReq then TyparStaticReq.None else tp.StaticReq // ← BUG!
...
```

**The Mechanism:**

1. **SDK 8**: `FreshenTypar` did not exist. When typars were freshened, `StaticReq` was preserved from the original typar.

2. **SDK 9+**: When `InterfacesWithAbstractStaticMembers` is enabled (always on), `FreshenTypar` **clears `StaticReq` to `None`** unconditionally.

3. **Effect**: SRTP typars still have `MayResolveMember` constraints, but lose their `StaticReq` marker.

4. **Consequence**: `ApplyDefaults` checks `if tp.StaticReq <> None` → returns false → typar never solved → FS0030 error.

**The fix** adds an alternative check for `MayResolveMember` constraints directly, making `ApplyDefaults` robust against this `StaticReq` clearing.

## The Fix

Added a check for `MayResolveMember` constraints in addition to `StaticReq`:

```fsharp
let hasSRTPConstraint = tp.Constraints |> List.exists (function TyparConstraint.MayResolveMember _ -> true | _ -> false)
if (tp.StaticReq <> TyparStaticReq.None) || hasSRTPConstraint then
ChooseTyparSolutionAndSolve cenv.css denvAtEnd tp
```

## Verification

- ✅ New curryN-style regression test passes
- ✅ FSharpPlus `curryN` pattern compiles without type annotations
- ✅ No regressions introduced in SRTP-related tests
14 changes: 14 additions & 0 deletions azure-pipelines-PR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -914,3 +914,17 @@ stages:
buildScript: build.sh
displayName: FSharpPlus_Linux
useVmImage: $(UbuntuMachineQueueName)
- repo: fsprojects/FSharpPlus
commit: 2648efe
buildScript: build.cmd
displayName: FsharpPlus_NET10
# remove this before merging
- repo: fsprojects/FSharpPlus
commit: 2648efe
buildScript: dotnet msbuild build.proj -t:Build;Pack;Test;AllDocs
displayName: FsharpPlus_NET10_FullTestSuite
- repo: fsprojects/FSharpPlus
commit: 2648efe
buildScript: build.sh
displayName: FsharpPlus_Net10_Linux
useVmImage: $(UbuntuMachineQueueName)
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Fixed

* Fixed SRTP resolution regression causing FS0030 value restriction errors with FSharpPlus curryN-style patterns in .NET 9 SDK. ([PR #19218](https://github.com/dotnet/fsharp/pull/19218))
* Type relations cache: optimize key generation ([Issue #19116](https://github.com/dotnet/fsharp/issues/18767)) ([PR #19120](https://github.com/dotnet/fsharp/pull/19120))
* Fixed QuickParse to correctly handle optional parameter syntax with `?` prefix, resolving syntax highlighting issues. ([Issue #11008753](https://developercommunity.visualstudio.com/t/F-Highlighting-fails-on-optional-parame/11008753)) ([PR #XXXXX](https://github.com/dotnet/fsharp/pull/XXXXX))
* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19151](https://github.com/dotnet/fsharp/pull/19151))
Expand Down
Loading