Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/Compiler/Checking/InfoReader.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = this.GetIntrinsicMethInfosOfType None AccessibleFromSomeFSharpCode AllowMultiIntfInstantiations.Yes IgnoreOverrides m ty
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.DisplayNameCore
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<string option * AccessorDomain * AllowMultiIntfInstantiations> with
member _.GetHashCode((filter: string option, ad: AccessorDomain, _allowMultiIntfInst1)) = hash filter + AccessorDomain.CustomGetHashCode ad
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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(((None, AccessibleFromSomewhere, AllowMultiIntfInstantiations.Yes), m, interfaceTy))

let checkLanguageFeatureRuntimeAndRecover (infoReader: InfoReader) langFeature m =
if not (infoReader.IsLanguageFeatureRuntimeSupported langFeature) then
let featureStr = LanguageVersion.GetFeatureString langFeature
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/Checking/InfoReader.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ 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

Expand Down
41 changes: 41 additions & 0 deletions src/Compiler/Checking/PostInferenceChecks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -681,6 +699,15 @@ 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
// This only applies when the type parameter has an interface constraint - using interfaces
// with unconstrained generics (like List<ITest> or Dictionary<K, ITest>) is fine.
// See: https://github.com/dotnet/fsharp/issues/19184
if tcref.CanDeref then
let typars = tcref.Typars m
if typars.Length = tinst.Length then
(typars, tinst) ||> List.iter2 (CheckInterfaceTypeArgForUnimplementedStaticAbstractMembers cenv m)

let visitTraitSolution info =
match info with
Expand Down Expand Up @@ -1374,6 +1401,20 @@ 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 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 (CheckInterfaceTypeArgForUnimplementedStaticAbstractMembers cenv m)
| _ -> ()
| _ -> ()

CheckExprNoByrefs cenv env f

let hasReceiver =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1783,6 +1783,7 @@ featureReuseSameFieldsInStructUnions,"Share underlying fields in a [<Struct>] 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'."
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading