From c5feb775d3cb0bbf4c5b4bbdabec34a55a1635e8 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <Tomas.Grosup@gmail.com>
Date: Mon, 16 Feb 2026 16:14:26 +0100
Subject: [PATCH 1/7] Fix runtime crashes: TypeLoadException,
 InvalidProgramException, stack corruption

Fix 6 codegen bugs that cause hard crashes or invalid IL:
- #11132: voidptr delegate TypeLoadException
- #13447: tail. + localloc stack corruption
- #14492: inline constraints TypeLoadException in Release
- #14508: nativeptr in interfaces TypeLoadException
- #18319: literal upcast missing box instruction
- #18956: decimal literal InvalidProgramException in Debug

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 .../.FSharp.Compiler.Service/10.0.300.md      |   6 +
 src/Compiler/AbstractIL/il.fs                 |   9 +
 src/Compiler/AbstractIL/il.fsi                |   1 +
 src/Compiler/CodeGen/EraseClosures.fs         |  36 +-
 src/Compiler/CodeGen/IlxGen.fs                | 159 +++++++--
 .../CodeGenRegressions_Crashes.fs             | 327 ++++++++++++++++++
 .../StaticOptimizations/String_Enum.fs.il.bsl |   1 -
 .../TestFunction03.fs.OptimizeOff.il.bsl      |  11 -
 ...estFunction03.fs.OptimizeOn.il.release.bsl |   1 -
 .../TestFunction03b.fs.OptimizeOff.il.bsl     |  11 -
 ...stFunction03b.fs.OptimizeOn.il.release.bsl |   1 -
 .../TestFunction03c.fs.OptimizeOff.il.bsl     |  13 +-
 ...stFunction03c.fs.OptimizeOn.il.release.bsl |   1 -
 .../FSharp.Compiler.ComponentTests.fsproj     |   1 +
 14 files changed, 513 insertions(+), 65 deletions(-)
 create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs

diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md
index 7a8279aac29..53802cc1153 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md
@@ -25,6 +25,12 @@
 * F# Scripts: Fix default reference paths resolving when an SDK directory is specified. ([PR #19270](https://github.com/dotnet/fsharp/pull/19270))
 * Improve static compilation of state machines. ([PR #19297](https://github.com/dotnet/fsharp/pull/19297))
 * Fix a bug where `let!` and `use!` were incorrectly allowed outside computation expressions. [PR #19347](https://github.com/dotnet/fsharp/pull/19347)
+* Fix TypeLoadException when creating delegate with voidptr parameter. (Issue [#11132](https://github.com/dotnet/fsharp/issues/11132), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Suppress tail calls when localloc (NativePtr.stackalloc) is used. (Issue [#13447](https://github.com/dotnet/fsharp/issues/13447), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Fix TypeLoadException in Release builds with inline constraints. (Issue [#14492](https://github.com/dotnet/fsharp/issues/14492), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Fix nativeptr in interfaces leads to TypeLoadException. (Issue [#14508](https://github.com/dotnet/fsharp/issues/14508), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Fix box instruction for literal upcasts. (Issue [#18319](https://github.com/dotnet/fsharp/issues/18319), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Fix Decimal Literal causes InvalidProgramException in Debug builds. (Issue [#18956](https://github.com/dotnet/fsharp/issues/18956), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
 
 ### Added
 * FSharpType: add ImportILType ([PR #19300](https://github.com/dotnet/fsharp/pull/19300))
diff --git a/src/Compiler/AbstractIL/il.fs b/src/Compiler/AbstractIL/il.fs
index b620ee8e7e5..8e836ef548e 100644
--- a/src/Compiler/AbstractIL/il.fs
+++ b/src/Compiler/AbstractIL/il.fs
@@ -3375,6 +3375,15 @@ let mkILSimpleTypar nm =
         MetadataIndex = NoMetadataIdx
     }
 
+let stripILGenericParamConstraints (gp: ILGenericParameterDef) =
+    { gp with
+        Constraints = []
+        HasReferenceTypeConstraint = false
+        HasNotNullableValueTypeConstraint = false
+        HasDefaultConstructorConstraint = false
+        HasAllowsRefStruct = false
+    }
+
 let genericParamOfGenericActual (_ga: ILType) = mkILSimpleTypar "T"
 
 let mkILFormalTypars (x: ILGenericArgsList) = List.map genericParamOfGenericActual x
diff --git a/src/Compiler/AbstractIL/il.fsi b/src/Compiler/AbstractIL/il.fsi
index 2071b8658e4..3539e235518 100644
--- a/src/Compiler/AbstractIL/il.fsi
+++ b/src/Compiler/AbstractIL/il.fsi
@@ -2096,6 +2096,7 @@ val internal mkILFormalNamedTy: ILBoxity -> ILTypeRef -> ILGenericParameterDef l
 val internal mkILFormalTypars: ILType list -> ILGenericParameterDefs
 val internal mkILFormalGenericArgs: int -> ILGenericParameterDefs -> ILGenericArgsList
 val internal mkILSimpleTypar: string -> ILGenericParameterDef
+val internal stripILGenericParamConstraints: ILGenericParameterDef -> ILGenericParameterDef
 
 /// Make custom attributes.
 val internal mkILCustomAttribMethRef:
diff --git a/src/Compiler/CodeGen/EraseClosures.fs b/src/Compiler/CodeGen/EraseClosures.fs
index 930e6201bf8..6aef85d40b5 100644
--- a/src/Compiler/CodeGen/EraseClosures.fs
+++ b/src/Compiler/CodeGen/EraseClosures.fs
@@ -153,7 +153,19 @@ let newIlxPubCloEnv (ilg, addMethodGeneratedAttrs, addFieldGeneratedAttrs, addFi
 
 let mkILTyFuncTy cenv = cenv.mkILTyFuncTy
 
+let inline private (|IsVoidPtr|_|) ty =
+    match ty with
+    | ILType.Ptr ILType.Void -> true
+    | _ -> false
+
+let private fixVoidPtrForGenericArg (ilg: ILGlobals) ty =
+    match ty with
+    | IsVoidPtr -> ilg.typ_IntPtr
+    | _ -> ty
+
 let mkILFuncTy cenv dty rty =
+    let dty = fixVoidPtrForGenericArg cenv.ilg dty
+    let rty = fixVoidPtrForGenericArg cenv.ilg rty
     mkILBoxedTy cenv.tref_Func[0] [ dty; rty ]
 
 let mkILCurriedFuncTy cenv dtys rty =
@@ -168,6 +180,8 @@ let typ_Func cenv (dtys: ILType list) rty =
         else
             mkFuncTypeRef cenv.ilg.fsharpCoreAssemblyScopeRef n
 
+    let dtys = dtys |> List.map (fixVoidPtrForGenericArg cenv.ilg)
+    let rty = fixVoidPtrForGenericArg cenv.ilg rty
     mkILBoxedTy tref (dtys @ [ rty ])
 
 let rec mkTyOfApps cenv apps =
@@ -190,6 +204,8 @@ let mkMethSpecForMultiApp cenv (argTys: ILType list, retTy) =
     let n = argTys.Length
     let formalArgTys = List.mapi (fun i _ -> ILType.TypeVar(uint16 i)) argTys
     let formalRetTy = ILType.TypeVar(uint16 n)
+    let argTys = argTys |> List.map (fixVoidPtrForGenericArg cenv.ilg)
+    let retTy = fixVoidPtrForGenericArg cenv.ilg retTy
     let inst = argTys @ [ retTy ]
 
     if n = 1 then
@@ -548,12 +564,14 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo =
 
                 let convil = convILMethodBody (Some nowCloSpec, boxReturnTy) clo.cloCode.Value
 
+                let specializeGenParams = addedGenParams |> List.map stripILGenericParamConstraints
+
                 let nowApplyMethDef =
                     mkILGenericVirtualMethod (
                         "Specialize",
                         ILCallingConv.Instance,
                         ILMemberAccess.Public,
-                        addedGenParams (* method is generic over added ILGenericParameterDefs *) ,
+                        specializeGenParams,
                         [],
                         mkILReturn cenv.ilg.typ_Object,
                         MethodBody.IL(notlazy convil)
@@ -681,7 +699,17 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo =
             else
                 // CASE 2b - Build an Invoke method
 
-                let nowEnvParentClass = typ_Func cenv (typesOfILParams nowParams) nowReturnTy
+                let fixedNowParams =
+                    nowParams
+                    |> List.map (fun (p: ILParameter) ->
+                        { p with
+                            Type = fixVoidPtrForGenericArg cenv.ilg p.Type
+                        })
+
+                let fixedNowReturnTy = fixVoidPtrForGenericArg cenv.ilg nowReturnTy
+
+                let nowEnvParentClass =
+                    typ_Func cenv (typesOfILParams fixedNowParams) fixedNowReturnTy
 
                 let cloTypeDef =
                     let convil = convILMethodBody (Some nowCloSpec, None) clo.cloCode.Value
@@ -690,8 +718,8 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo =
                         mkILNonGenericVirtualInstanceMethod (
                             "Invoke",
                             ILMemberAccess.Public,
-                            nowParams,
-                            mkILReturn nowReturnTy,
+                            fixedNowParams,
+                            mkILReturn fixedNowReturnTy,
                             MethodBody.IL(notlazy convil)
                         )
 
diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs
index 4e3452a2d8e..55582b6aa8a 100644
--- a/src/Compiler/CodeGen/IlxGen.fs
+++ b/src/Compiler/CodeGen/IlxGen.fs
@@ -1398,6 +1398,28 @@ let TryStorageForWitness (_g: TcGlobals) eenv (w: TraitWitnessInfo) =
 let IsValRefIsDllImport g (vref: ValRef) =
     ValHasWellKnownAttribute g WellKnownValAttributes.DllImportAttribute vref.Deref
 
+/// Check if a type contains nativeptr with a type parameter from the given set.
+/// Used to detect interface implementations that need 'native int' return type.
+let hasNativePtrWithTypar (g: TcGlobals) (typars: Typar list) ty =
+    let rec check ty =
+        let ty = stripTyEqns g ty
+
+        match ty with
+        | TType_app(tcref, tinst, _) when tyconRefEq g g.nativeptr_tcr tcref ->
+            tinst
+            |> List.exists (fun t ->
+                match stripTyEqns g t with
+                | TType_var(tp, _) -> typars |> List.exists (fun tp2 -> tp.Stamp = tp2.Stamp)
+                | _ -> false)
+        | TType_app(_, tinst, _) -> tinst |> List.exists check
+        | TType_fun(d, r, _) -> check d || check r
+        | TType_tuple(_, tys) -> tys |> List.exists check
+        | TType_anon(_, tys) -> tys |> List.exists check
+        | TType_forall(_, t) -> check t
+        | _ -> false
+
+    check ty
+
 /// Determine how a top level value is represented, when it is being represented
 /// as a method.
 let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) =
@@ -1425,7 +1447,34 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) =
 
     let ilActualRetTy =
         let ilRetTy = GenReturnType cenv m tyenvUnderTypars returnTy
-        if isCtor || cctor then ILType.Void else ilRetTy
+
+        if isCtor || cctor then
+            ILType.Void
+        // When implementing an interface slot with nativeptr<'T> where 'T is an interface
+        // type parameter and the interface type args are concrete, the interface method
+        // returns 'native int'. The method def must also return 'native int' to match.
+        elif memberInfo.MemberFlags.IsOverrideOrExplicitImpl then
+            match
+                memberInfo.ImplementedSlotSigs
+                |> List.tryPick (fun (TSlotSig(_, ty, sctps, _, _, sRetTy)) ->
+                    let interfaceTypeArgs = argsOfAppTy g ty
+
+                    if
+                        not sctps.IsEmpty
+                        && (freeInTypes CollectTypars interfaceTypeArgs).FreeTypars.IsEmpty
+                        && sRetTy |> Option.exists (hasNativePtrWithTypar g sctps)
+                    then
+                        Some(sctps, sRetTy)
+                    else
+                        None)
+            with
+            | Some(sctps, sRetTy) ->
+                // Generate return type using the slot's type params so nativeptr<'T> → native int
+                let slotEnv = TypeReprEnv.Empty.ForTypars sctps
+                GenReturnType cenv m slotEnv sRetTy
+            | None -> ilRetTy
+        else
+            ilRetTy
 
     let ilTy =
         GenType cenv m tyenvUnderTypars (mkWoNullAppTy parentTcref (List.map mkTyparTy ctps))
@@ -2516,6 +2565,8 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs
     let mutable hasDebugPoints = false
     let mutable anyDocument = None // we collect an arbitrary document in order to emit the header FeeFee if needed
 
+    let mutable hasStackAllocatedLocals = false
+
     let codeLabelToPC: Dictionary<ILCodeLabel, int> = Dictionary<_, _>(10)
 
     let codeLabelToCodeLabel: Dictionary<ILCodeLabel, ILCodeLabel> =
@@ -2574,11 +2625,19 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs
     member cgbuf.EmitInstr(pops, pushes, i) =
         cgbuf.DoPops pops
         cgbuf.DoPushes pushes
+
+        if i = I_localloc then
+            hasStackAllocatedLocals <- true
+
         codebuf.Add i
 
     member cgbuf.EmitInstrs(pops, pushes, is) =
         cgbuf.DoPops pops
         cgbuf.DoPushes pushes
+
+        if is |> List.exists (fun i -> i = I_localloc) then
+            hasStackAllocatedLocals <- true
+
         is |> List.iter codebuf.Add
 
     member private _.EnsureNopBetweenDebugPoints() =
@@ -2711,6 +2770,8 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs
     member _.HasPinnedLocals() =
         locals |> Seq.exists (fun (_, _, isFixed, _) -> isFixed)
 
+    member _.HasStackAllocatedLocals() = hasStackAllocatedLocals
+
     member _.Close() =
 
         let instrs = codebuf.ToArray()
@@ -3341,32 +3402,50 @@ and GenConstant cenv cgbuf eenv (c, m, ty) sequel =
         match TryEliminateDesugaredConstants g m c with
         | Some e -> GenExpr cenv cgbuf eenv e Continue
         | None ->
-            let emitInt64Constant i =
+            let needsBoxingToTargetTy =
+                (match ilTy with
+                 | ILType.Value _ -> false
+                 | _ -> true)
+
+            // Wraps an emitter: calls it, then boxes if target type is not a value type (e.g. literal upcast to obj).
+            let inline emitAndBoxIfNeeded emitter uty arg =
+                emitter uty arg
+
+                if needsBoxingToTargetTy then
+                    CG.EmitInstr cgbuf (pop 1) (Push [ ilTy ]) (I_box uty)
+
+            let emitInt64Constant uty i =
                 // see https://github.com/dotnet/fsharp/pull/3620
                 // and https://github.com/dotnet/fsharp/issue/8683
                 // and https://github.com/dotnet/roslyn/blob/98f12bb/src/Compilers/Core/Portable/CodeGen/ILBuilderEmit.cs#L679
                 if i >= int64 Int32.MinValue && i <= int64 Int32.MaxValue then
-                    CG.EmitInstrs cgbuf (pop 0) (Push [ ilTy ]) [ mkLdcInt32 (int32 i); AI_conv DT_I8 ]
+                    CG.EmitInstrs cgbuf (pop 0) (Push [ uty ]) [ mkLdcInt32 (int32 i); AI_conv DT_I8 ]
                 elif i >= int64 UInt32.MinValue && i <= int64 UInt32.MaxValue then
-                    CG.EmitInstrs cgbuf (pop 0) (Push [ ilTy ]) [ mkLdcInt32 (int32 i); AI_conv DT_U8 ]
+                    CG.EmitInstrs cgbuf (pop 0) (Push [ uty ]) [ mkLdcInt32 (int32 i); AI_conv DT_U8 ]
                 else
-                    CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (iLdcInt64 i)
+                    CG.EmitInstr cgbuf (pop 0) (Push [ uty ]) (iLdcInt64 i)
+
+            let emitConst uty instr =
+                CG.EmitInstr cgbuf (pop 0) (Push [ uty ]) instr
+
+            let emitConstI uty instrs =
+                CG.EmitInstrs cgbuf (pop 0) (Push [ uty ]) instrs
 
             match c with
-            | Const.Bool b -> CG.EmitInstr cgbuf (pop 0) (Push [ g.ilg.typ_Bool ]) (mkLdcInt32 (if b then 1 else 0))
-            | Const.SByte i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i))
-            | Const.Int16 i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i))
-            | Const.Int32 i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 i)
-            | Const.Int64 i -> emitInt64Constant i
-            | Const.IntPtr i -> CG.EmitInstrs cgbuf (pop 0) (Push [ ilTy ]) [ iLdcInt64 i; AI_conv DT_I ]
-            | Const.Byte i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i))
-            | Const.UInt16 i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i))
-            | Const.UInt32 i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i))
-            | Const.UInt64 i -> emitInt64Constant (int64 i)
-            | Const.UIntPtr i -> CG.EmitInstrs cgbuf (pop 0) (Push [ ilTy ]) [ iLdcInt64 (int64 i); AI_conv DT_U ]
-            | Const.Double f -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (AI_ldc(DT_R8, ILConst.R8 f))
-            | Const.Single f -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (AI_ldc(DT_R4, ILConst.R4 f))
-            | Const.Char c -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int c))
+            | Const.Bool b -> emitAndBoxIfNeeded emitConst g.ilg.typ_Bool (mkLdcInt32 (if b then 1 else 0))
+            | Const.SByte i -> emitAndBoxIfNeeded emitConst g.ilg.typ_SByte (mkLdcInt32 (int32 i))
+            | Const.Int16 i -> emitAndBoxIfNeeded emitConst g.ilg.typ_Int16 (mkLdcInt32 (int32 i))
+            | Const.Int32 i -> emitAndBoxIfNeeded emitConst g.ilg.typ_Int32 (mkLdcInt32 i)
+            | Const.Int64 i -> emitAndBoxIfNeeded emitInt64Constant g.ilg.typ_Int64 i
+            | Const.IntPtr i -> emitAndBoxIfNeeded emitConstI g.ilg.typ_IntPtr [ iLdcInt64 i; AI_conv DT_I ]
+            | Const.Byte i -> emitAndBoxIfNeeded emitConst g.ilg.typ_Byte (mkLdcInt32 (int32 i))
+            | Const.UInt16 i -> emitAndBoxIfNeeded emitConst g.ilg.typ_UInt16 (mkLdcInt32 (int32 i))
+            | Const.UInt32 i -> emitAndBoxIfNeeded emitConst g.ilg.typ_UInt32 (mkLdcInt32 (int32 i))
+            | Const.UInt64 i -> emitAndBoxIfNeeded emitInt64Constant g.ilg.typ_UInt64 (int64 i)
+            | Const.UIntPtr i -> emitAndBoxIfNeeded emitConstI g.ilg.typ_UIntPtr [ iLdcInt64 (int64 i); AI_conv DT_U ]
+            | Const.Double f -> emitAndBoxIfNeeded emitConst g.ilg.typ_Double (AI_ldc(DT_R8, ILConst.R8 f))
+            | Const.Single f -> emitAndBoxIfNeeded emitConst g.ilg.typ_Single (AI_ldc(DT_R4, ILConst.R4 f))
+            | Const.Char c -> emitAndBoxIfNeeded emitConst g.ilg.typ_Char (mkLdcInt32 (int c))
             | Const.String s -> GenString cenv cgbuf s
             | Const.Unit -> GenUnit cenv eenv m cgbuf
             | Const.Zero -> GenDefaultValue cenv cgbuf eenv (ty, m)
@@ -4554,6 +4633,7 @@ and CanTailcall
     // Can't tailcall with a .NET 2.0 generic constrained call since it involves a byref
     // Can't tailcall when there are pinned locals since the stack frame must remain alive
     let hasPinnedLocals = cgbuf.HasPinnedLocals()
+    let hasStackAllocatedLocals = cgbuf.HasStackAllocatedLocals()
 
     if
         not hasStructObjArg
@@ -4564,6 +4644,7 @@ and CanTailcall
         && not isSelfInit
         && not makesNoCriticalTailcalls
         && not hasPinnedLocals
+        && not hasStackAllocatedLocals
         &&
 
         // We can tailcall even if we need to generate "unit", as long as we're about to throw the value away anyway as par of the return.
@@ -5894,6 +5975,8 @@ and GenFormalReturnType m cenv eenvFormal returnTy : ILReturn =
 and instSlotParam inst (TSlotParam(nm, ty, inFlag, fl2, fl3, attrs)) =
     TSlotParam(nm, instType inst ty, inFlag, fl2, fl3, attrs)
 
+and containsNativePtrWithTypar (g: TcGlobals) (typars: Typar list) ty = hasNativePtrWithTypar g typars ty
+
 and GenActualSlotsig
     m
     cenv
@@ -5902,14 +5985,44 @@ and GenActualSlotsig
     methTyparsOfOverridingMethod
     (methodParams: Val list)
     =
+    let g = cenv.g
     let ilSlotParams = List.concat ilSlotParams
 
+    let interfaceTypeArgs = argsOfAppTy g ty
+
     let instForSlotSig =
-        mkTyparInst (ctps @ mtps) (argsOfAppTy cenv.g ty @ generalizeTypars methTyparsOfOverridingMethod)
+        mkTyparInst (ctps @ mtps) (interfaceTypeArgs @ generalizeTypars methTyparsOfOverridingMethod)
+
+    let interfaceTypeArgsAreConcrete =
+        not ctps.IsEmpty
+        && (freeInTypes CollectTypars interfaceTypeArgs).FreeTypars.IsEmpty
+
+    let slotHasNativePtrWithCtps =
+        interfaceTypeArgsAreConcrete
+        && (ilSlotParams
+            |> List.exists (fun (TSlotParam(_, ty, _, _, _, _)) -> containsNativePtrWithTypar g ctps ty)
+            || ilSlotRetTy |> Option.exists (containsNativePtrWithTypar g ctps))
+
+    let eenvForSlotGen =
+        if slotHasNativePtrWithCtps then
+            EnvForTypars ctps eenv
+        else
+            eenv
+
+    // When the slot has nativeptr with concrete interface type args, don't substitute
+    // the class type params (ctps) - only substitute method type params (mtps).
+    // This keeps nativeptr<'T> unsubstituted so it generates 'native int' in IL,
+    // matching the interface method's signature. Without this, nativeptr<'T> would be
+    // substituted to nativeptr<concrete> which generates 'T*', causing a TypeLoadException.
+    let instForSlotSigGen =
+        if slotHasNativePtrWithCtps then
+            mkTyparInst mtps (generalizeTypars methTyparsOfOverridingMethod)
+        else
+            instForSlotSig
 
     let ilParams =
         ilSlotParams
-        |> List.map (instSlotParam instForSlotSig >> GenSlotParam m cenv eenv)
+        |> List.map (instSlotParam instForSlotSigGen >> GenSlotParam m cenv eenvForSlotGen)
 
     // Use the better names if available
     let ilParams =
@@ -5920,7 +6033,7 @@ and GenActualSlotsig
             ilParams
 
     let ilRetTy =
-        GenReturnType cenv m eenv.tyenv (Option.map (instType instForSlotSig) ilSlotRetTy)
+        GenReturnType cenv m eenvForSlotGen.tyenv (Option.map (instType instForSlotSigGen) ilSlotRetTy)
 
     let iLRet = mkILReturn ilRetTy
 
@@ -5928,7 +6041,7 @@ and GenActualSlotsig
         match ilSlotRetTy with
         | None -> iLRet
         | Some t ->
-            match GenAdditionalAttributesForTy cenv.g t with
+            match GenAdditionalAttributesForTy g t with
             | [] -> iLRet
             | attrs -> iLRet.WithCustomAttrs(mkILCustomAttrs attrs)
 
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
new file mode 100644
index 00000000000..6a20d871776
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
@@ -0,0 +1,327 @@
+// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.
+
+namespace EmittedIL
+
+open Xunit
+open FSharp.Test
+open FSharp.Test.Compiler
+open FSharp.Test.Utilities
+
+module CodeGenRegressions_Crashes =
+
+    let private getActualIL (result: CompilationResult) =
+        match result with
+        | CompilationResult.Success s ->
+            match s.OutputPath with
+            | Some p ->
+                let (_, _, actualIL) = ILChecker.verifyILAndReturnActual [] p [ "// dummy" ]
+                actualIL
+            | None -> failwith "No output path"
+        | _ -> failwith "Compilation failed"
+
+    // https://github.com/dotnet/fsharp/issues/18956
+    [<Fact>]
+    let ``Issue_18956_DecimalConstantInvalidProgram`` () =
+        let source = """
+module A =
+    [<Literal>]
+    let B = 42m
+
+[<EntryPoint>]
+let main args =
+    printfn "%M" A.B
+    0
+"""
+        FSharp source
+        |> asExe
+        |> withDebug
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/18319
+    [<Fact>]
+    let ``Issue_18319_LiteralUpcastMissingBox`` () =
+        let source = """
+module Test
+
+[<Literal>]
+let badobj: System.ValueType = 1
+
+[<EntryPoint>]
+let main _ =
+    System.Console.WriteLine(badobj)
+    0
+"""
+        FSharp source
+        |> asExe
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/18319
+    // Second case from issue: literal upcast used as default parameter value
+    [<Fact>]
+    let ``Issue_18319_LiteralUpcastDefaultParam`` () =
+        let source = """
+module Test
+
+open System.Runtime.InteropServices
+
+[<Literal>]
+let lit: System.ValueType = 1
+
+type C() =
+    static member M([<Optional; DefaultParameterValue(lit)>] param: System.ValueType) =
+        System.Console.WriteLine(param)
+
+[<EntryPoint>]
+let main _ =
+    C.M()
+    C.M(42)
+    0
+"""
+        FSharp source
+        |> asExe
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14508
+    [<Fact>]
+    let ``Issue_14508_NativeptrInInterfaces_CompileOnly`` () =
+        let source = """
+module Test
+
+open Microsoft.FSharp.NativeInterop
+
+type IFoo<'T when 'T : unmanaged> =
+    abstract member Pointer : nativeptr<'T>
+
+type Broken() =
+    member x.Pointer : nativeptr<int> = Unchecked.defaultof<_>
+    interface IFoo<int> with
+        member x.Pointer = x.Pointer
+
+type Working<'T when 'T : unmanaged>() =
+    member x.Pointer : nativeptr<'T> = Unchecked.defaultof<_>
+    interface IFoo<'T> with
+        member x.Pointer = x.Pointer
+"""
+        FSharp source
+        |> asLibrary
+        |> compile
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14508
+    [<Fact>]
+    let ``Issue_14508_NativeptrInInterfaces_RuntimeWorking`` () =
+        let source = """
+open Microsoft.FSharp.NativeInterop
+
+type IFoo<'T when 'T : unmanaged> =
+    abstract member Pointer : nativeptr<'T>
+
+type Working<'T when 'T : unmanaged>() =
+    member x.Pointer : nativeptr<'T> = Unchecked.defaultof<_>
+    interface IFoo<'T> with
+        member x.Pointer = x.Pointer
+
+printfn "Working type loaded successfully"
+"""
+        FSharp source
+        |> asExe
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14508
+    [<Fact>]
+    let ``Issue_14508_NativeptrInInterfaces_RuntimeBroken`` () =
+        let source = """
+open Microsoft.FSharp.NativeInterop
+
+type IFoo<'T when 'T : unmanaged> =
+    abstract member Pointer : nativeptr<'T>
+
+type Broken() =
+    member x.Pointer : nativeptr<int> = Unchecked.defaultof<_>
+    interface IFoo<int> with
+        member x.Pointer = x.Pointer
+
+let b = Broken()
+let p = (b :> IFoo<int>).Pointer
+printfn "Broken type loaded and Pointer accessed: %A" p
+"""
+        FSharp source
+        |> asExe
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14492
+    [<Fact>]
+    let ``Issue_14492_ReleaseConfigError`` () =
+        let source = """
+module Test
+
+#nowarn "3370"
+
+let inline refEquals<'a when 'a : not struct> (a : 'a) (b : 'a) = obj.ReferenceEquals (a, b)
+
+let inline tee f x = 
+    f x
+    x
+
+let memoizeLatestRef (f: 'a -> 'b) =
+    let cell = ref None
+    let f' (x: 'a) =
+        match !cell with
+        | Some (x', value) when refEquals x' x -> value
+        | _ -> f x |> tee (fun y -> cell := Some (x, y))
+    f'
+
+module BugInReleaseConfig = 
+    let test f x = 
+        printfn "%s" (f x)
+
+    let f: string -> string = memoizeLatestRef id
+
+    let run () = test f "ok"
+
+[<EntryPoint>]
+let main _ =
+    BugInReleaseConfig.run ()
+    0
+"""
+        FSharp source
+        |> asExe
+        |> withOptimize
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/13447
+    [<Fact>]
+    let ``Issue_13447_TailInstructionCorruption`` () =
+        let source = """
+module Test
+open System
+open Microsoft.FSharp.NativeInterop
+
+#nowarn "9" // Uses of this construct may result in the generation of unverifiable .NET IL code
+
+[<Struct>]
+type MyResult<'T, 'E> = 
+    | Ok of value: 'T 
+    | Error of error: 'E
+
+let useStackAlloc () : MyResult<int, string> =
+    let ptr = NativePtr.stackalloc<byte> 100
+    let span = Span<byte>(NativePtr.toVoidPtr ptr, 100)
+    span.[0] <- 42uy
+    Ok (int span.[0])
+
+let test () =
+    match useStackAlloc () with
+    | Ok v -> v
+    | Error _ -> -1
+"""
+        let actualIL =
+            FSharp source
+            |> asLibrary
+            |> withOptimize
+            |> compile
+            |> shouldSucceed
+            |> getActualIL
+
+        let useStackAllocIdx = actualIL.IndexOf("useStackAlloc")
+        Assert.True(useStackAllocIdx >= 0, "useStackAlloc method not found in IL")
+        let methodEnd = actualIL.IndexOf("\n    } ", useStackAllocIdx)
+        let methodIL = if methodEnd > 0 then actualIL.Substring(useStackAllocIdx, methodEnd - useStackAllocIdx) else actualIL.Substring(useStackAllocIdx)
+        Assert.DoesNotContain("tail.", methodIL)
+
+    // https://github.com/dotnet/fsharp/issues/11132
+    [<Fact>]
+    let ``Issue_11132_VoidptrDelegate`` () =
+        let source = """
+module Test
+#nowarn "9"
+
+open System
+
+type MyDelegate = delegate of voidptr -> unit
+
+let method (ptr: voidptr) = ()
+
+let getDelegate (m: voidptr -> unit) : MyDelegate = MyDelegate(m)
+
+let test() =
+    let d = getDelegate method
+    d.Invoke(IntPtr.Zero.ToPointer())
+
+do test()
+"""
+        FSharp source
+        |> asExe
+        |> withOptimize
+        |> compileAndRun
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14492
+    // Variant: cross-assembly inlining with constrained generics triggers Specialize closure
+    [<Fact>]
+    let ``Issue_14492_CrossAssemblyInline`` () =
+        let lib = FSharp """
+module Lib
+
+let inline refEquals<'a when 'a : not struct> (a : 'a) (b : 'a) = obj.ReferenceEquals (a, b)
+
+let inline tee f x = 
+    f x
+    x
+"""                     |> withOptimize
+                        |> asLibrary
+
+        let app = FSharp """
+module App
+
+open Lib
+
+let memoizeLatestRef (f: 'a -> 'b) =
+    let cell = ref None
+    let f' (x: 'a) =
+        match cell.Value with
+        | Some (x', value) when refEquals x' x -> value
+        | _ -> f x |> tee (fun y -> cell.Value <- Some (x, y))
+    f'
+
+module Test = 
+    let f: string -> string = memoizeLatestRef id
+    let run () = printfn "%s" (f "ok")
+
+[<EntryPoint>]
+let main _ = Test.run(); 0
+"""
+        app
+        |> withOptimize
+        |> asExe
+        |> withReferences [lib]
+        |> compileAndRun
+        |> shouldSucceed
+        |> ignore
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl
index 740505ce6a7..1af36573396 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl
@@ -540,4 +540,3 @@
 
 
 
-
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOff.il.bsl
index dd6e6ab1b40..9de983bbfa2 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOff.il.bsl
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOff.il.bsl
@@ -16,16 +16,6 @@
 
   .hash algorithm 0x00008004
   .ver 0:0:0:0
-}
-.mresource public FSharpSignatureCompressedData.assembly
-{
-  
-  
-}
-.mresource public FSharpOptimizationCompressedData.assembly
-{
-  
-  
 }
 .module assembly.exe
 
@@ -112,4 +102,3 @@
 
 
 
-
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOn.il.release.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOn.il.release.bsl
index d360b2a91eb..aa576cc3d13 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOn.il.release.bsl
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOn.il.release.bsl
@@ -123,4 +123,3 @@
 
 
 
-
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOff.il.bsl
index 6b0f2cd2642..15905929f58 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOff.il.bsl
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOff.il.bsl
@@ -16,16 +16,6 @@
 
   .hash algorithm 0x00008004
   .ver 0:0:0:0
-}
-.mresource public FSharpSignatureCompressedData.assembly
-{
-  
-  
-}
-.mresource public FSharpOptimizationCompressedData.assembly
-{
-  
-  
 }
 .module assembly.exe
 
@@ -126,4 +116,3 @@
 
 
 
-
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOn.il.release.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOn.il.release.bsl
index 0a3ae50e3e0..05caaf30094 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOn.il.release.bsl
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOn.il.release.bsl
@@ -130,4 +130,3 @@
 
 
 
-
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOff.il.bsl
index 0eeedf099d9..f67fcf38d92 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOff.il.bsl
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOff.il.bsl
@@ -8,7 +8,7 @@
 .assembly extern netstandard
 {
   .publickeytoken = (CC 7B 13 FF CD 2D DD 51 )                         
-  .ver 2:0:0:0
+  .ver 2:1:0:0
 }
 .assembly assembly
 {
@@ -21,16 +21,6 @@
 
   .hash algorithm 0x00008004
   .ver 0:0:0:0
-}
-.mresource public FSharpSignatureCompressedData.assembly
-{
-  
-  
-}
-.mresource public FSharpOptimizationCompressedData.assembly
-{
-  
-  
 }
 .module assembly.exe
 
@@ -145,4 +135,3 @@
 
 
 
-
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOn.il.release.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOn.il.release.bsl
index ad3903afb89..3dbb67a8837 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOn.il.release.bsl
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOn.il.release.bsl
@@ -141,4 +141,3 @@
 
 
 
-
diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
index f5d1048408e..5d808376295 100644
--- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
+++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
@@ -283,6 +283,7 @@
     <Compile Include="EmittedIL\CodeGenRegressions\CodeGenRegressions_TypeDefs.fs" />
     <Compile Include="EmittedIL\CodeGenRegressions\CodeGenRegressions_Signatures.fs" />
     <Compile Include="EmittedIL\CodeGenRegressions\CodeGenRegressions_Closures.fs" />
+    <Compile Include="EmittedIL\CodeGenRegressions\CodeGenRegressions_Crashes.fs" />
     <Compile Include="ErrorMessages\TypedInterpolatedStringsTests.fs" />
     <!--<Compile Include="EmittedIL\StructDefensiveCopy\StructDefensiveCopy.fs" />-->
     <Compile Include="ErrorMessages\UnsupportedAttributes.fs" />

From 8c8be870417067f13ce425c9cc2dfed354f4db69 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <Tomas.Grosup@gmail.com>
Date: Thu, 26 Feb 2026 15:12:51 +0100
Subject: [PATCH 2/7] Fix EmittedIL tests failing on net472 Desktop
 configuration

Mark Issue_13447_TailInstructionCorruption and Issue_11132_VoidptrDelegate
tests with FactForNETCOREAPP to skip on net472 Desktop test runs.

- Issue_13447 uses Span<byte> which is not available on net472
- Issue_11132 uses voidptr in closure generic args which requires
  netcore runtime support

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 .../CodeGenRegressions/CodeGenRegressions_Crashes.fs          | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
index 6a20d871776..7dd573b9b9d 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
@@ -215,7 +215,7 @@ let main _ =
         |> ignore
 
     // https://github.com/dotnet/fsharp/issues/13447
-    [<Fact>]
+    [<FactForNETCOREAPP>]
     let ``Issue_13447_TailInstructionCorruption`` () =
         let source = """
 module Test
@@ -255,7 +255,7 @@ let test () =
         Assert.DoesNotContain("tail.", methodIL)
 
     // https://github.com/dotnet/fsharp/issues/11132
-    [<Fact>]
+    [<FactForNETCOREAPP>]
     let ``Issue_11132_VoidptrDelegate`` () =
         let source = """
 module Test

From 71aec744229b7a4366f6da8584ddd36fa9a12fb9 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <Tomas.Grosup@gmail.com>
Date: Fri, 6 Mar 2026 19:17:41 +0100
Subject: [PATCH 3/7] Move release notes to 11.0.100

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
index d2d59c319a1..7d2823606ed 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
@@ -19,6 +19,12 @@
 * Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408)))
 * Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440))
 * Fix completion inconsistently showing some obsolete members (fields and events) while hiding others (methods and properties). All obsolete members are now consistently hidden by default. ([Issue #13512](https://github.com/dotnet/fsharp/issues/13512), [PR #19506](https://github.com/dotnet/fsharp/pull/19506))
+* Fix TypeLoadException when creating delegate with voidptr parameter. (Issue [#11132](https://github.com/dotnet/fsharp/issues/11132), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Suppress tail calls when localloc (NativePtr.stackalloc) is used. (Issue [#13447](https://github.com/dotnet/fsharp/issues/13447), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Fix TypeLoadException in Release builds with inline constraints. (Issue [#14492](https://github.com/dotnet/fsharp/issues/14492), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Fix nativeptr in interfaces leads to TypeLoadException. (Issue [#14508](https://github.com/dotnet/fsharp/issues/14508), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Fix box instruction for literal upcasts. (Issue [#18319](https://github.com/dotnet/fsharp/issues/18319), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
+* Fix Decimal Literal causes InvalidProgramException in Debug builds. (Issue [#18956](https://github.com/dotnet/fsharp/issues/18956), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
 
 ### Added
 

From 0cb685add6781004af78beb9b90813e65eef7950 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <Tomas.Grosup@gmail.com>
Date: Fri, 6 Mar 2026 19:37:52 +0100
Subject: [PATCH 4/7] Re-trigger CI

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

From ec3197baa0601742b03225e99e5d68b288d4ffe6 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <Tomas.Grosup@gmail.com>
Date: Tue, 31 Mar 2026 11:25:20 +0200
Subject: [PATCH 5/7] Fix nativeptr rewriting for params in
 GetMethodSpecForMemberVal

Address PR review feedback from abonie:
- Extract shared slotSigRequiresNativePtrRewrite helper to detect
  nativeptr<'T> with concrete class type params in both params and
  return types
- GetMethodSpecForMemberVal now selectively rewrites param types
  (not just return types) when interface slot has nativeptr<ClassTypar>
- GenActualSlotsig refactored to use the shared helper, removing
  the containsNativePtrWithTypar wrapper
- Add 4 new runtime tests covering nativeptr in interface params

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 src/Compiler/CodeGen/IlxGen.fs                | 152 +++++++++---------
 .../CodeGenRegressions_Crashes.fs             | 100 +++++++++++-
 2 files changed, 175 insertions(+), 77 deletions(-)

diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs
index 55582b6aa8a..e3e3a36251a 100644
--- a/src/Compiler/CodeGen/IlxGen.fs
+++ b/src/Compiler/CodeGen/IlxGen.fs
@@ -1399,7 +1399,7 @@ let IsValRefIsDllImport g (vref: ValRef) =
     ValHasWellKnownAttribute g WellKnownValAttributes.DllImportAttribute vref.Deref
 
 /// Check if a type contains nativeptr with a type parameter from the given set.
-/// Used to detect interface implementations that need 'native int' return type.
+/// Used to detect interface implementations that need 'native int' instead of 'T*'.
 let hasNativePtrWithTypar (g: TcGlobals) (typars: Typar list) ty =
     let rec check ty =
         let ty = stripTyEqns g ty
@@ -1420,6 +1420,19 @@ let hasNativePtrWithTypar (g: TcGlobals) (typars: Typar list) ty =
 
     check ty
 
+/// Check if a slot signature requires nativeptr rewriting: the slot has nativeptr<'T>
+/// where 'T is a class type parameter and the interface type arguments are concrete.
+/// When true, method definitions must use 'native int' instead of 'T*' for the
+/// affected params/returns to match the interface slot's IL signature.
+let slotSigRequiresNativePtrRewrite (g: TcGlobals) ty (sctps: Typars) (sParams: SlotParam list list) (sRetTy: TType option) =
+    not sctps.IsEmpty
+    && (let interfaceTypeArgs = argsOfAppTy g ty
+        (freeInTypes CollectTypars interfaceTypeArgs).FreeTypars.IsEmpty)
+    && (sParams
+        |> List.concat
+        |> List.exists (fun (TSlotParam(_, paramTy, _, _, _, _)) -> hasNativePtrWithTypar g sctps paramTy)
+        || sRetTy |> Option.exists (hasNativePtrWithTypar g sctps))
+
 /// Determine how a top level value is represented, when it is being represented
 /// as a method.
 let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) =
@@ -1445,42 +1458,43 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) =
     let ctps, mtps = List.splitAt numParentTypars tps
     let isCompiledAsInstance = ValRefIsCompiledAsInstanceMember g vref
 
+    // When implementing an interface slot with nativeptr<'T> where 'T is an interface
+    // type parameter and the interface type args are concrete, the interface method
+    // uses 'native int'. The method def must also use 'native int' to match.
+    // This applies to both return types and parameter types.
+    let nativePtrSlotRewriteInfo =
+        if
+            not (isCtor || cctor)
+            && memberInfo.MemberFlags.IsOverrideOrExplicitImpl
+        then
+            memberInfo.ImplementedSlotSigs
+            |> List.tryPick (fun (TSlotSig(_, ty, sctps, _, sParams, sRetTy)) ->
+                if slotSigRequiresNativePtrRewrite g ty sctps sParams sRetTy then
+                    let slotEnv = TypeReprEnv.Empty.ForTypars sctps
+                    let slotParamTys = sParams |> List.concat |> List.map (fun p -> p.Type)
+                    Some(slotEnv, sctps, slotParamTys, sRetTy)
+                else
+                    None)
+        else
+            None
+
     let ilActualRetTy =
         let ilRetTy = GenReturnType cenv m tyenvUnderTypars returnTy
 
         if isCtor || cctor then
             ILType.Void
-        // When implementing an interface slot with nativeptr<'T> where 'T is an interface
-        // type parameter and the interface type args are concrete, the interface method
-        // returns 'native int'. The method def must also return 'native int' to match.
-        elif memberInfo.MemberFlags.IsOverrideOrExplicitImpl then
-            match
-                memberInfo.ImplementedSlotSigs
-                |> List.tryPick (fun (TSlotSig(_, ty, sctps, _, _, sRetTy)) ->
-                    let interfaceTypeArgs = argsOfAppTy g ty
-
-                    if
-                        not sctps.IsEmpty
-                        && (freeInTypes CollectTypars interfaceTypeArgs).FreeTypars.IsEmpty
-                        && sRetTy |> Option.exists (hasNativePtrWithTypar g sctps)
-                    then
-                        Some(sctps, sRetTy)
-                    else
-                        None)
-            with
-            | Some(sctps, sRetTy) ->
-                // Generate return type using the slot's type params so nativeptr<'T> → native int
-                let slotEnv = TypeReprEnv.Empty.ForTypars sctps
-                GenReturnType cenv m slotEnv sRetTy
-            | None -> ilRetTy
         else
-            ilRetTy
+            match nativePtrSlotRewriteInfo with
+            | Some(slotEnv, sctps, _, sRetTy) when sRetTy |> Option.exists (hasNativePtrWithTypar g sctps) ->
+                GenReturnType cenv m slotEnv sRetTy
+            | _ -> ilRetTy
 
     let ilTy =
         GenType cenv m tyenvUnderTypars (mkWoNullAppTy parentTcref (List.map mkTyparTy ctps))
 
     let nm = vref.CompiledName g.CompilerGlobalState
 
+    // Instance methods: validate 'this' type and remove it from the arg list
     if isCompiledAsInstance || isCtor then
         // Find the 'this' argument type if any
         let thisTy, flatArgInfos =
@@ -1519,53 +1533,52 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) =
                         )
                     ))
 
-        let methodArgTys, paramInfos = List.unzip flatArgInfos
-
-        let isSlotSig =
-            memberInfo.MemberFlags.IsDispatchSlot
-            || memberInfo.MemberFlags.IsOverrideOrExplicitImpl
-
-        let ilMethodArgTys = GenParamTypes cenv m tyenvUnderTypars isSlotSig methodArgTys
-        let ilMethodInst = GenTypeArgs cenv m tyenvUnderTypars (List.map mkTyparTy mtps)
+        flatArgInfos
+    else
+        flatArgInfos
+    // Common: generate param types, method spec, witness spec
+    |> fun flatArgInfos ->
 
-        let mspec =
-            mkILInstanceMethSpecInTy (ilTy, nm, ilMethodArgTys, ilActualRetTy, ilMethodInst)
+    let methodArgTys, paramInfos = List.unzip flatArgInfos
 
-        let mspecW =
-            if not g.generateWitnesses || witnessInfos.IsEmpty then
-                mspec
-            else
-                let ilWitnessArgTys =
-                    GenTypes cenv m tyenvUnderTypars (GenWitnessTys g witnessInfos)
+    let isSlotSig =
+        memberInfo.MemberFlags.IsDispatchSlot
+        || memberInfo.MemberFlags.IsOverrideOrExplicitImpl
 
-                let nmW = ExtraWitnessMethodName nm
-                mkILInstanceMethSpecInTy (ilTy, nmW, ilWitnessArgTys @ ilMethodArgTys, ilActualRetTy, ilMethodInst)
+    let ilMethodArgTys =
+        let ilArgTys = GenParamTypes cenv m tyenvUnderTypars isSlotSig methodArgTys
 
-        mspec, mspecW, ctps, mtps, curriedArgInfos, paramInfos, retInfo, witnessInfos, methodArgTys, returnTy
-    else
-        let methodArgTys, paramInfos = List.unzip flatArgInfos
+        match nativePtrSlotRewriteInfo with
+        | Some(slotEnv, sctps, slotParamTys, _) when slotParamTys.Length = ilArgTys.Length ->
+            (ilArgTys, slotParamTys)
+            ||> List.map2 (fun ilArgTy slotParamTy ->
+                if hasNativePtrWithTypar g sctps slotParamTy then
+                    GenParamType cenv m slotEnv true slotParamTy
+                else
+                    ilArgTy)
+        | _ -> ilArgTys
 
-        let isSlotSig =
-            memberInfo.MemberFlags.IsDispatchSlot
-            || memberInfo.MemberFlags.IsOverrideOrExplicitImpl
+    let ilMethodInst = GenTypeArgs cenv m tyenvUnderTypars (List.map mkTyparTy mtps)
 
-        let ilMethodArgTys = GenParamTypes cenv m tyenvUnderTypars isSlotSig methodArgTys
-        let ilMethodInst = GenTypeArgs cenv m tyenvUnderTypars (List.map mkTyparTy mtps)
+    let mkMethSpec =
+        if isCompiledAsInstance || isCtor then
+            mkILInstanceMethSpecInTy
+        else
+            mkILStaticMethSpecInTy
 
-        let mspec =
-            mkILStaticMethSpecInTy (ilTy, nm, ilMethodArgTys, ilActualRetTy, ilMethodInst)
+    let mspec = mkMethSpec (ilTy, nm, ilMethodArgTys, ilActualRetTy, ilMethodInst)
 
-        let mspecW =
-            if not g.generateWitnesses || witnessInfos.IsEmpty then
-                mspec
-            else
-                let ilWitnessArgTys =
-                    GenTypes cenv m tyenvUnderTypars (GenWitnessTys g witnessInfos)
+    let mspecW =
+        if not g.generateWitnesses || witnessInfos.IsEmpty then
+            mspec
+        else
+            let ilWitnessArgTys =
+                GenTypes cenv m tyenvUnderTypars (GenWitnessTys g witnessInfos)
 
-                let nmW = ExtraWitnessMethodName nm
-                mkILStaticMethSpecInTy (ilTy, nmW, ilWitnessArgTys @ ilMethodArgTys, ilActualRetTy, ilMethodInst)
+            let nmW = ExtraWitnessMethodName nm
+            mkMethSpec (ilTy, nmW, ilWitnessArgTys @ ilMethodArgTys, ilActualRetTy, ilMethodInst)
 
-        mspec, mspecW, ctps, mtps, curriedArgInfos, paramInfos, retInfo, witnessInfos, methodArgTys, returnTy
+    mspec, mspecW, ctps, mtps, curriedArgInfos, paramInfos, retInfo, witnessInfos, methodArgTys, returnTy
 
 /// Determine how a top-level value is represented, when representing as a field, by computing an ILFieldSpec
 let ComputeFieldSpecForVal
@@ -5975,8 +5988,6 @@ and GenFormalReturnType m cenv eenvFormal returnTy : ILReturn =
 and instSlotParam inst (TSlotParam(nm, ty, inFlag, fl2, fl3, attrs)) =
     TSlotParam(nm, instType inst ty, inFlag, fl2, fl3, attrs)
 
-and containsNativePtrWithTypar (g: TcGlobals) (typars: Typar list) ty = hasNativePtrWithTypar g typars ty
-
 and GenActualSlotsig
     m
     cenv
@@ -5988,20 +5999,11 @@ and GenActualSlotsig
     let g = cenv.g
     let ilSlotParams = List.concat ilSlotParams
 
-    let interfaceTypeArgs = argsOfAppTy g ty
-
     let instForSlotSig =
-        mkTyparInst (ctps @ mtps) (interfaceTypeArgs @ generalizeTypars methTyparsOfOverridingMethod)
-
-    let interfaceTypeArgsAreConcrete =
-        not ctps.IsEmpty
-        && (freeInTypes CollectTypars interfaceTypeArgs).FreeTypars.IsEmpty
+        mkTyparInst (ctps @ mtps) (argsOfAppTy g ty @ generalizeTypars methTyparsOfOverridingMethod)
 
     let slotHasNativePtrWithCtps =
-        interfaceTypeArgsAreConcrete
-        && (ilSlotParams
-            |> List.exists (fun (TSlotParam(_, ty, _, _, _, _)) -> containsNativePtrWithTypar g ctps ty)
-            || ilSlotRetTy |> Option.exists (containsNativePtrWithTypar g ctps))
+        slotSigRequiresNativePtrRewrite g ty ctps [ilSlotParams] ilSlotRetTy
 
     let eenvForSlotGen =
         if slotHasNativePtrWithCtps then
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
index 7dd573b9b9d..4a91986a73c 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
@@ -170,9 +170,105 @@ printfn "Broken type loaded and Pointer accessed: %A" p
         |> shouldSucceed
         |> ignore
 
-    // https://github.com/dotnet/fsharp/issues/14492
+    // https://github.com/dotnet/fsharp/issues/14508
+    [<Fact>]
+    let ``Issue_14508_NativeptrInInterfaceParams`` () =
+        let source = """
+open Microsoft.FSharp.NativeInterop
+
+type IBar<'T when 'T : unmanaged> =
+    abstract member DoSomething : nativeptr<'T> -> unit
+
+type BarImpl() =
+    interface IBar<int> with
+        member _.DoSomething(_p: nativeptr<int>) = ()
+
+let b = BarImpl()
+(b :> IBar<int>).DoSomething(Unchecked.defaultof<_>)
+printfn "BarImpl loaded and DoSomething called"
+"""
+        FSharp source
+        |> asExe
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14508
     [<Fact>]
-    let ``Issue_14492_ReleaseConfigError`` () =
+    let ``Issue_14508_NativeptrInInterfaceParamsAndReturn`` () =
+        let source = """
+open Microsoft.FSharp.NativeInterop
+
+type ITransform<'T when 'T : unmanaged> =
+    abstract member Transform : nativeptr<'T> -> nativeptr<'T>
+
+type TransformImpl() =
+    interface ITransform<int> with
+        member _.Transform(p: nativeptr<int>) : nativeptr<int> = p
+
+let t = TransformImpl()
+let result = (t :> ITransform<int>).Transform(Unchecked.defaultof<_>)
+printfn "TransformImpl loaded, result: %A" result
+"""
+        FSharp source
+        |> asExe
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14508
+    [<Fact>]
+    let ``Issue_14508_NativeptrInInterfaceParamWithOtherParams`` () =
+        let source = """
+open Microsoft.FSharp.NativeInterop
+
+type IProcess<'T when 'T : unmanaged> =
+    abstract member Process : nativeptr<'T> * int -> unit
+
+type ProcessImpl() =
+    interface IProcess<int> with
+        member _.Process((_p: nativeptr<int>), (_len: int)) = ()
+
+let p = ProcessImpl()
+(p :> IProcess<int>).Process(Unchecked.defaultof<_>, 42)
+printfn "ProcessImpl loaded and Process called"
+"""
+        FSharp source
+        |> asExe
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14508
+    [<Fact>]
+    let ``Issue_14508_NativeptrMultipleParams`` () =
+        let source = """
+open Microsoft.FSharp.NativeInterop
+
+type ICopy<'T when 'T : unmanaged> =
+    abstract member Copy : nativeptr<'T> * nativeptr<'T> * int -> unit
+
+type CopyImpl() =
+    interface ICopy<int> with
+        member _.Copy((_src: nativeptr<int>), (_dst: nativeptr<int>), (_len: int)) = ()
+
+let c = CopyImpl()
+(c :> ICopy<int>).Copy(Unchecked.defaultof<_>, Unchecked.defaultof<_>, 10)
+printfn "CopyImpl loaded and Copy called"
+"""
+        FSharp source
+        |> asExe
+        |> compile
+        |> shouldSucceed
+        |> run
+        |> shouldSucceed
+        |> ignore
         let source = """
 module Test
 

From 8a022ea8ebb279aceecb40f0b469399b580202d3 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Fri, 10 Apr 2026 12:06:50 +0200
Subject: [PATCH 6/7] Fix fantomas formatting in IlxGen.fs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 src/Compiler/CodeGen/IlxGen.fs | 69 ++++++++++++++++------------------
 1 file changed, 33 insertions(+), 36 deletions(-)

diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs
index e3e3a36251a..1ee335581b8 100644
--- a/src/Compiler/CodeGen/IlxGen.fs
+++ b/src/Compiler/CodeGen/IlxGen.fs
@@ -1463,10 +1463,7 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) =
     // uses 'native int'. The method def must also use 'native int' to match.
     // This applies to both return types and parameter types.
     let nativePtrSlotRewriteInfo =
-        if
-            not (isCtor || cctor)
-            && memberInfo.MemberFlags.IsOverrideOrExplicitImpl
-        then
+        if not (isCtor || cctor) && memberInfo.MemberFlags.IsOverrideOrExplicitImpl then
             memberInfo.ImplementedSlotSigs
             |> List.tryPick (fun (TSlotSig(_, ty, sctps, _, sParams, sRetTy)) ->
                 if slotSigRequiresNativePtrRewrite g ty sctps sParams sRetTy then
@@ -1539,46 +1536,46 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) =
     // Common: generate param types, method spec, witness spec
     |> fun flatArgInfos ->
 
-    let methodArgTys, paramInfos = List.unzip flatArgInfos
+        let methodArgTys, paramInfos = List.unzip flatArgInfos
 
-    let isSlotSig =
-        memberInfo.MemberFlags.IsDispatchSlot
-        || memberInfo.MemberFlags.IsOverrideOrExplicitImpl
+        let isSlotSig =
+            memberInfo.MemberFlags.IsDispatchSlot
+            || memberInfo.MemberFlags.IsOverrideOrExplicitImpl
 
-    let ilMethodArgTys =
-        let ilArgTys = GenParamTypes cenv m tyenvUnderTypars isSlotSig methodArgTys
+        let ilMethodArgTys =
+            let ilArgTys = GenParamTypes cenv m tyenvUnderTypars isSlotSig methodArgTys
 
-        match nativePtrSlotRewriteInfo with
-        | Some(slotEnv, sctps, slotParamTys, _) when slotParamTys.Length = ilArgTys.Length ->
-            (ilArgTys, slotParamTys)
-            ||> List.map2 (fun ilArgTy slotParamTy ->
-                if hasNativePtrWithTypar g sctps slotParamTy then
-                    GenParamType cenv m slotEnv true slotParamTy
-                else
-                    ilArgTy)
-        | _ -> ilArgTys
+            match nativePtrSlotRewriteInfo with
+            | Some(slotEnv, sctps, slotParamTys, _) when slotParamTys.Length = ilArgTys.Length ->
+                (ilArgTys, slotParamTys)
+                ||> List.map2 (fun ilArgTy slotParamTy ->
+                    if hasNativePtrWithTypar g sctps slotParamTy then
+                        GenParamType cenv m slotEnv true slotParamTy
+                    else
+                        ilArgTy)
+            | _ -> ilArgTys
 
-    let ilMethodInst = GenTypeArgs cenv m tyenvUnderTypars (List.map mkTyparTy mtps)
+        let ilMethodInst = GenTypeArgs cenv m tyenvUnderTypars (List.map mkTyparTy mtps)
 
-    let mkMethSpec =
-        if isCompiledAsInstance || isCtor then
-            mkILInstanceMethSpecInTy
-        else
-            mkILStaticMethSpecInTy
+        let mkMethSpec =
+            if isCompiledAsInstance || isCtor then
+                mkILInstanceMethSpecInTy
+            else
+                mkILStaticMethSpecInTy
 
-    let mspec = mkMethSpec (ilTy, nm, ilMethodArgTys, ilActualRetTy, ilMethodInst)
+        let mspec = mkMethSpec (ilTy, nm, ilMethodArgTys, ilActualRetTy, ilMethodInst)
 
-    let mspecW =
-        if not g.generateWitnesses || witnessInfos.IsEmpty then
-            mspec
-        else
-            let ilWitnessArgTys =
-                GenTypes cenv m tyenvUnderTypars (GenWitnessTys g witnessInfos)
+        let mspecW =
+            if not g.generateWitnesses || witnessInfos.IsEmpty then
+                mspec
+            else
+                let ilWitnessArgTys =
+                    GenTypes cenv m tyenvUnderTypars (GenWitnessTys g witnessInfos)
 
-            let nmW = ExtraWitnessMethodName nm
-            mkMethSpec (ilTy, nmW, ilWitnessArgTys @ ilMethodArgTys, ilActualRetTy, ilMethodInst)
+                let nmW = ExtraWitnessMethodName nm
+                mkMethSpec (ilTy, nmW, ilWitnessArgTys @ ilMethodArgTys, ilActualRetTy, ilMethodInst)
 
-    mspec, mspecW, ctps, mtps, curriedArgInfos, paramInfos, retInfo, witnessInfos, methodArgTys, returnTy
+        mspec, mspecW, ctps, mtps, curriedArgInfos, paramInfos, retInfo, witnessInfos, methodArgTys, returnTy
 
 /// Determine how a top-level value is represented, when representing as a field, by computing an ILFieldSpec
 let ComputeFieldSpecForVal
@@ -6003,7 +6000,7 @@ and GenActualSlotsig
         mkTyparInst (ctps @ mtps) (argsOfAppTy g ty @ generalizeTypars methTyparsOfOverridingMethod)
 
     let slotHasNativePtrWithCtps =
-        slotSigRequiresNativePtrRewrite g ty ctps [ilSlotParams] ilSlotRetTy
+        slotSigRequiresNativePtrRewrite g ty ctps [ ilSlotParams ] ilSlotRetTy
 
     let eenvForSlotGen =
         if slotHasNativePtrWithCtps then

From 0dcec45e67a1d0f2348d53fa847461c8c373d7e5 Mon Sep 17 00:00:00 2001
From: Tomas Grosup <tomasgrosup@microsoft.com>
Date: Mon, 13 Apr 2026 13:25:50 +0200
Subject: [PATCH 7/7] Extract Issue_14492 same-assembly test into its own test
 function

The test for issue #14492 (same-assembly inline with constrained generics)
was embedded inside Issue_14508_NativeptrMultipleParams via variable shadowing.
Extract it into Issue_14492_SameAssemblyInline for proper test identification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 .../CodeGenRegressions/CodeGenRegressions_Crashes.fs         | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
index 4a91986a73c..e49c3a3b211 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs
@@ -269,6 +269,11 @@ printfn "CopyImpl loaded and Copy called"
         |> run
         |> shouldSucceed
         |> ignore
+
+    // https://github.com/dotnet/fsharp/issues/14492
+    // Same-assembly variant: constrained generics inlined into closures trigger Specialize
+    [<Fact>]
+    let ``Issue_14492_SameAssemblyInline`` () =
         let source = """
 module Test
 
