Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Fixed

* Fix internal error FS0192 "Iterate2D" when a `[<ReflectedDefinition(true)>]` parameter auto-quotes an argument that captures a not-yet-generalized use of an inferred generically-recursive function. The auto-quoted (`Expr.WithValue`) copy now keeps a fresh link to the recursive-value use so it receives the same inferred type arguments as the executable expression at the letrec point. ([Issue #20379](https://github.com/dotnet/fsharp/issues/20379))
* Fix `NativePtr.stackalloc` nested in a larger expression (e.g. a call argument or the right of an assignment) producing an assembly that throws `InvalidProgramException` at load. ([Issue #8083](https://github.com/dotnet/fsharp/issues/8083), [PR #20302](https://github.com/dotnet/fsharp/pull/20302))
* Fix internal error "Unexpected generalized type variables when compiling an active pattern" when an active pattern is used in a `let` binding whose right-hand side is a generic value, e.g. `let (T) = id`. Such a binding is now checked like the equivalent `match` and is not generalized. ([Issue #16856](https://github.com/dotnet/fsharp/issues/16856), [PR #20383](https://github.com/dotnet/fsharp/pull/20383))
* Fix Release-only (`--optimize+`) `System.InvalidProgramException` from `Seq.collect` / `yield!` over a value-type (struct) collection implementing `seq<'T>` (e.g. `ImmutableArray<_>`) when materialised with `List.ofSeq` / `Seq.toList` / `Seq.toArray` or a list/array comprehension. The collector lowering now boxes a struct sub-collection to `seq<'T>` before calling `AddMany`/`AddManyAndClose` (matching the coercion the type checker already inserts for `yield!`), and uses `unit` as the try/finally result type instead of the body type (removing a spurious `ldnull` store). ([Issue #20203](https://github.com/dotnet/fsharp/issues/20203))
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1491,7 +1491,7 @@ module internal Makers =
// Use "Expr.WithValue" if it exists in FSharp.Core
match vref.TryDeref with
| ValueSome _ ->
let copyOfExpr = copyExpr g ValCopyFlag.CloneAll e1
let copyOfExpr = copyExprKeepingRecursiveValLinks g ValCopyFlag.CloneAll e1
let quoteOfCopyOfExpr = Expr.Quote(copyOfExpr, ref None, false, m, qty)

mkApps
Expand Down
66 changes: 63 additions & 3 deletions src/Compiler/TypedTree/TypedTreeOps.Remapping.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1654,9 +1654,42 @@ module internal ExprRemapping =
tps', tmenvinner

type RemapContext =
{ g: TcGlobals; stackGuard: StackGuard }
{
g: TcGlobals
stackGuard: StackGuard
// When set, an Expr.Link that refers to a recursive value still in its letrec scope is copied as a
// fresh link that keeps pointing at the original fixup node, rather than being inlined at copy time.
// This lets an auto-quoted (WithValue) copy of a recursive-value use receive the inferred type
// arguments that AdjustAndForgetUsesOfRecValue inserts at the letrec point. See issue #20379.
keepRecursiveValLinks: bool
}

let mkRemapContext g stackGuard = { g = g; stackGuard = stackGuard }
let mkRemapContext g stackGuard =
{
g = g
stackGuard = stackGuard
keepRecursiveValLinks = false
}

/// Detect an Expr.Link that stands for a use of a recursive *function* value which is still within its
/// letrec scope (and will therefore be fixed up by AdjustAndForgetUsesOfRecValue once type arguments are
/// inferred). Only function-valued recursive bindings are matched: they are bound as lambdas and so are
/// never rewritten by the lazy-initialization morph in EliminateInitializationGraphs, whereas a monomorphic
/// recursive *data* value would have this same shared fixup node re-mutated to a lazy 'Force', leaking that
/// node into the captured quotation. See issue #20379.
let isRecursiveValFixupLink (eref: Expr ref) =
match stripDebugPoints eref.Value with
| Expr.Val(vref, _, _)
// A recursive-use fixup node is always a type-only application with no value args (see mkTyAppExpr and
// the shape AdjustAndForgetUsesOfRecValue accepts), so match that exact shape.
| Expr.App(Expr.Val(vref, _, _), _, _, [], _) ->
match vref.RecursiveValInfo with
| ValInRecScope _ ->
match vref.ValReprInfo with
| Some info -> info.NumCurriedArgs > 0
| None -> false
| ValNotInRecScope -> false
| _ -> false

let rec remapAttribImpl ctxt tmenv (Attrib(tcref, kind, args, props, isGetOrSetAttr, targets, m)) =
Attrib(
Expand Down Expand Up @@ -1862,7 +1895,14 @@ module internal ExprRemapping =

| Expr.App(e1, e1ty, tyargs, args, m) -> remapAppExpr ctxt compgen tmenv (e1, e1ty, tyargs, args, m) expr

| Expr.Link eref -> remapExprImpl ctxt compgen tmenv eref.Value
| Expr.Link eref ->
if ctxt.keepRecursiveValLinks && isRecursiveValFixupLink eref then
// Keep a fresh link that still points at the original recursive-use fixup node so the
// quoted copy also receives the inferred type arguments inserted later at the letrec point,
// instead of snapshotting the not-yet-generalized value. See issue #20379.
Expr.Link(ref (Expr.Link eref))
else
remapExprImpl ctxt compgen tmenv eref.Value

| Expr.StaticOptimization(cs, e2, e3, m) ->
// note that type instantiation typically resolve the static constraints here
Expand Down Expand Up @@ -2445,6 +2485,7 @@ module internal ExprRemapping =
{
g = g
stackGuard = StackGuard("RemapExprStackGuardDepth")
keepRecursiveValLinks = false
}

remapAttribImpl ctxt tmenv attrib
Expand All @@ -2454,6 +2495,7 @@ module internal ExprRemapping =
{
g = g
stackGuard = StackGuard("RemapExprStackGuardDepth")
keepRecursiveValLinks = false
}

remapExprImpl ctxt compgen tmenv expr
Expand All @@ -2463,6 +2505,7 @@ module internal ExprRemapping =
{
g = g
stackGuard = StackGuard("RemapExprStackGuardDepth")
keepRecursiveValLinks = false
}

remapPossibleForallTyImpl ctxt tmenv ty
Expand All @@ -2472,6 +2515,7 @@ module internal ExprRemapping =
{
g = g
stackGuard = StackGuard("RemapExprStackGuardDepth")
keepRecursiveValLinks = false
}

copyAndRemapAndBindModTy ctxt compgen Remap.Empty mtyp |> fst
Expand All @@ -2481,6 +2525,20 @@ module internal ExprRemapping =
{
g = g
stackGuard = StackGuard("RemapExprStackGuardDepth")
keepRecursiveValLinks = false
}

remapExprImpl ctxt compgen Remap.Empty e

/// Copy an expression for use as the definition inside an auto-quotation (Expr.WithValue), keeping fresh
/// links to any recursive-value uses that are still within their letrec scope so the copy also receives
/// the inferred type arguments applied later by AdjustAndForgetUsesOfRecValue. See issue #20379.
let copyExprKeepingRecursiveValLinks g compgen e =
let ctxt =
{
g = g
stackGuard = StackGuard("RemapExprStackGuardDepth")
keepRecursiveValLinks = true
}

remapExprImpl ctxt compgen Remap.Empty e
Expand All @@ -2490,6 +2548,7 @@ module internal ExprRemapping =
{
g = g
stackGuard = StackGuard("RemapExprStackGuardDepth")
keepRecursiveValLinks = false
}

remapImplFile ctxt compgen Remap.Empty e |> fst
Expand All @@ -2499,6 +2558,7 @@ module internal ExprRemapping =
{
g = g
stackGuard = StackGuard("RemapExprStackGuardDepth")
keepRecursiveValLinks = false
}

remapExprImpl ctxt CloneAll (mkInstRemap tpinst) e
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.Remapping.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ module internal ExprRemapping =
/// Copy an entire expression using the given copying flags
val copyExpr: TcGlobals -> ValCopyFlag -> Expr -> Expr

/// Copy an expression for use as the definition inside an auto-quotation (Expr.WithValue), keeping fresh
/// links to recursive-value uses still within their letrec scope so the copy also receives the inferred
/// type arguments applied later by AdjustAndForgetUsesOfRecValue. See issue #20379.
val copyExprKeepingRecursiveValLinks: TcGlobals -> ValCopyFlag -> Expr -> Expr

/// Copy an entire implementation file using the given copying flags
val copyImplFile: TcGlobals -> ValCopyFlag -> CheckedImplFile -> CheckedImplFile

Expand Down
Loading
Loading