From 5db231836926c53566a39a2c68d08a009f4db4c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 11:18:07 +0000 Subject: [PATCH 1/7] Initial plan From e7b726288fc72d79cad133e242448c79d238ac8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 11:33:41 +0000 Subject: [PATCH 2/7] Implement IWSAM type argument validation Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Checking/InfoReader.fs | 33 ++++ src/Compiler/Checking/InfoReader.fsi | 6 + src/Compiler/Checking/PostInferenceChecks.fs | 29 +++ src/Compiler/FSComp.txt | 1 + .../IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs | 166 ++++++++++++++++++ 5 files changed, 235 insertions(+) diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index def6ae79f75..1b06d6fd37e 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -775,6 +775,26 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = )) g amap m AllowMultiIntfInstantiations.Yes ty + let GetUnimplementedStaticAbstractMemberOfTypeUncached (_flags, m, interfaceTy) = + if not (isInterfaceTy g interfaceTy) then + None + else + let checkMembersOfInterface (ty: TType) = + let meths = GetIntrinsicMethInfosOfType infoReader None AccessibleFromSomeFSharpCode AllowMultiIntfInstantiations.Yes IgnoreOverrides m ty + meths |> List.tryPick (fun minfo -> + // Static abstract non-sealed (non-DIM) members + if minfo.IsStatic && minfo.IsAbstract && not minfo.IsFinal then + Some minfo.LogicalName + else + None + ) + + match checkMembersOfInterface interfaceTy with + | Some name -> Some name + | None -> + let baseInterfaces = AllInterfacesOfType g amap m AllowMultiIntfInstantiations.Yes interfaceTy + baseInterfaces |> List.tryPick checkMembersOfInterface + let hashFlags0 = { new IEqualityComparer with member _.GetHashCode((filter: string option, ad: AccessorDomain, _allowMultiIntfInst1)) = hash filter + AccessorDomain.CustomGetHashCode ad @@ -815,6 +835,11 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = let primaryTypeHierarchyCache = MakeInfoCache "primaryTypeHierarchyCache" GetPrimaryTypeHierarchyUncached HashIdentity.Structural let implicitConversionCache = MakeInfoCache "implicitConversionCache" FindImplicitConversionsUncached hashFlags3 let isInterfaceWithStaticAbstractMethodCache = MakeInfoCache "isInterfaceWithStaticAbstractMethodCache" IsInterfaceTypeWithMatchingStaticAbstractMemberUncached hashFlags4 + let unimplementedStaticAbstractMemberCache = + MakeInfoCache + "unimplementedStaticAbstractMemberCache" + GetUnimplementedStaticAbstractMemberOfTypeUncached + hashFlags0 // Runtime feature support @@ -992,6 +1017,14 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = member _.IsInterfaceTypeWithMatchingStaticAbstractMember m nm ad ty = isInterfaceWithStaticAbstractMethodCache.Apply((ad, nm), m, ty) + member _.TryFindUnimplementedStaticAbstractMemberOfType(m: range, interfaceTy: TType) : string option = + if not (isInterfaceTy g interfaceTy) then + None + elif not (g.langVersion.SupportsFeature LanguageFeature.InterfacesWithAbstractStaticMembers) then + None + else + unimplementedStaticAbstractMemberCache.Apply((AccessibleFromSomewhere, m, interfaceTy)) + let checkLanguageFeatureRuntimeAndRecover (infoReader: InfoReader) langFeature m = if not (infoReader.IsLanguageFeatureRuntimeSupported langFeature) then let featureStr = LanguageVersion.GetFeatureString langFeature diff --git a/src/Compiler/Checking/InfoReader.fsi b/src/Compiler/Checking/InfoReader.fsi index f96c1757add..1c16ad187ec 100644 --- a/src/Compiler/Checking/InfoReader.fsi +++ b/src/Compiler/Checking/InfoReader.fsi @@ -225,6 +225,12 @@ type InfoReader = member IsInterfaceTypeWithMatchingStaticAbstractMember: m: range -> nm: string -> ad: AccessorDomain -> ty: TType -> bool + /// Check if an interface type has an unimplemented static abstract member. + /// Returns Some(memberLogicalName) if found, None otherwise. + /// Results are cached per interface type definition. + member TryFindUnimplementedStaticAbstractMemberOfType: + m: range -> interfaceTy: TType -> string option + val checkLanguageFeatureRuntimeAndRecover: infoReader: InfoReader -> langFeature: Features.LanguageFeature -> m: range -> unit diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index b9a3b4df351..c492ee35cd6 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -681,6 +681,14 @@ let CheckTypeAux permitByRefLike (cenv: cenv) env m ty onInnerByrefError = if isByrefTyconRef cenv.g tcref2 then errorR(Error(FSComp.SR.chkNoByrefsOfByrefs(NicePrint.minimalStringOfType cenv.denv ty), m)) CheckTypesDeep cenv (visitType, None, None, None, None) cenv.g env tinst + + // Check for interfaces with unimplemented static abstract members used as type arguments + if tcref.CanDeref then + let typars = tcref.Typars m + if typars.Length = tinst.Length then + (typars, tinst) ||> List.iter2 (fun typar typeArg -> + CheckInterfaceTypeArgumentForStaticAbstractMembers cenv m typar typeArg + ) let visitTraitSolution info = match info with @@ -721,6 +729,27 @@ let CheckTypeInstNoByrefs cenv env m tyargs = let CheckTypeInstNoInnerByrefs cenv env m tyargs = tyargs |> List.iter (CheckTypeNoInnerByrefs cenv env m) +/// Check that an interface type used as a type argument to a constrained type parameter +/// has implementations for all static abstract members. +/// This only applies when the type parameter has an interface constraint - using interfaces +/// with unconstrained generics (like List or Dictionary) is fine. +/// +/// See: https://github.com/dotnet/fsharp/issues/19184 +let CheckInterfaceTypeArgumentForStaticAbstractMembers (cenv: cenv) (m: range) (typar: Typar) (typeArg: TType) = + if cenv.reportErrors then + // Only check if the type parameter has interface constraints + let hasInterfaceConstraint = + typar.Constraints |> List.exists (function + | TyparConstraint.CoercesTo(constraintTy, _) -> isInterfaceTy cenv.g constraintTy + | _ -> false) + + if hasInterfaceConstraint && isInterfaceTy cenv.g typeArg then + match cenv.infoReader.TryFindUnimplementedStaticAbstractMemberOfType(m, typeArg) with + | Some memberName -> + let interfaceTypeName = NicePrint.minimalStringOfType cenv.denv typeArg + errorR(Error(FSComp.SR.chkInterfaceWithUnimplementedStaticAbstractMemberUsedAsTypeArgument(interfaceTypeName, memberName), m)) + | None -> () + /// Applied functions get wrapped in coerce nodes for subsumption coercions let (|OptionalCoerce|) expr = match stripDebugPoints expr with diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 512e9b4dca7..043a2ae30e2 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1783,6 +1783,7 @@ featureReuseSameFieldsInStructUnions,"Share underlying fields in a [] di 3865,parsOnlySimplePatternsAreAllowedInConstructors,"Only simple patterns are allowed in primary constructors" 3866,chkStaticAbstractInterfaceMembers,"A static abstract non-virtual interface member should only be called via type parameter (for example: 'T.%s)." 3867,chkStaticAbstractMembersOnClasses,"Classes cannot contain static abstract members." +3868,chkInterfaceWithUnimplementedStaticAbstractMemberUsedAsTypeArgument,"The interface '%s' cannot be used as a type argument because the static abstract member '%s' does not have a most specific implementation in the interface." 3868,tcActivePatternArgsCountNotMatchNoArgsNoPat,"This active pattern does not expect any arguments, i.e., it should be used like '%s' instead of '%s x'." 3868,tcActivePatternArgsCountNotMatchOnlyPat,"This active pattern expects exactly one pattern argument, e.g., '%s pat'." 3868,tcActivePatternArgsCountNotMatchArgs,"This active pattern expects %d expression argument(s), e.g., '%s%s'." 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 32c16b46e04..606a8058e70 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs @@ -1579,3 +1579,169 @@ type T = Note that all interface members must be implemented and listed under an appropriate 'interface' declaration, e.g. 'interface ... with member ...'.") ] + // Tests for IWSAM type argument validation (issue #19184) + + [] + let ``Error when interface with unimplemented static abstract is used as type argument to constrained generic`` () = + Fsx """ +type ITest = + static abstract Doot : int + +type Test() = + interface ITest with + static member Doot = 5 + +let test<'T when 'T :> ITest>(x: 'T) = 'T.Doot + +let t = Test() :> ITest +let result = test(t) + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3868, Line 12, Col 14, Line 12, Col 21, + "The interface 'ITest' cannot be used as a type argument because the static abstract member 'Doot' does not have a most specific implementation in the interface.") + ] + + [] + let ``No error when concrete type implementing IWSAM is used as type argument`` () = + Fsx """ +type ITest = + static abstract Doot : int + +type Test() = + interface ITest with + static member Doot = 5 + +let test<'T when 'T :> ITest>(x: 'T) = 'T.Doot + +let t = Test() +let result = test(t) + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> typecheck + |> shouldSucceed + + [] + let ``Error when interface with unimplemented static abstract in base interface is used as type argument`` () = + Fsx """ +type IBase = + static abstract BaseMember : int + +type IDerived = + inherit IBase + +let test<'T when 'T :> IDerived>(x: 'T) = 'T.BaseMember + +let t = Unchecked.defaultof +let result = test(t) + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3868, Line 11, Col 14, Line 11, Col 21, + "The interface 'IDerived' cannot be used as a type argument because the static abstract member 'BaseMember' does not have a most specific implementation in the interface.") + ] + + [] + let ``No error when interface with static abstract is used with unconstrained generic List`` () = + Fsx """ +type ITest = + static abstract Doot : int + +type Test() = + interface ITest with + static member Doot = 5 + +// Using interface as type argument to List - this is fine because List doesn't +// have any constraints that would invoke static abstract members +let items : ITest list = [] +let items2 = [ Test() :> ITest ] +let head = List.tryHead items + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> typecheck + |> shouldSucceed + + [] + let ``No error when interface with static abstract is used with unconstrained generic Map`` () = + Fsx """ +type ITest = + static abstract Doot : int + +type Test() = + interface ITest with + static member Doot = 5 + +// Using interface as type argument to Map - this is fine +let m : Map = Map.empty +let m2 = Map.add "key" (Test() :> ITest) m +let v = Map.tryFind "key" m + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> typecheck + |> shouldSucceed + + [] + let ``No error when interface with static abstract is used with Option`` () = + Fsx """ +type ITest = + static abstract Doot : int + +type Test() = + interface ITest with + static member Doot = 5 + +// Using interface as type argument to Option - this is fine +let opt : ITest option = None +let opt2 = Some (Test() :> ITest) +let opt3 : Option = Option.None + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> typecheck + |> shouldSucceed + + [] + let ``No error when interface with static abstract is used with generic functions`` () = + Fsx """ +type ITest = + static abstract Doot : int + +type Test() = + interface ITest with + static member Doot = 5 + +// Using interface with unconstrained generic functions - this is fine +let t = Test() :> ITest +let same = id t +let u = ignore t +let boxed = box t +let arr : ITest array = [| t |] + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> typecheck + |> shouldSucceed + + [] + let ``No error when interface with static abstract is used with Dictionary`` () = + Fsx """ +open System.Collections.Generic + +type ITest = + static abstract Doot : int + +type Test() = + interface ITest with + static member Doot = 5 + +// Using interface as type argument to Dictionary - this is fine +let dict = Dictionary() +dict.Add("key", Test()) +let v = dict["key"] + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> typecheck + |> shouldSucceed + From a24969fad60beea64eab8d7f20ca5c679678d8f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 11:48:28 +0000 Subject: [PATCH 3/7] Fix build errors in IWSAM implementation Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Checking/InfoReader.fs | 10 ++--- src/Compiler/Checking/PostInferenceChecks.fs | 39 ++++++++------------ src/Compiler/xlf/FSComp.txt.cs.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.de.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.es.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.fr.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.it.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.ja.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.ko.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.pl.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.ru.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.tr.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 +++ src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 +++ 15 files changed, 86 insertions(+), 28 deletions(-) diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index 1b06d6fd37e..36b969041fe 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -780,10 +780,10 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = None else let checkMembersOfInterface (ty: TType) = - let meths = GetIntrinsicMethInfosOfType infoReader None AccessibleFromSomeFSharpCode AllowMultiIntfInstantiations.Yes IgnoreOverrides m ty - meths |> List.tryPick (fun minfo -> + let meths = this.GetIntrinsicMethInfosOfType None AccessibleFromSomeFSharpCode AllowMultiIntfInstantiations.Yes IgnoreOverrides m ty + meths |> List.tryPick (fun (minfo: MethInfo) -> // Static abstract non-sealed (non-DIM) members - if minfo.IsStatic && minfo.IsAbstract && not minfo.IsFinal then + if not minfo.IsInstance && minfo.IsAbstract && not minfo.IsFinal then Some minfo.LogicalName else None @@ -1017,13 +1017,13 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = member _.IsInterfaceTypeWithMatchingStaticAbstractMember m nm ad ty = isInterfaceWithStaticAbstractMethodCache.Apply((ad, nm), m, ty) - member _.TryFindUnimplementedStaticAbstractMemberOfType(m: range, interfaceTy: TType) : string option = + member _.TryFindUnimplementedStaticAbstractMemberOfType (m: range) (interfaceTy: TType) : string option = if not (isInterfaceTy g interfaceTy) then None elif not (g.langVersion.SupportsFeature LanguageFeature.InterfacesWithAbstractStaticMembers) then None else - unimplementedStaticAbstractMemberCache.Apply((AccessibleFromSomewhere, m, interfaceTy)) + unimplementedStaticAbstractMemberCache.Apply(((None, AccessibleFromSomewhere, AllowMultiIntfInstantiations.Yes), m, interfaceTy)) let checkLanguageFeatureRuntimeAndRecover (infoReader: InfoReader) langFeature m = if not (infoReader.IsLanguageFeatureRuntimeSupported langFeature) then diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index c492ee35cd6..c62ec9681e9 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -683,11 +683,25 @@ let CheckTypeAux permitByRefLike (cenv: cenv) env m ty onInnerByrefError = CheckTypesDeep cenv (visitType, None, None, None, None) cenv.g env tinst // Check for interfaces with unimplemented static abstract members used as type arguments - if tcref.CanDeref then + // This only applies when the type parameter has an interface constraint - using interfaces + // with unconstrained generics (like List or Dictionary) is fine. + // See: https://github.com/dotnet/fsharp/issues/19184 + if cenv.reportErrors && tcref.CanDeref then let typars = tcref.Typars m if typars.Length = tinst.Length then (typars, tinst) ||> List.iter2 (fun typar typeArg -> - CheckInterfaceTypeArgumentForStaticAbstractMembers cenv m typar typeArg + // Only check if the type parameter has interface constraints + let hasInterfaceConstraint = + typar.Constraints |> List.exists (function + | TyparConstraint.CoercesTo(constraintTy, _) -> isInterfaceTy cenv.g constraintTy + | _ -> false) + + if hasInterfaceConstraint && isInterfaceTy cenv.g typeArg then + match cenv.infoReader.TryFindUnimplementedStaticAbstractMemberOfType m typeArg with + | Some memberName -> + let interfaceTypeName = NicePrint.minimalStringOfType cenv.denv typeArg + errorR(Error(FSComp.SR.chkInterfaceWithUnimplementedStaticAbstractMemberUsedAsTypeArgument(interfaceTypeName, memberName), m)) + | None -> () ) let visitTraitSolution info = @@ -729,27 +743,6 @@ let CheckTypeInstNoByrefs cenv env m tyargs = let CheckTypeInstNoInnerByrefs cenv env m tyargs = tyargs |> List.iter (CheckTypeNoInnerByrefs cenv env m) -/// Check that an interface type used as a type argument to a constrained type parameter -/// has implementations for all static abstract members. -/// This only applies when the type parameter has an interface constraint - using interfaces -/// with unconstrained generics (like List or Dictionary) is fine. -/// -/// See: https://github.com/dotnet/fsharp/issues/19184 -let CheckInterfaceTypeArgumentForStaticAbstractMembers (cenv: cenv) (m: range) (typar: Typar) (typeArg: TType) = - if cenv.reportErrors then - // Only check if the type parameter has interface constraints - let hasInterfaceConstraint = - typar.Constraints |> List.exists (function - | TyparConstraint.CoercesTo(constraintTy, _) -> isInterfaceTy cenv.g constraintTy - | _ -> false) - - if hasInterfaceConstraint && isInterfaceTy cenv.g typeArg then - match cenv.infoReader.TryFindUnimplementedStaticAbstractMemberOfType(m, typeArg) with - | Some memberName -> - let interfaceTypeName = NicePrint.minimalStringOfType cenv.denv typeArg - errorR(Error(FSComp.SR.chkInterfaceWithUnimplementedStaticAbstractMemberUsedAsTypeArgument(interfaceTypeName, memberName), m)) - | None -> () - /// Applied functions get wrapped in coerce nodes for subsumption coercions let (|OptionalCoerce|) expr = match stripDebugPoints expr with diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 244be90e142..a345e7b8bbf 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -132,6 +132,11 @@ Pokud typ používá atribut [<Sealed>] i [<AbstractClass>], znamená to, že je statický. Členové instance nejsou povoleni. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. Člen nebo funkce „{0}“ má atribut „TailCallAttribute“, ale nepoužívá se koncovým (tail) rekurzivním způsobem. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index d1b1782d093..a2240970789 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -132,6 +132,11 @@ Wenn ein Typ sowohl das Attribute [<Sealed>] wie auch [<AbstractClass>] verwendet, bedeutet dies, dass er statisch ist. Members in Instanzen sind nicht zulässig. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. Der Member oder die Funktion "{0}" weist das Attribut "TailCallAttribute" auf, wird jedoch nicht endrekursiv verwendet. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index cd311cc7fc5..e643ec6bb52 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -132,6 +132,11 @@ Si un tipo usa los atributos [<Sealed>] y [<AbstractClass>], significa que es estático. No se permiten miembros de instancia. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. El miembro o la función “{0}” tiene el atributo “TailCallAttribute”, pero no se usa de forma de recursión de cola. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index e05e9ecdf6c..01f2038adb7 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -132,6 +132,11 @@ Si un type utilise les attributs [<Sealed>] et [<AbstractClass>], cela signifie qu’il est statique. Les membres de l’instance ne sont pas autorisés. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. Le membre ou la fonction « {0} » possède l'attribut « TailCallAttribute », mais n'est pas utilisé de manière récursive. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index a25fd816046..ca030a8cbe4 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -132,6 +132,11 @@ Se un tipo usa entrambi gli attributi [<Sealed>] e [<AbstractClass>], significa che è statico. Membri dell'istanza non consentiti. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. Il membro o la funzione "{0}" ha l'attributo "TailCallAttribute", ma non è in uso in modo ricorsivo finale. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 87b7d40df1e..6008dd1067f 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -132,6 +132,11 @@ 型が [<Sealed>] と [<AbstractClass>] の両方の属性を使用する場合、それは静的であることを意味します。インスタンス メンバーは許可されません。 + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. メンバーまたは関数 '{0}' には 'TailCallAttribute' 属性がありますが、末尾の再帰的な方法では使用されていません。 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index f2fe6e20f97..d7e05c339be 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -132,6 +132,11 @@ 형식이 [<Sealed>] 및 [<AbstractClass>] 특성을 모두 사용하는 경우 정적임을 의미합니다. 인스턴스 멤버는 허용되지 않습니다. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. 멤버 또는 함수 '{0}'에 'TailCallAttribute' 특성이 있지만 비상 재귀적인 방식으로 사용되고 있지 않습니다. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 23a194ff258..beb757ca1bf 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -132,6 +132,11 @@ Jeśli typ używa obu [<Sealed>] i [< AbstractClass>] atrybutów, oznacza to, że jest statyczny. Elementy członkowskie wystąpienia są niedozwolone. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. Składowa lub funkcja „{0}” ma atrybut „TailCallAttribute”, ale nie jest używana w sposób cykliczny końca. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 503fc0f073f..ea5a6181948 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -132,6 +132,11 @@ Se um tipo usa os atributos [<Sealed>] e [<AbstractClass>], significa que é estático. Membros da instância não são permitidos. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. O membro ou a função "{0}" tem o atributo "TailCallAttribute", mas não está sendo usado de maneira recursiva em cauda. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 7013fb0bc83..b997715c857 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -132,6 +132,11 @@ Если тип использует атрибуты [<Sealed>] и [<AbstractClass>], это означает, что он статический. Элементы экземпляра не разрешены. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. Элемент или функция "{0}" содержит атрибут "TailCallAttribute", но не используется в рекурсивном хвостовом режиме. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 49d2a295b45..7ef128ca435 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -132,6 +132,11 @@ Bir tür, hem [<Sealed>] hem de [< AbstractClass>] özniteliklerini kullanıyorsa bu statik olduğu anlamına gelir. Örnek üyelerine izin verilmez. + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. Üye veya '{0}' işlevi, 'TailCallAttribute' özniteliğine sahip ancak kuyruk özyinelemeli bir şekilde kullanılmıyor. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 3fc65eebc96..9a008a0bb89 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -132,6 +132,11 @@ 如果类型同时使用 [<Sealed>] 和 [<AbstractClass>] 属性,则表示它是静态的。不允许使用实例成员。 + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. 成员或函数“{0}”具有 "TailCallAttribute" 属性,但未以尾递归方式使用。 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 07fea0efd23..27b200ce286 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -132,6 +132,11 @@ 如果類型同時使用 [<Sealed>] 和 [<AbstractClass>] 屬性,表示其為靜態。不允許執行個體成員。 + + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface. + + The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way. 成員或函式 '{0}' 具有 'TailCallAttribute' 屬性,但未以尾遞迴方式使用。 From 7854351942ee5f3aa802c62d73768af053e8b4ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:03:38 +0000 Subject: [PATCH 4/7] Add function application checks and fix member name display Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Checking/InfoReader.fs | 2 +- src/Compiler/Checking/PostInferenceChecks.fs | 27 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index 36b969041fe..0e564c3add8 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -784,7 +784,7 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = meths |> List.tryPick (fun (minfo: MethInfo) -> // Static abstract non-sealed (non-DIM) members if not minfo.IsInstance && minfo.IsAbstract && not minfo.IsFinal then - Some minfo.LogicalName + Some minfo.DisplayNameCore else None ) diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index c62ec9681e9..a7f0d2e9837 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -1396,6 +1396,33 @@ and CheckApplication cenv env expr (f, tyargs, argsl, m) ctxt = let env = { env with isInAppExpr = true } CheckTypeInstNoByrefs cenv env m tyargs + + // Check for interfaces with unimplemented static abstract members used as type arguments + // See: https://github.com/dotnet/fsharp/issues/19184 + if cenv.reportErrors && not tyargs.IsEmpty then + match f with + | Expr.Val (vref, _, _) -> + match vref.TryDeref with + | ValueSome v -> + let typars = v.Typars + if typars.Length = tyargs.Length then + (typars, tyargs) ||> List.iter2 (fun typar typeArg -> + // Only check if the type parameter has interface constraints + let hasInterfaceConstraint = + typar.Constraints |> List.exists (function + | TyparConstraint.CoercesTo(constraintTy, _) -> isInterfaceTy cenv.g constraintTy + | _ -> false) + + if hasInterfaceConstraint && isInterfaceTy cenv.g typeArg then + match cenv.infoReader.TryFindUnimplementedStaticAbstractMemberOfType m typeArg with + | Some memberName -> + let interfaceTypeName = NicePrint.minimalStringOfType cenv.denv typeArg + errorR(Error(FSComp.SR.chkInterfaceWithUnimplementedStaticAbstractMemberUsedAsTypeArgument(interfaceTypeName, memberName), m)) + | None -> () + ) + | _ -> () + | _ -> () + CheckExprNoByrefs cenv env f let hasReceiver = From 1da1d21168042267ac8ed8b7c9a8bcef6333b37d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:05:21 +0000 Subject: [PATCH 5/7] Run code formatter Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Checking/InfoReader.fsi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compiler/Checking/InfoReader.fsi b/src/Compiler/Checking/InfoReader.fsi index 1c16ad187ec..df3fbaf0d2d 100644 --- a/src/Compiler/Checking/InfoReader.fsi +++ b/src/Compiler/Checking/InfoReader.fsi @@ -228,8 +228,7 @@ type InfoReader = /// Check if an interface type has an unimplemented static abstract member. /// Returns Some(memberLogicalName) if found, None otherwise. /// Results are cached per interface type definition. - member TryFindUnimplementedStaticAbstractMemberOfType: - m: range -> interfaceTy: TType -> string option + member TryFindUnimplementedStaticAbstractMemberOfType: m: range -> interfaceTy: TType -> string option val checkLanguageFeatureRuntimeAndRecover: infoReader: InfoReader -> langFeature: Features.LanguageFeature -> m: range -> unit From d65b7972236cd4810863a387383f7b138447a296 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 13:44:25 +0000 Subject: [PATCH 6/7] Extract duplicate IWSAM check logic to common function - Extracted CheckInterfaceTypeArgForUnimplementedStaticAbstractMembers to eliminate code duplication - Both visitAppTy and CheckApplication now use the same validation logic - Added compileAndRun test to verify IL verification works correctly with concrete types Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Checking/PostInferenceChecks.fs | 52 ++++++++----------- .../IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs | 22 ++++++++ 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index a7f0d2e9837..3a8263e0d81 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -635,6 +635,24 @@ let rec mkArgsForAppliedExpr isBaseCall argsl x = | Expr.Op (TOp.Coerce, _, [f], _) -> mkArgsForAppliedExpr isBaseCall argsl f | _ -> [] +/// Check if a type argument is an interface with unimplemented static abstract members +/// when used with a type parameter that has interface constraints. +/// See: https://github.com/dotnet/fsharp/issues/19184 +let CheckInterfaceTypeArgForUnimplementedStaticAbstractMembers (cenv: cenv) m (typar: Typar) (typeArg: TType) = + if cenv.reportErrors then + // Only check if the type parameter has interface constraints + let hasInterfaceConstraint = + typar.Constraints |> List.exists (function + | TyparConstraint.CoercesTo(constraintTy, _) -> isInterfaceTy cenv.g constraintTy + | _ -> false) + + if hasInterfaceConstraint && isInterfaceTy cenv.g typeArg then + match cenv.infoReader.TryFindUnimplementedStaticAbstractMemberOfType m typeArg with + | Some memberName -> + let interfaceTypeName = NicePrint.minimalStringOfType cenv.denv typeArg + errorR(Error(FSComp.SR.chkInterfaceWithUnimplementedStaticAbstractMemberUsedAsTypeArgument(interfaceTypeName, memberName), m)) + | None -> () + /// Check types occurring in the TAST. let CheckTypeAux permitByRefLike (cenv: cenv) env m ty onInnerByrefError = if cenv.reportErrors then @@ -686,23 +704,10 @@ let CheckTypeAux permitByRefLike (cenv: cenv) env m ty onInnerByrefError = // This only applies when the type parameter has an interface constraint - using interfaces // with unconstrained generics (like List or Dictionary) is fine. // See: https://github.com/dotnet/fsharp/issues/19184 - if cenv.reportErrors && tcref.CanDeref then + if tcref.CanDeref then let typars = tcref.Typars m if typars.Length = tinst.Length then - (typars, tinst) ||> List.iter2 (fun typar typeArg -> - // Only check if the type parameter has interface constraints - let hasInterfaceConstraint = - typar.Constraints |> List.exists (function - | TyparConstraint.CoercesTo(constraintTy, _) -> isInterfaceTy cenv.g constraintTy - | _ -> false) - - if hasInterfaceConstraint && isInterfaceTy cenv.g typeArg then - match cenv.infoReader.TryFindUnimplementedStaticAbstractMemberOfType m typeArg with - | Some memberName -> - let interfaceTypeName = NicePrint.minimalStringOfType cenv.denv typeArg - errorR(Error(FSComp.SR.chkInterfaceWithUnimplementedStaticAbstractMemberUsedAsTypeArgument(interfaceTypeName, memberName), m)) - | None -> () - ) + (typars, tinst) ||> List.iter2 (CheckInterfaceTypeArgForUnimplementedStaticAbstractMembers cenv m) let visitTraitSolution info = match info with @@ -1399,27 +1404,14 @@ and CheckApplication cenv env expr (f, tyargs, argsl, m) ctxt = // Check for interfaces with unimplemented static abstract members used as type arguments // See: https://github.com/dotnet/fsharp/issues/19184 - if cenv.reportErrors && not tyargs.IsEmpty then + if not tyargs.IsEmpty then match f with | Expr.Val (vref, _, _) -> match vref.TryDeref with | ValueSome v -> let typars = v.Typars if typars.Length = tyargs.Length then - (typars, tyargs) ||> List.iter2 (fun typar typeArg -> - // Only check if the type parameter has interface constraints - let hasInterfaceConstraint = - typar.Constraints |> List.exists (function - | TyparConstraint.CoercesTo(constraintTy, _) -> isInterfaceTy cenv.g constraintTy - | _ -> false) - - if hasInterfaceConstraint && isInterfaceTy cenv.g typeArg then - match cenv.infoReader.TryFindUnimplementedStaticAbstractMemberOfType m typeArg with - | Some memberName -> - let interfaceTypeName = NicePrint.minimalStringOfType cenv.denv typeArg - errorR(Error(FSComp.SR.chkInterfaceWithUnimplementedStaticAbstractMemberUsedAsTypeArgument(interfaceTypeName, memberName), m)) - | None -> () - ) + (typars, tyargs) ||> List.iter2 (CheckInterfaceTypeArgForUnimplementedStaticAbstractMembers cenv m) | _ -> () | _ -> () 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 606a8058e70..b5fe18bac80 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs @@ -1745,3 +1745,25 @@ let v = dict["key"] |> typecheck |> shouldSucceed + [] + let ``Compile and run succeeds for concrete type with IWSAM constraint`` () = + Fsx """ +type ITest = + static abstract Doot : int + +type Test() = + interface ITest with + static member Doot = 5 + +let test<'T when 'T :> ITest>(x: 'T) = 'T.Doot + +// This should work - passing concrete type Test() +let t = Test() +let result = test(t) +if result <> 5 then failwith "Expected 5" +printfn "Success: %d" result + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> compileAndRun + |> shouldSucceed + From aeaf552b75e33ea09ce3ff2b18ed94a35fb22896 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 09:37:54 +0000 Subject: [PATCH 7/7] Add release notes for IWSAM type argument validation fix Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- docs/release-notes/.FSharp.Compiler.Service/10.0.200.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md index a5b9fe2ed7a..e84d4d6573f 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md @@ -2,6 +2,7 @@ * Type relations cache: optimize key generation ([Issue #19116](https://github.com/dotnet/fsharp/issues/18767)) ([PR #19120](https://github.com/dotnet/fsharp/pull/19120)) * Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19151](https://github.com/dotnet/fsharp/pull/19151)) +* Fixed runtime crash when using interfaces with unimplemented static abstract members as constrained type arguments. ([Issue #19184](https://github.com/dotnet/fsharp/issues/19184)) ### Added