Skip to content

Commit b1dc2f1

Browse files
authored
Reject static abstract member invocations on F# interface types (fixes #19231) (#19232)
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).
1 parent 24a4308 commit b1dc2f1

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

docs/release-notes/.FSharp.Compiler.Service/11.0.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* Fix insertion context for modules with multiline attributes. ([Issue #18671](https://github.com/dotnet/fsharp/issues/18671))
1717
* Fix `--typecheck-only` for scripts stopping after processing `#load`-ed script ([PR #19048](https://github.com/dotnet/fsharp/pull/19048))
1818
* 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))
19+
* 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))
1920

2021
### Added
2122

src/Compiler/Checking/ConstraintSolver.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3485,9 +3485,12 @@ and ResolveOverloading
34853485
| Some ttype -> isTyparTy g ttype
34863486
| None -> false
34873487

3488-
match calledMeth.Method with
3488+
let minfo = calledMeth.Method
3489+
match minfo with
34893490
| ILMeth(ilMethInfo= ilMethInfo) when not isStaticConstrainedCall && ilMethInfo.IsStatic && ilMethInfo.IsAbstract ->
34903491
None, ErrorD (Error (FSComp.SR.chkStaticAbstractInterfaceMembers(ilMethInfo.ILName), m)), NoTrace
3492+
| FSMeth(g, _, vref, _) when not isStaticConstrainedCall && not minfo.IsInstance && isInterfaceTy g minfo.ApparentEnclosingType && vref.IsDispatchSlotMember ->
3493+
None, ErrorD (Error (FSComp.SR.chkStaticAbstractInterfaceMembers(minfo.LogicalName), m)), NoTrace
34913494
| _ -> Some calledMeth, CompleteD, NoTrace
34923495

34933496
| [], _ when not isOpConversion ->

tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,3 +1752,46 @@ printfn "Success: %d" result
17521752
|> compileAndRun
17531753
|> shouldSucceed
17541754

1755+
// Tests for issue #19231: Invoking static abstract member on interface type should be rejected
1756+
let private iwsamWarnings = [ "--nowarn:3536" ; "--nowarn:3535" ]
1757+
1758+
[<TheoryForNETCOREAPP>]
1759+
[<InlineData("static abstract Name : string", "ITest.Name", "get_Name", 3, 9, 3, 19)>]
1760+
[<InlineData("static abstract Parse : string -> int", "ITest.Parse \"42\"", "Parse", 3, 9, 3, 25)>]
1761+
let ``Direct call to static abstract on interface produces error 3866`` (memberDef: string, call: string, memberName: string, l1, c1, l2, c2) =
1762+
Fsx $"type ITest =\n {memberDef}\nlet x = {call}"
1763+
|> withOptions iwsamWarnings |> typecheck |> shouldFail
1764+
|> 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}).")
1765+
1766+
[<FactForNETCOREAPP>]
1767+
let ``SRTP call to static abstract via type parameter succeeds`` () =
1768+
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<P> \"42\" <> 42 then failwith \"fail\""
1769+
|> withOptions iwsamWarnings |> compileAndRun |> shouldSucceed
1770+
1771+
[<TheoryForNETCOREAPP>]
1772+
[<InlineData("op_CheckedAddition", true)>] // DIM - succeeds
1773+
[<InlineData("op_Addition", false)>] // Pure abstract - fails
1774+
let ``BCL IAdditionOperators static member`` (op: string, shouldPass: bool) =
1775+
let code = Fsx $"open System.Numerics\nlet _ = IAdditionOperators.{op} (5, 7)"
1776+
if shouldPass then code |> withOptions iwsamWarnings |> compileAndRun |> shouldSucceed |> ignore
1777+
else code |> withOptions iwsamWarnings |> typecheck |> shouldFail |> withErrorCode 3866 |> ignore
1778+
1779+
[<FactForNETCOREAPP>]
1780+
let ``Inherited static abstract without impl produces error 3866`` () =
1781+
Fsx "type IBase = static abstract Value : int\ntype IDerived = inherit IBase\nlet _ = IDerived.Value"
1782+
|> withOptions iwsamWarnings |> typecheck |> shouldFail |> withErrorCode 3866 |> ignore
1783+
1784+
[<FactForNETCOREAPP>]
1785+
let ``C# static abstract consumed by F# produces error 3866`` () =
1786+
let csLib = CSharp "namespace CsLib { public interface IP { static abstract string Get(); } }"
1787+
|> withCSharpLanguageVersion CSharpLanguageVersion.Preview |> withName "csLib"
1788+
FSharp "module T\nopen CsLib\nlet _ = IP.Get()" |> asExe |> withOptions iwsamWarnings |> withReferences [csLib]
1789+
|> compile |> shouldFail |> withErrorCode 3866 |> ignore
1790+
1791+
[<FactForNETCOREAPP>]
1792+
let ``C# static virtual DIM called on interface succeeds`` () =
1793+
let csLib = CSharp "namespace CsLib { public interface IP { static virtual string Get() => \"x\"; } }"
1794+
|> withCSharpLanguageVersion CSharpLanguageVersion.Preview |> withName "csLib"
1795+
FSharp "module T\nopen CsLib\nlet _ = IP.Get()" |> asExe |> withOptions iwsamWarnings |> withReferences [csLib]
1796+
|> compileAndRun |> shouldSucceed
1797+

0 commit comments

Comments
 (0)