Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support consuming IL(C#)-defined generic T with AllowByRefLike anti-constraint #17597

Merged
merged 14 commits into from
Sep 3, 2024
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/9.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* Allow object expression without overrides. ([Language suggestion #632](https://github.com/fsharp/fslang-suggestions/issues/632), [PR #17387](https://github.com/dotnet/fsharp/pull/17387))
* Enable FSharp 9.0 Language Version ([Issue #17497](https://github.com/dotnet/fsharp/issues/17438)), [PR](https://github.com/dotnet/fsharp/pull/17500)))
* Enable LanguageFeature.EnforceAttributeTargets in F# 9.0. ([Issue #17514](https://github.com/dotnet/fsharp/issues/17558), [PR #17516](https://github.com/dotnet/fsharp/pull/17558))
* Enable consuming generic arguments defined as `allows ref struct` in C# ([Issue #17597](https://github.com/dotnet/fsharp/issues/17597)

### Changed

Expand Down
4 changes: 3 additions & 1 deletion src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1862,9 +1862,10 @@ type ILGenericParameterDef =
Name: string
Constraints: ILTypes
Variance: ILGenericVariance
HasReferenceTypeConstraint: bool
HasReferenceTypeConstraint: bool
HasNotNullableValueTypeConstraint: bool
HasDefaultConstructorConstraint: bool
HasAllowsRefStruct: bool
CustomAttrsStored: ILAttributesStored
MetadataIndex: int32
}
Expand Down Expand Up @@ -3283,6 +3284,7 @@ let mkILSimpleTypar nm =
HasReferenceTypeConstraint = false
HasNotNullableValueTypeConstraint = false
HasDefaultConstructorConstraint = false
HasAllowsRefStruct = false
CustomAttrsStored = storeILCustomAttrs emptyILCustomAttrs
MetadataIndex = NoMetadataIdx
}
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/AbstractIL/il.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,9 @@ type ILGenericParameterDef =
/// Indicates the type argument must have a public nullary constructor.
HasDefaultConstructorConstraint: bool

/// Indicates the type parameter allows ref struct, i.e. an anti constraint.
HasAllowsRefStruct: bool

/// Do not use this
CustomAttrsStored: ILAttributesStored

Expand Down
1 change: 1 addition & 0 deletions src/Compiler/AbstractIL/ilread.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2291,6 +2291,7 @@ and seekReadGenericParamsUncached ctxtH (GenericParamsIdx(numTypars, a, b)) =
HasReferenceTypeConstraint = (flags &&& 0x0004) <> 0
HasNotNullableValueTypeConstraint = (flags &&& 0x0008) <> 0
HasDefaultConstructorConstraint = (flags &&& 0x0010) <> 0
HasAllowsRefStruct = (flags &&& 0x0020) <> 0
})
)

Expand Down
43 changes: 23 additions & 20 deletions src/Compiler/AbstractIL/ilreflect.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1714,31 +1714,34 @@ let buildGenParamsPass1b cenv emEnv (genArgs: Type array) (gps: ILGenericParamet
gp.CustomAttrs
|> emitCustomAttrs cenv emEnv (wrapCustomAttr gpB.SetCustomAttribute)

let flags = GenericParameterAttributes.None

let flags =
match gp.Variance with
| NonVariant -> flags
| CoVariant -> flags ||| GenericParameterAttributes.Covariant
| ContraVariant -> flags ||| GenericParameterAttributes.Contravariant
| NonVariant -> GenericParameterAttributes.None
| CoVariant -> GenericParameterAttributes.Covariant
| ContraVariant -> GenericParameterAttributes.Contravariant

let flags =
if gp.HasReferenceTypeConstraint then
flags ||| GenericParameterAttributes.ReferenceTypeConstraint
else
flags
let zero = GenericParameterAttributes.None
T-Gro marked this conversation as resolved.
Show resolved Hide resolved

let flags =
if gp.HasNotNullableValueTypeConstraint then
flags ||| GenericParameterAttributes.NotNullableValueTypeConstraint
else
flags

let flags =
if gp.HasDefaultConstructorConstraint then
flags ||| GenericParameterAttributes.DefaultConstructorConstraint
else
flags
flags
||| (if gp.HasReferenceTypeConstraint then
GenericParameterAttributes.ReferenceTypeConstraint
else
zero)
||| (if gp.HasNotNullableValueTypeConstraint then
GenericParameterAttributes.NotNullableValueTypeConstraint
else
zero)
||| (if gp.HasDefaultConstructorConstraint then
GenericParameterAttributes.DefaultConstructorConstraint
else
zero)
|||
// GenericParameterAttributes.AllowByRefLike from net9, not present in ns20
(if gp.HasAllowsRefStruct then
(enum<GenericParameterAttributes> 0x0020)
else
zero)

gpB.SetGenericParameterAttributes flags)
//----------------------------------------------------------------------------
Expand Down
3 changes: 2 additions & 1 deletion src/Compiler/AbstractIL/ilwrite.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2513,7 +2513,8 @@ let rec GetGenericParamAsGenericParamRow cenv _env idx owner gp =
| ContraVariant -> 0x0002) |||
(if gp.HasReferenceTypeConstraint then 0x0004 else 0x0000) |||
(if gp.HasNotNullableValueTypeConstraint then 0x0008 else 0x0000) |||
(if gp.HasDefaultConstructorConstraint then 0x0010 else 0x0000)
(if gp.HasDefaultConstructorConstraint then 0x0010 else 0x0000) |||
(if gp.HasAllowsRefStruct then 0x0020 else 0x0000)


let mdVersionMajor, _ = metadataSchemaVersionSupportedByCLRVersion cenv.desiredMetadataVersion
Expand Down
114 changes: 85 additions & 29 deletions src/Compiler/Checking/PostInferenceChecks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ open FSharp.Compiler.TypedTreeBasics
open FSharp.Compiler.TypedTreeOps
open FSharp.Compiler.TypeHierarchy
open FSharp.Compiler.TypeRelations
open Import

//--------------------------------------------------------------------------
// NOTES: reraise safety checks
Expand Down Expand Up @@ -334,7 +335,20 @@ let RecordAnonRecdInfo cenv (anonInfo: AnonRecdTypeInfo) =
// approx walk of type
//--------------------------------------------------------------------------

let rec CheckTypeDeep (cenv: cenv) (visitTy, visitTyconRefOpt, visitAppTyOpt, visitTraitSolutionOpt, visitTyparOpt as f) (g: TcGlobals) env isInner ty =
/// Represents the container for nester type instantions, carrying information about the parent (generic type) and data about correspinding generic typar definition.
/// For current use, IlGenericParameterDef was enough. For other future use cases, conversion into F# Typar might be needed.
type TypeInstCtx =
| NoInfo
| IlGenericInst of parent:TyconRef * genericArg:ILGenericParameterDef
| TyparInst of parent:TyconRef
| TopLevelAllowingByRef

with member x.TyparAllowsRefStruct() =
match x with
| IlGenericInst(_,ilTypar) -> ilTypar.HasAllowsRefStruct
| _ -> false

let rec CheckTypeDeep (cenv: cenv) (visitTy, visitTyconRefOpt, visitAppTyOpt, visitTraitSolutionOpt, visitTyparOpt as f) (g: TcGlobals) env (typeInstParentOpt:TypeInstCtx) ty =
// We iterate the _solved_ constraints as well, to pick up any record of trait constraint solutions
// This means we walk _all_ the constraints _everywhere_ in a type, including
// those attached to _solved_ type variables. This is used by PostTypeCheckSemanticChecks to detect uses of
Expand Down Expand Up @@ -366,22 +380,30 @@ let rec CheckTypeDeep (cenv: cenv) (visitTy, visitTyconRefOpt, visitAppTyOpt, vi
match ty with
| TType_forall (tps, body) ->
let env = BindTypars g env tps
CheckTypeDeep cenv f g env isInner body
CheckTypeDeep cenv f g env typeInstParentOpt body
tps |> List.iter (fun tp -> tp.Constraints |> List.iter (CheckTypeConstraintDeep cenv f g env))

| TType_measure _ -> ()

| TType_app (tcref, tinst, _) ->
match visitTyconRefOpt with
| Some visitTyconRef -> visitTyconRef isInner tcref
| Some visitTyconRef -> visitTyconRef typeInstParentOpt tcref
| None -> ()

// If it's a 'byref<'T>', don't check 'T as an inner. This allows byref<Span<'T>>.
// 'byref<byref<'T>>' is invalid and gets checked in visitAppTy.
if isByrefTyconRef g tcref then
CheckTypesDeepNoInner cenv f g env tinst
//if isByrefTyconRef g tcref then
// CheckTypesDeepNoInner cenv f g env tinst

if tcref.CanDeref && tcref.IsILTycon && tinst.Length = tcref.ILTyconRawMetadata.GenericParams.Length then
(tinst,tcref.ILTyconRawMetadata.GenericParams)
||> List.iter2 (fun ty ilGenericParam ->
let typeInstParent = IlGenericInst(tcref, ilGenericParam)
CheckTypeDeep cenv f g env typeInstParent ty)
else
CheckTypesDeep cenv f g env tinst
let parentRef = TyparInst(tcref)
for ty in tinst do
CheckTypeDeep cenv f g env parentRef ty

match visitAppTyOpt with
| Some visitAppTy -> visitAppTy (tcref, tinst)
Expand All @@ -398,8 +420,8 @@ let rec CheckTypeDeep (cenv: cenv) (visitTy, visitTyconRefOpt, visitAppTyOpt, vi
CheckTypesDeep cenv f g env tys

| TType_fun (s, t, _) ->
CheckTypeDeep cenv f g env true s
CheckTypeDeep cenv f g env true t
CheckTypeDeep cenv f g env NoInfo s
CheckTypeDeep cenv f g env NoInfo t

| TType_var (tp, _) ->
if not tp.IsSolved then
Expand All @@ -410,20 +432,16 @@ let rec CheckTypeDeep (cenv: cenv) (visitTy, visitTyconRefOpt, visitAppTyOpt, vi

and CheckTypesDeep cenv f g env tys =
for ty in tys do
CheckTypeDeep cenv f g env true ty

and CheckTypesDeepNoInner cenv f g env tys =
for ty in tys do
CheckTypeDeep cenv f g env false ty
CheckTypeDeep cenv f g env NoInfo ty

and CheckTypeConstraintDeep cenv f g env x =
match x with
| TyparConstraint.CoercesTo(ty, _) -> CheckTypeDeep cenv f g env true ty
| TyparConstraint.CoercesTo(ty, _) -> CheckTypeDeep cenv f g env NoInfo ty
| TyparConstraint.MayResolveMember(traitInfo, _) -> CheckTraitInfoDeep cenv f g env traitInfo
| TyparConstraint.DefaultsTo(_, ty, _) -> CheckTypeDeep cenv f g env true ty
| TyparConstraint.DefaultsTo(_, ty, _) -> CheckTypeDeep cenv f g env NoInfo ty
| TyparConstraint.SimpleChoice(tys, _) -> CheckTypesDeep cenv f g env tys
| TyparConstraint.IsEnum(underlyingTy, _) -> CheckTypeDeep cenv f g env true underlyingTy
| TyparConstraint.IsDelegate(argTys, retTy, _) -> CheckTypeDeep cenv f g env true argTys; CheckTypeDeep cenv f g env true retTy
| TyparConstraint.IsEnum(underlyingTy, _) -> CheckTypeDeep cenv f g env NoInfo underlyingTy
| TyparConstraint.IsDelegate(argTys, retTy, _) -> CheckTypeDeep cenv f g env NoInfo argTys; CheckTypeDeep cenv f g env NoInfo retTy
| TyparConstraint.SupportsComparison _
| TyparConstraint.SupportsEquality _
| TyparConstraint.SupportsNull _
Expand All @@ -436,18 +454,18 @@ and CheckTypeConstraintDeep cenv f g env x =
and CheckTraitInfoDeep cenv (_, _, _, visitTraitSolutionOpt, _ as f) g env traitInfo =
CheckTypesDeep cenv f g env traitInfo.SupportTypes
CheckTypesDeep cenv f g env traitInfo.CompiledObjectAndArgumentTypes
Option.iter (CheckTypeDeep cenv f g env true ) traitInfo.CompiledReturnType
Option.iter (CheckTypeDeep cenv f g env NoInfo ) traitInfo.CompiledReturnType
match visitTraitSolutionOpt, traitInfo.Solution with
| Some visitTraitSolution, Some sln -> visitTraitSolution sln
| _ -> ()

/// Check for byref-like types
let CheckForByrefLikeType cenv env m ty check =
CheckTypeDeep cenv (ignore, Some (fun _deep tcref -> if isByrefLikeTyconRef cenv.g m tcref then check()), None, None, None) cenv.g env false ty
CheckTypeDeep cenv (ignore, Some (fun ctx tcref -> if (isByrefLikeTyconRef cenv.g m tcref && not(ctx.TyparAllowsRefStruct())) then check()), None, None, None) cenv.g env NoInfo ty

/// Check for byref types
let CheckForByrefType cenv env ty check =
CheckTypeDeep cenv (ignore, Some (fun _deep tcref -> if isByrefTyconRef cenv.g tcref then check()), None, None, None) cenv.g env false ty
CheckTypeDeep cenv (ignore, Some (fun _ctx tcref -> if isByrefTyconRef cenv.g tcref then check()), None, None, None) cenv.g env NoInfo ty

/// check captures under lambdas
///
Expand Down Expand Up @@ -516,7 +534,7 @@ let CheckTypeForAccess (cenv: cenv) env objName valAcc m ty =
if isLessAccessible tyconAcc valAcc then
errorR(Error(FSComp.SR.chkTypeLessAccessibleThanType(tcref.DisplayName, (objName())), m))

CheckTypeDeep cenv (visitType, None, None, None, None) cenv.g env false ty
CheckTypeDeep cenv (visitType, None, None, None, None) cenv.g env NoInfo ty

let WarnOnWrongTypeForAccess (cenv: cenv) env objName valAcc m ty =
if cenv.reportErrors then
Expand All @@ -534,7 +552,7 @@ let WarnOnWrongTypeForAccess (cenv: cenv) env objName valAcc m ty =
let warningText = errorText + Environment.NewLine + FSComp.SR.tcTypeAbbreviationsCheckedAtCompileTime()
warning(AttributeChecking.ObsoleteWarning(warningText, m))

CheckTypeDeep cenv (visitType, None, None, None, None) cenv.g env false ty
CheckTypeDeep cenv (visitType, None, None, None, None) cenv.g env NoInfo ty

/// Indicates whether a byref or byref-like type is permitted at a particular location
[<RequireQualifiedAccess>]
Expand Down Expand Up @@ -629,16 +647,26 @@ let CheckTypeAux permitByRefLike (cenv: cenv) env m ty onInnerByrefError =
else
errorR (Error(FSComp.SR.checkNotSufficientlyGenericBecauseOfScope(tp.DisplayName), m))

let visitTyconRef isInner tcref =
let visitTyconRef (ctx:TypeInstCtx) tcref =
let checkInner() =
match ctx with
| TopLevelAllowingByRef -> false
| TyparInst(parentTcRef)
| IlGenericInst(parentTcRef,_) when isByrefTyconRef cenv.g parentTcRef -> false
| _ -> true

let isInnerByRefLike() = checkInner() && isByrefLikeTyconRef cenv.g m tcref

let permitByRefLike =
if ctx.TyparAllowsRefStruct() then PermitByRefType.All else permitByRefLike

let isInnerByRefLike = isInner && isByrefLikeTyconRef cenv.g m tcref

match permitByRefLike with
| PermitByRefType.None when isByrefLikeTyconRef cenv.g m tcref ->
errorR(Error(FSComp.SR.chkErrorUseOfByref(), m))
| PermitByRefType.NoInnerByRefLike when isInnerByRefLike ->
| PermitByRefType.NoInnerByRefLike when isInnerByRefLike() ->
onInnerByrefError ()
| PermitByRefType.SpanLike when isByrefTyconRef cenv.g tcref || isInnerByRefLike ->
| PermitByRefType.SpanLike when isByrefTyconRef cenv.g tcref || isInnerByRefLike() ->
onInnerByrefError ()
| _ -> ()

Expand All @@ -665,7 +693,13 @@ let CheckTypeAux permitByRefLike (cenv: cenv) env m ty onInnerByrefError =
cenv.potentialUnboundUsesOfVals <- cenv.potentialUnboundUsesOfVals.Add(vref.Stamp, m)
| _ -> ()

CheckTypeDeep cenv (ignore, Some visitTyconRef, Some visitAppTy, Some visitTraitSolution, Some visitTyar) cenv.g env false ty
let initialCtx =
match permitByRefLike with
| PermitByRefType.SpanLike
| PermitByRefType.NoInnerByRefLike -> TopLevelAllowingByRef
| _ -> NoInfo

CheckTypeDeep cenv (ignore, Some visitTyconRef, Some visitAppTy, Some visitTraitSolution, Some visitTyar) cenv.g env initialCtx ty

let CheckType permitByRefLike cenv env m ty =
CheckTypeAux permitByRefLike cenv env m ty (fun () -> errorR(Error(FSComp.SR.chkErrorUseOfByref(), m)))
Expand Down Expand Up @@ -1458,9 +1492,31 @@ and CheckExprOp cenv env (op, tyargs, args, m) ctxt expr =
CombineTwoLimits limit1 limit2

| TOp.ILCall (_, _, _, _, _, _, _, ilMethRef, enclTypeInst, methInst, retTypes), _, _ ->

CheckTypeInstNoByrefs cenv env m tyargs
CheckTypeInstNoByrefs cenv env m enclTypeInst
CheckTypeInstNoByrefs cenv env m methInst

match enclTypeInst,methInst with
| [],[] -> ()
| enclTypeInst,methInst ->
let tyconRef = ImportILTypeRef cenv.amap m ilMethRef.DeclaringTypeRef
match tyconRef.TypeReprInfo with
| TILObjectRepr(TILObjectReprData(scoref, _, tdef)) ->
(enclTypeInst,tdef.GenericParams)
||> List.iter2 (fun typeInst typeGeneric ->
if not typeGeneric.HasAllowsRefStruct then
CheckTypeNoByrefs cenv env m typeInst)

match methInst with
| [] -> ()
| methInst ->
let methDef = resolveILMethodRefWithRescope (rescopeILType scoref) tdef ilMethRef
(methInst,methDef.GenericParams)
||> List.iter2 (fun methInst methGeneric ->
if not methGeneric.HasAllowsRefStruct then
CheckTypeNoByrefs cenv env m methInst)

| _ -> ()

CheckTypeInstNoInnerByrefs cenv env m retTypes // permit byref returns

let hasReceiver =
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,7 @@ type AnonTypeGenerationTable() =
HasReferenceTypeConstraint = false
HasNotNullableValueTypeConstraint = false
HasDefaultConstructorConstraint = false
HasAllowsRefStruct = false
MetadataIndex = NoMetadataIdx
}
]
Expand Down Expand Up @@ -5733,6 +5734,7 @@ and GenGenericParam cenv eenv (tp: Typar) =
HasReferenceTypeConstraint = refTypeConstraint
HasNotNullableValueTypeConstraint = notNullableValueTypeConstraint || emitUnmanagedInIlOutput
HasDefaultConstructorConstraint = defaultConstructorConstraint
HasAllowsRefStruct = false
}

//--------------------------------------------------------------------------
Expand Down
Loading
Loading