Skip to content

Commit

Permalink
Support consuming IL(C#)-defined generic T with AllowByRefLike anti-c…
Browse files Browse the repository at this point in the history
…onstraint (dotnet#17597)
  • Loading branch information
T-Gro authored Sep 3, 2024
1 parent 11cb682 commit bb027e1
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 54 deletions.
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 @@ -29,6 +29,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 @@ -2292,6 +2292,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

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

0 comments on commit bb027e1

Please sign in to comment.