From 0b150dfaf63203c558501b8adb9d49412324fa61 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 21 Jan 2026 16:18:30 +0100 Subject: [PATCH] Reject static abstract member invocations on F# interface types (fixes #19231) Extend the pattern match in ConstraintSolver.fs to also reject FSMeth when it represents a static abstract interface member being called directly on the interface type. The check handles FSMeth case alongside ILMeth: - Not a constrained call via type parameter - Not an instance member (i.e., static) - Is on an interface type - Is a dispatch slot member (abstract) Uses the same error message 3866 (chkStaticAbstractInterfaceMembers). --- .../.FSharp.Compiler.Service/11.0.0.md | 1 + src/Compiler/Checking/ConstraintSolver.fs | 5 ++- .../IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs | 43 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md index 63ec01c0985..b5826b6430c 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md @@ -15,6 +15,7 @@ * Fix insertion context for modules with multiline attributes. ([Issue #18671](https://github.com/dotnet/fsharp/issues/18671)) * Fix `--typecheck-only` for scripts stopping after processing `#load`-ed script ([PR #19048](https://github.com/dotnet/fsharp/pull/19048)) * Fix object expressions in struct types generating invalid IL with byref fields causing TypeLoadException at runtime. ([Issue #19068](https://github.com/dotnet/fsharp/issues/19068), [PR #19070](https://github.com/dotnet/fsharp/pull/19070)) +* Reject direct invocation of static abstract interface members on F#-defined interface types, which caused BadImageFormatException at runtime. ([Issue #19231](https://github.com/dotnet/fsharp/issues/19231), [PR #19232](https://github.com/dotnet/fsharp/pull/19232)) ### Added diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 94e948cfaf6..529c90d6b03 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -3485,9 +3485,12 @@ and ResolveOverloading | Some ttype -> isTyparTy g ttype | None -> false - match calledMeth.Method with + let minfo = calledMeth.Method + match minfo with | ILMeth(ilMethInfo= ilMethInfo) when not isStaticConstrainedCall && ilMethInfo.IsStatic && ilMethInfo.IsAbstract -> None, ErrorD (Error (FSComp.SR.chkStaticAbstractInterfaceMembers(ilMethInfo.ILName), m)), NoTrace + | FSMeth(g, _, vref, _) when not isStaticConstrainedCall && not minfo.IsInstance && isInterfaceTy g minfo.ApparentEnclosingType && vref.IsDispatchSlotMember -> + None, ErrorD (Error (FSComp.SR.chkStaticAbstractInterfaceMembers(minfo.LogicalName), m)), NoTrace | _ -> Some calledMeth, CompleteD, NoTrace | [], _ when not isOpConversion -> diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs index 120d52b4190..74a67444458 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs @@ -1752,3 +1752,46 @@ printfn "Success: %d" result |> compileAndRun |> shouldSucceed + // Tests for issue #19231: Invoking static abstract member on interface type should be rejected + let private iwsamWarnings = [ "--nowarn:3536" ; "--nowarn:3535" ] + + [] + [] + [ int", "ITest.Parse \"42\"", "Parse", 3, 9, 3, 25)>] + let ``Direct call to static abstract on interface produces error 3866`` (memberDef: string, call: string, memberName: string, l1, c1, l2, c2) = + Fsx $"type ITest =\n {memberDef}\nlet x = {call}" + |> withOptions iwsamWarnings |> typecheck |> shouldFail + |> withSingleDiagnostic (Error 3866, Line l1, Col c1, Line l2, Col c2, $"A static abstract non-virtual interface member should only be called via type parameter (for example: 'T.{memberName}).") + + [] + let ``SRTP call to static abstract via type parameter succeeds`` () = + Fsx "type IP = static abstract Parse : string -> int\ntype P() = interface IP with static member Parse s = int s\nlet inline p<'T when 'T :> IP> s = 'T.Parse s\nif p

\"42\" <> 42 then failwith \"fail\"" + |> withOptions iwsamWarnings |> compileAndRun |> shouldSucceed + + [] + [] // DIM - succeeds + [] // Pure abstract - fails + let ``BCL IAdditionOperators static member`` (op: string, shouldPass: bool) = + let code = Fsx $"open System.Numerics\nlet _ = IAdditionOperators.{op} (5, 7)" + if shouldPass then code |> withOptions iwsamWarnings |> compileAndRun |> shouldSucceed |> ignore + else code |> withOptions iwsamWarnings |> typecheck |> shouldFail |> withErrorCode 3866 |> ignore + + [] + let ``Inherited static abstract without impl produces error 3866`` () = + Fsx "type IBase = static abstract Value : int\ntype IDerived = inherit IBase\nlet _ = IDerived.Value" + |> withOptions iwsamWarnings |> typecheck |> shouldFail |> withErrorCode 3866 |> ignore + + [] + let ``C# static abstract consumed by F# produces error 3866`` () = + let csLib = CSharp "namespace CsLib { public interface IP { static abstract string Get(); } }" + |> withCSharpLanguageVersion CSharpLanguageVersion.Preview |> withName "csLib" + FSharp "module T\nopen CsLib\nlet _ = IP.Get()" |> asExe |> withOptions iwsamWarnings |> withReferences [csLib] + |> compile |> shouldFail |> withErrorCode 3866 |> ignore + + [] + let ``C# static virtual DIM called on interface succeeds`` () = + let csLib = CSharp "namespace CsLib { public interface IP { static virtual string Get() => \"x\"; } }" + |> withCSharpLanguageVersion CSharpLanguageVersion.Preview |> withName "csLib" + FSharp "module T\nopen CsLib\nlet _ = IP.Get()" |> asExe |> withOptions iwsamWarnings |> withReferences [csLib] + |> compileAndRun |> shouldSucceed +