diff --git a/src/fsharp/TastOps.fs b/src/fsharp/TastOps.fs index 19414aa5f5b..e6dbff68a05 100644 --- a/src/fsharp/TastOps.fs +++ b/src/fsharp/TastOps.fs @@ -1776,19 +1776,17 @@ let isRefTy g ty = isUnitTy g ty ) -// ECMA C# LANGUAGE SPECIFICATION, 27.2 -// An unmanaged-type is any type that isn't a reference-type, a type-parameter, or a generic struct-type and +// An unmanaged-type is any type that isn't a reference-type and // contains no fields whose type is not an unmanaged-type. In other words, an unmanaged-type is one of the // following: // - sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool. // - Any enum-type. // - Any pointer-type. -// - Any non-generic user-defined struct-type that contains fields of unmanaged-types only. -// [Note: Constructed types and type-parameters are never unmanaged-types. end note] +// - Any generic user-defined struct-type that can be statically determined to be 'unmanaged' at construction. let rec isUnmanagedTy g ty = let ty = stripTyEqnsAndMeasureEqns g ty - match tryDestAppTy g ty with - | ValueSome tcref -> + match ty with + | TType_app(tcref, tinst) -> let isEq tcref2 = tyconRefEq g tcref tcref2 if isEq g.nativeptr_tcr || isEq g.nativeint_tcr || isEq g.sbyte_tcr || isEq g.byte_tcr || @@ -1807,11 +1805,47 @@ let rec isUnmanagedTy g ty = true elif tycon.IsStructOrEnumTycon then match tycon.TyparsNoRange with - | [] -> tycon.AllInstanceFieldsAsList |> List.forall (fun r -> isUnmanagedTy g r.rfield_type) - | _ -> false // generic structs are never + | [] -> tycon.AllInstanceFieldsAsList |> List.forall (fun r -> isUnmanagedTy g r.rfield_type) + | typars -> + // Handle generic structs + // REVIEW: This may not be the most optimal, but it's probably better than having to iterate over all type arguments for every field. + // However, what we have currently is probably just fine as unmanaged constraints are used infrequently; even more so when combined with generic struct construction. + let lookup = Dictionary(typars.Length) + (typars, tinst) + ||> List.iter2 (fun typar ty -> lookup.Add(typar.Stamp, ty)) + + tycon.AllInstanceFieldsAsList + |> List.forall (fun r -> + let fieldTy = stripTyEqnsAndMeasureEqns g r.rfield_type + match fieldTy with + | TType_var fieldTypar -> + match lookup.TryGetValue(fieldTypar.Stamp) with + | true, ty -> + match tryDestTyparTy g ty with + | ValueSome typar -> + typar.Constraints |> List.exists (function | TyparConstraint.IsUnmanaged _ -> true | _ -> false) + | _ -> isUnmanagedTy g ty + | _ -> false + | TType_app(fieldTcref, fieldTinst) when fieldTinst.Length > 0 -> + // This will handle nested generic structs + let fieldTinst = + fieldTinst + |> List.map (fun ty -> + match tryDestTyparTy g ty with + | ValueSome(typar) -> + match lookup.TryGetValue(typar.Stamp) with + | true, ty -> ty + | _ -> ty + | _ -> ty + ) + isUnmanagedTy g (mkAppTy fieldTcref fieldTinst) + | _ -> isUnmanagedTy g fieldTy + ) else false - | ValueNone -> - false + // Handle struct tuples + | TType_tuple(TupInfo.Const true, tinst) -> + tinst |> List.forall (isUnmanagedTy g) + | _ -> false let isInterfaceTycon x = isILInterfaceTycon x || x.IsFSharpInterfaceTycon