diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md index 17bcd329342..440b83d1125 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md @@ -8,6 +8,8 @@ ### Fixed +* Finish type inference for traits resolved during code generation: type parameters of the chosen method that the trait does not determine are inferred from their member constraints and defaulted as for a direct call. This fixes internal error FS0073 for methods such as `static member M<'X>(x: int) : int`, and programs that compiled into a `NotSupportedException` ("Dynamic invocation of ... is not supported") are now rejected at compile time. ([Issue #8690](https://github.com/dotnet/fsharp/issues/8690), [Issue #7384](https://github.com/dotnet/fsharp/issues/7384), [PR #20821](https://github.com/dotnet/fsharp/pull/20821)) +* Fix struct state machines in generic code dropping type parameters that only their data type mentions, which code generation had replaced with `obj`. ([PR #20821](https://github.com/dotnet/fsharp/pull/20821)) * Fix `--pathmap` not producing path-independent binaries for portable PDBs: the debug directory was reserved from the original PDB path rather than the mapped one, so output retained padding proportional to the original path's length. ([PR #20509](https://github.com/dotnet/fsharp/pull/20509)) * Capture diagnostics emitted when transparent project checking finishes a project, including FS0247 for namespace-module name collisions. ([Issue #6036](https://github.com/dotnet/fsharp/issues/6036), [PR #20706](https://github.com/dotnet/fsharp/pull/20706)) * Fix false IDE type-mismatch diagnostics for interpolated strings with multiple formatted holes. ([Issue #20752](https://github.com/dotnet/fsharp/issues/20752), [PR #20775](https://github.com/dotnet/fsharp/pull/20775)) @@ -36,6 +38,7 @@ ### Changed +* Revert the IlxGen fallback from [PR #19710](https://github.com/dotnet/fsharp/pull/19710) that silently defaulted type variables missing from the IL type environment. Code generation reports FS0073 again instead of emitting wrong IL, which had masked [Issue #20812](https://github.com/dotnet/fsharp/issues/20812). ([PR #20821](https://github.com/dotnet/fsharp/pull/20821)) * Debugger: emit a call for a self tail call in non-optimized builds ([PR #20782](https://github.com/dotnet/fsharp/pull/20782)) * Remove the unused internal `--test:NoErrorText` switch and its argument-only diagnostic formatting. ([PR #20742](https://github.com/dotnet/fsharp/pull/20742)) * Assign seven previously preview-only features to [F# 11.2](https://fsharp.github.io/fsharp-compiler-docs/release-notes/Language.html#11.2), now the default language version. `default`, `latest`, and `latestmajor` select F# 11.2; explicit `11` and `11.0` retain F# 11.0 behavior. Preview SDK projects use the same compiler default unless `LangVersion` is explicitly set. `FromEndSlicing` remains preview-only. ([PR #20617](https://github.com/dotnet/fsharp/pull/20617)) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index df9037a8d47..02152edb446 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -6102,13 +6102,7 @@ let ApplyDefaults (cenv: cenv) g denvAtEnd m moduleContents extraAttribs = CanonicalizePartialInferenceProblem cenv.css denvAtEnd m unsolved - // The priority order comes from the order of declaration of the defaults in FSharp.Core. - for priority = 10 downto 0 do - unsolved |> List.iter (fun tp -> - if not tp.IsSolved then - // Apply the first default. If we're defaulting one type variable to another then - // the defaults will be propagated to the new type variable. - ApplyTyparDefaultAtPriority denvAtEnd cenv.css priority tp) + ApplyTyparDefaultsByPriority denvAtEnd cenv.css unsolved |> RaiseOperationResult // OK, now apply defaults for any unsolved TyparStaticReq.HeadType or typars with SRTP (MayResolveMember) constraints // Note: We also check for MayResolveMember constraints because some SRTP typars may not have StaticReq set diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 062d4ac376a..ad77f5dd84a 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -4406,8 +4406,8 @@ let SolveTypeAsError denv css m ty = let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv SolveTypeEqualsTypeKeepAbbrevs csenv 0 m NoTrace ty ty2 |> ignore -let ApplyTyparDefaultAtPriority denv css priority (tp: Typar) = - tp.Constraints |> List.iter (fun tpc -> +let private ApplyTyparDefaultAtPriority denv css priority (tp: Typar) = + tp.Constraints |> IterateD (fun tpc -> match tpc with | TyparConstraint.DefaultsTo(priority2, ty2, m) when priority2 = priority -> let ty1 = mkTyparTy tp @@ -4419,8 +4419,26 @@ let ApplyTyparDefaultAtPriority denv css priority (tp: Typar) = (fun res -> SolveTypeAsError denv css m ty1 ErrorD(ErrorFromApplyingDefault(css.g, denv, tp, ty2, res, m))) - |> RaiseOperationResult - | _ -> ()) + else + CompleteD + | _ -> CompleteD) + +/// Apply the 'default' constraints of the type variables, highest priority first. A default's priority is its +/// position from the end of its 'when' clause (see TcTyparConstraints), so FSharp.Core's operators try e.g. +/// 'default ^T2: ^T3' before 'default ^T1: int'. Defaulting one type variable to another propagates the defaults +/// to the other. +let ApplyTyparDefaultsByPriority denv css (tps: Typar list) = + [ 10..-1..0 ] |> IterateD (fun priority -> tps |> IterateD (ApplyTyparDefaultAtPriority denv css priority)) + +/// Choose a solution for a type parameter subject to its constraints and apply it by using a constraint. +let SolveTyparWithChosenSolution css denv tp = + let g = css.g + let amap = css.amap + let max, m = ChooseTyparSolutionAndRange g amap tp + let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv + PostponeOnFailedMemberConstraintResolution csenv NoTrace + (fun csenv -> SolveTyparEqualsType csenv 0 m NoTrace (mkTyparTy tp) max) + (fun err -> ErrorD(ErrorFromApplyingDefault(g, denv, tp, max, err, m))) let CreateCodegenState tcVal g amap = ConstraintSolverState.New(g, amap, InfoReader(g, amap), tcVal) @@ -4439,6 +4457,66 @@ let SolveMemberConstraintForCodegen tcVal g amap m (traitInfo: TraitConstraintIn else SolveMemberConstraint csenv true PermitWeakResolution.Yes 0 m NoTrace traitInfo +/// Overload resolution freshens the chosen method's type parameters, and a codegen solve leaves those the trait +/// does not determine to whatever processes the code generated from the solution: inlining the method and solving +/// its nested traits. Once that is done, any still unsolved, e.g. 'X of 'static member M<'X>(x: int) : int', are +/// finished the way ApplyDefaults and SolveInternalUnknowns finish a file: their member constraints are solved, +/// then ApplyTyparDefaultsByPriority and SolveTyparWithChosenSolution apply. This holds in generic code too: in an +/// inline template the processed solution is stored and reused by every instantiation, so nothing later determines +/// them, just as type checking defaults a direct call's undetermined type parameters there. +let FinishCodegenTraitSolution tcVal g amap m (traitInfo: TraitConstraintInfo) = + let openTypars minst = + (freeInTypes CollectTypars minst).FreeTypars + |> Zset.elements + |> List.filter (fun tp -> tp.Rigidity <> TyparRigidity.Rigid) + + // Nearly every solution has nothing open, so check that before creating a solver state. + match traitInfo.Solution with + | Some(FSMethSln(minst = minst)) + | Some(ILMethSln(minst = minst)) when not (List.isEmpty (openTypars minst)) -> + let css = CreateCodegenState tcVal g amap + let denv = DisplayEnv.Empty g + let unsolved () = openTypars minst + + trackErrors { + // Unlike CanonicalizePartialInferenceProblem, which walks the constraints recorded in a solver state, + // canonicalize from the type parameters themselves: FreshenMethInfo copied their constraints onto them. + do! + IgnoreFailedMemberConstraintResolution + (fun () -> + let csenv = + { MakeConstraintSolverEnv ContextInfo.NoContext css m denv with + ErrorOnFailedMemberConstraintResolution = true + } + + RepeatWhileD 0 (fun ndeep -> + unsolved () + |> AtLeastOneD (fun tp -> + tp.Constraints + |> AtLeastOneD (function + | TyparConstraint.MayResolveMember(traitInfo2, m2) when traitInfo2.Solution.IsNone -> + SolveMemberConstraint csenv true PermitWeakResolution.Yes ndeep m2 NoTrace traitInfo2 + | _ -> ResultD false)))) + ErrorD + + do! ApplyTyparDefaultsByPriority denv css (unsolved ()) + + do! + unsolved () + |> IterateD (fun tp -> if tp.IsSolved then CompleteD else SolveTyparWithChosenSolution css denv tp) + + // Nothing later resolves what solving postponed, so run those checks now, as the end of checking does. + do! + [ yield! css.GetPostInferenceChecksPreDefaults(); yield! css.GetPostInferenceChecksFinal() ] + |> IterateD (fun check -> + try + check () + CompleteD + with RecoverableException err -> + ErrorD err) + } + | _ -> CompleteD + /// Determine if a codegen witness for a trait will require witness args to be available, e.g. in generic code let CodegenWitnessExprForTraitConstraintWillRequireWitnessArgs tcVal g amap m (traitInfo:TraitConstraintInfo) = let traitInfo = GetTraitConstraintForCodegen g traitInfo @@ -4478,6 +4556,7 @@ let CodegenWitnessesForTyparInstWith tcVal g amap m typars tyargs f = traitInfo.Solution <- TryGetSelectedTraitSolution g (instTrait actualInst traitInfo) | _ -> () let! _res = SolveTyparsEqualTypesAux csenv 0 m NoTrace tinst tyargs + do! traitInfos |> IterateD (FinishCodegenTraitSolution tcVal g amap m) return [ for traitInfo in traitInfos -> f traitInfo (GenWitnessExprLambda amap g m traitInfo) ] } @@ -4489,6 +4568,7 @@ let CodegenWitnessArgForTraitConstraint tcVal g amap m traitInfo = let traitInfo = GetTraitConstraintForCodegen g traitInfo trackErrors { let! _res = SolveMemberConstraintForCodegen tcVal g amap m traitInfo + do! FinishCodegenTraitSolution tcVal g amap m traitInfo return GenWitnessExprLambda amap g m traitInfo } @@ -4496,14 +4576,7 @@ let CodegenWitnessArgForTraitConstraint tcVal g amap m traitInfo = /// for an interior type variable. This chooses a solution for a type parameter subject /// to its constraints and applies that solution by using a constraint. let ChooseTyparSolutionAndSolve css denv tp = - let g = css.g - let amap = css.amap - let max, m = ChooseTyparSolutionAndRange g amap tp - let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv - PostponeOnFailedMemberConstraintResolution csenv NoTrace - (fun csenv -> SolveTyparEqualsType csenv 0 m NoTrace (mkTyparTy tp) max) - (fun err -> ErrorD(ErrorFromApplyingDefault(g, denv, tp, max, err, m))) - |> RaiseOperationResult + SolveTyparWithChosenSolution css denv tp |> RaiseOperationResult let CheckDeclaredTypars denv css m typars1 typars2 = let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m denv diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi index 5dbb4ca9fa0..71f0c37cfb5 100644 --- a/src/Compiler/Checking/ConstraintSolver.fsi +++ b/src/Compiler/Checking/ConstraintSolver.fsi @@ -368,12 +368,19 @@ val AddCxTyparDefaultsTo: DisplayEnv -> ConstraintSolverState -> range -> Contex val SolveTypeAsError: DisplayEnv -> ConstraintSolverState -> range -> TType -> unit -val ApplyTyparDefaultAtPriority: DisplayEnv -> ConstraintSolverState -> priority: int -> Typar -> unit +/// Apply the 'default' constraints of the type variables, highest priority first. +val ApplyTyparDefaultsByPriority: DisplayEnv -> ConstraintSolverState -> Typar list -> OperationResult /// Generate a witness expression if none is otherwise available, e.g. in legacy non-witness-passing code val CodegenWitnessExprForTraitConstraint: TcValF -> TcGlobals -> ImportMap -> range -> TraitConstraintInfo -> Expr list -> OperationResult +/// Finish the type parameters a codegen trait solution left open, once the code generated from the solution has +/// been processed: inlined and its nested traits solved. Callers of CodegenWitnessExprForTraitConstraint +/// call it with the same trait; the witness lambda functions call it themselves. +val FinishCodegenTraitSolution: + TcValF -> TcGlobals -> ImportMap -> range -> TraitConstraintInfo -> OperationResult + /// Determine if a codegen witness for a trait will require witness args to be available, e.g. in generic code val CodegenWitnessExprForTraitConstraintWillRequireWitnessArgs: TcValF -> TcGlobals -> ImportMap -> range -> TraitConstraintInfo -> OperationResult diff --git a/src/Compiler/Checking/QuotationTranslator.fs b/src/Compiler/Checking/QuotationTranslator.fs index 8c801a7b213..5420108e5f9 100644 --- a/src/Compiler/Checking/QuotationTranslator.fs +++ b/src/Compiler/Checking/QuotationTranslator.fs @@ -755,6 +755,7 @@ and private ConvExprCore cenv (env : QuotationTranslationEnv) (expr: Expr) : Exp | None -> wfail(Error(FSComp.SR.crefQuotationsCantCallTraitMembers(), m)) | Some expr -> + ConstraintSolver.FinishCodegenTraitSolution cenv.tcVal g cenv.amap m traitInfo |> CommitOperationResult ConvExpr cenv env expr | _ -> diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 2b28167974b..ce8bc6debd2 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -553,9 +553,6 @@ type TypeReprEnv // Random value for post-hoc diagnostic analysis on generated tree * uint16 666 - /// Check if a type parameter is in the environment - member _.ContainsKey(tp: Typar) = reprs.ContainsKey(tp.Stamp) - /// Add an additional type parameter to the environment. If the parameter is a units-of-measure parameter /// then it is ignored, since it doesn't correspond to a .NET type parameter. member tyenv.AddOne(tp: Typar) = @@ -736,15 +733,7 @@ and GenTypeAux cenv m (tyenv: TypeReprEnv) voidOK ptrsOK ty = else EraseClosures.mkILTyFuncTy cenv.ilxPubCloEnv - | TType_var(tp, _) -> - if tyenv.ContainsKey tp then - mkILTyvarTy tyenv[tp, m] - else - // Unsolved type variable not in the TypeReprEnv — can arise for inline SRTP - // functions where constraint resolution leaves phantom typars unsolved. - // Default the typar and generate the type for the default to avoid an ICE. - let defaultTy = TypeRelations.ChooseTyparSolution g cenv.amap tp - GenTypeAux cenv m tyenv voidOK ptrsOK defaultTy + | TType_var(tp, _) -> mkILTyvarTy tyenv[tp, m] | TType_measure _ -> g.ilg.typ_Int32 @@ -6137,6 +6126,10 @@ and GenTraitCall (cenv: cenv) cgbuf eenv (traitInfo: TraitConstraintInfo, argExp GenExpr cenv cgbuf eenv replacementExpr sequel | Some expr -> let expr = cenv.optimizeDuringCodeGen false expr + + ConstraintSolver.FinishCodegenTraitSolution cenv.tcVal g cenv.amap m traitInfo + |> CommitOperationResult + GenExpr cenv cgbuf eenv expr sequel //-------------------------------------------------------------------------- @@ -7765,6 +7758,14 @@ and GetIlxClosureFreeVars cenv m (thisVars: ValRef list) boxity eenv takenNames | Local(_, _, Some(moreFtyvs, _)) -> unionFreeTyvars ftyvs moreFtyvs | _ -> ftyvs) + // A struct state machine's type can mention type variables its MoveNext body does not, e.g. through its Data type + let cloFreeTyvars = + match boxity with + | ILBoxity.AsValue -> + (cloFreeTyvars, thisVars) + ||> List.fold (fun ftyvs v -> unionFreeTyvars ftyvs (freeInType CollectTyparsNoCaching v.Type)) + | ILBoxity.AsObject -> cloFreeTyvars + let cloFreeTyvars = cloFreeTyvars.FreeTypars |> Zset.elements // Work out if the closure captures any witnesses. diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 2506b209eaf..aab5627654b 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -3502,7 +3502,11 @@ and OptimizeTraitCall cenv env (traitInfo, args, m) = // Resolve the static overloading early (during the compulsory rewrite phase) so we can inline. match ConstraintSolver.CodegenWitnessExprForTraitConstraint cenv.TcVal g cenv.amap m traitInfoForResolution args with - | OkResult (_, Some expr) -> OptimizeExpr cenv env expr + | OkResult (_, Some expr) -> + let res = OptimizeExpr cenv env expr + // Inlining the solution and solving its nested traits can determine its open type parameters, so finish after. + ConstraintSolver.FinishCodegenTraitSolution cenv.TcVal g cenv.amap m traitInfoForResolution |> RaiseOperationResult + res // Resolution fails when optimizing generic code, ignore the failure | _ -> diff --git a/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs b/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs index ae5f1b2fbab..20dcea2ff00 100644 --- a/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs +++ b/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs @@ -348,6 +348,227 @@ let main _ = |> compileExeAndRun |> shouldSucceed + // Resolving the trait after inlining picks a method whose type parameter the trait does not determine. + // Without --optimize the call to M survives into IL, where the undefaulted parameter caused FS0073. + [] + [] + [] + let ``Trait solved by a static method with an undetermined type parameter`` (optimize: bool) = + FSharp + """ +type C = + static member M<'X>(x: int) : int = x + 1 + +let inline call (w: ^T) = (^T : (static member M : int -> int) 1) + +[] +let main _ = + if call Unchecked.defaultof <> 2 then failwith "call failed" + 0 + """ + |> withOptimization optimize + |> asExe + |> compileExeAndRun + |> shouldSucceed + + [] + [] + [] + let ``Trait solved by an instance method with an undetermined type parameter`` (optimize: bool) = + FSharp + """ +type C() = + member _.M<'X>(x: int) : int = x + 1 + +let inline call (w: ^T) = (^T : (member M : int -> int) (w, 1)) +let inline wrap (w: ^T) = call w + +[] +let main _ = + if wrap (C()) <> 2 then failwith "wrap failed" + 0 + """ + |> withOptimization optimize + |> asExe + |> compileExeAndRun + |> shouldSucceed + + // Inlining the method does not determine 'X either: only its body mentions it. + [] + [] + [] + let ``Trait solved by an inline method with an undetermined type parameter`` (optimize: bool) = + FSharp + """ +type C = + static member inline M<'X>(x: int) : int = + box Unchecked.defaultof<'X> |> ignore + x + 1 + +let inline call (w: ^T) = (^T : (static member M : int -> int) 1) + +[] +let main _ = + if call Unchecked.defaultof <> 2 then failwith "call failed" + 0 + """ + |> withOptimization optimize + |> asExe + |> compileExeAndRun + |> shouldSucceed + + // The undetermined type parameter carries a member constraint obj cannot satisfy. Called directly, M is + // rejected when 'X is defaulted; reached through a trait it compiled into a NotSupportedException throw. + [] + [] + [] + let ``Trait solved by a method whose undetermined type parameter has an unsatisfiable member constraint`` (optimize: bool) = + FSharp + """ +type C = + static member inline M< ^X when ^X : (static member Foo : unit -> int)> (x: int) : int = + x + (^X : (static member Foo : unit -> int) ()) + +let inline call (w: ^T) = (^T : (static member M : int -> int) 1) + +[] +let main _ = + printfn "%d" (call Unchecked.defaultof) + 0 + """ + |> withOptimization optimize + |> asExe + |> compile + |> shouldFail + |> withErrorCode 71 + + // Generic callers reuse the solution for every instantiation, so 'X is defaulted there too, as for a direct call. + [] + [] + [] + let ``Trait solved in generic code by a method with an undetermined type parameter`` (optimize: bool) = + FSharp + """ +type C = + static member inline M<'T, 'X>(x: 'T) = + x, typeof<'X> + +let inline call (w: ^W) (x: 'T) = + (^W : (static member M : 'T -> 'T * System.Type) x) + +let generic (x: 'T) = call Unchecked.defaultof x +let inline genericInline (x: 'T) = call Unchecked.defaultof x + +[] +let main _ = + if snd (generic 1) <> typeof || snd (generic "a") <> typeof then failwith "generic failed" + if snd (genericInline 1) <> typeof || snd (genericInline "a") <> typeof then failwith "genericInline failed" + 0 + """ + |> withOptimization optimize + |> asExe + |> compileExeAndRun + |> shouldSucceed + + // Generic code cannot satisfy a member constraint on 'X that involves 'T; called directly, M is rejected the same way. + [] + [] + [] + let ``Trait solved in generic code by a method whose undetermined type parameter has an unsatisfiable member constraint`` (optimize: bool) = + FSharp + """ +type C = + static member inline M<'T, ^X when ^X : (static member Foo : 'T -> int)>(x: 'T) = + (^X : (static member Foo : 'T -> int) x) + +let inline call (w: ^W) (x: 'T) = + (^W : (static member M : 'T -> int) x) + +let generic (x: 'T) = call Unchecked.defaultof x + +[] +let main _ = + printfn "%d" (generic 1) + 0 + """ + |> withOptimization optimize + |> asExe + |> compile + |> shouldFail + |> withErrorCode 71 + + // https://github.com/dotnet/fsharp/issues/8690 + [] + [] + [] + let ``Issue 8690 - unsatisfiable trait reached through overload resolution is a compile error`` (optimize: bool) = + FSharp + """ +type T = T with + static member inline Mtd (f, x: 'FT) = ((^FT or ^FU) : (static member Mtd : _*_ -> _) f, x) + static member inline Mtd (_, _:'t when 't: struct) = () + +let inline invoke x : 'FV = + let inline call (mthd : ^M, i1: ^I1, _output: ^R) = ((^M or ^R) : (static member Mtd : _*_ -> _) (), i1) + call (T, x, Unchecked.defaultof<'FV>) + +[] +let main _ = + invoke [|1|] + 0 + """ + |> withOptimization optimize + |> asExe + |> compile + |> shouldFail + |> withErrorCode 71 + + // https://github.com/dotnet/fsharp/issues/7384 + // 'mt is determined only by the member constraints the dead branch contributes, which the optimizer drops. + [] + [] + [] + let ``Issue 7384 - type inference does not depend on the runtime path`` (optimize: bool) = + FSharp + """ +let compiles = false + +type A<'t> = A of 't +type B<'t> = B of 't + +type Bind = Bind with + static member (>>=) (A t, f: 'T -> A<'U> ) = f t + static member (>>=) (B t, f: 'T -> B<'U> ) = f t + static member inline Invoke (source: 'MT) (binder: 'T -> 'MU) : 'MU = + let inline call (_mthd: 'M, input: 'I, _output: 'R, f) = ((^M or ^I or ^R) : (static member (>>=) : _*_ -> _) input, f) + call (Bind, source, Unchecked.defaultof<'MU>, binder) + +type Result = Result with + static member Return (_: A<'a>) = A + static member Return (_: B<'a>) = B + static member inline Invoke (x: 'T) : 'MT = + let inline call (_mthd: ^M, output: ^R) = ((^M or ^R) : (static member Return : _ -> _) output) + call (Result, Unchecked.defaultof<'MT>) x + +type T<'m> () = class end +type U<'m,'t> () = class end + +let inline createT (_: 'mit) = + let _ = if compiles then Bind.Invoke (Result.Invoke Unchecked.defaultof<'t> : 'mt) ((fun _ -> Result.Invoke (U<'mt,'t>()))) else Unchecked.defaultof<'mit> + T() : T<'mt> + +type T<'m> with static member inline Return (_) = fun (_:'t) -> U() |> Result.Invoke |> createT + +let (u: T>) = Result.Invoke () + +[] +let main _ = 0 + """ + |> withOptimization optimize + |> asExe + |> compileExeAndRun + |> shouldSucceed + // Regression for PR #19602 (RFC FS-1043): a non-inline binding with an unsatisfiable operator/SRTP // trait must fail at compile time (FS0041), not compile into a NotSupportedException stub that throws // at runtime (which also leaked at feature-off langversions). The deleted neg116 shape '(1.0 - t) * p' diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 14 - StateMachine with unresolved trait from composed inline function.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 14 - StateMachine with unresolved trait from composed inline function.bsl index 8c3aca87f16..880a773e6a1 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 14 - StateMachine with unresolved trait from composed inline function.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DebugInlineAsCall/SRTP 14 - StateMachine with unresolved trait from composed inline function.bsl @@ -47,7 +47,7 @@ Test::h IL_0008: callvirt Invoke IL_000d: stloc.0 IL_000e: ldloca.s 2 - IL_0010: initobj h@19-1 + IL_0010: initobj 0x1b000009 IL_0016: ldloca.s 2 IL_0018: stloc.3 IL_0019: ldc.i4.0 @@ -63,7 +63,7 @@ Test::h$W IL_0009: callvirt Invoke IL_000e: stloc.0 IL_000f: ldloca.s 2 - IL_0011: initobj h@19-3 + IL_0011: initobj 0x1b00000b IL_0017: ldloca.s 2 IL_0019: stloc.3 IL_001a: ldc.i4.0 @@ -91,14 +91,14 @@ S`1::Equals IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloc.0 - IL_0003: isinst 0x1b00000a + IL_0003: isinst 0x1b00000c IL_0008: ldnull IL_0009: cgt.un IL_000b: brfalse.s IL_001d IL_000d: ldarg.1 - IL_000e: unbox.any 0x1b00000a + IL_000e: unbox.any 0x1b00000c IL_0013: stloc.1 IL_0014: ldarg.0 IL_0015: ldloc.1 @@ -119,14 +119,14 @@ S`1::Equals IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloc.0 - IL_0003: isinst 0x1b00000a + IL_0003: isinst 0x1b00000c IL_0008: ldnull IL_0009: cgt.un IL_000b: brfalse.s IL_001c IL_000d: ldarg.1 - IL_000e: unbox.any 0x1b00000a + IL_000e: unbox.any 0x1b00000c IL_0013: stloc.1 IL_0014: ldarg.0 IL_0015: ldloc.1