diff --git a/src/Compiler/AbstractIL/ilwrite.fs b/src/Compiler/AbstractIL/ilwrite.fs index 08fd1d05ae..ecddf2460d 100644 --- a/src/Compiler/AbstractIL/ilwrite.fs +++ b/src/Compiler/AbstractIL/ilwrite.fs @@ -2490,6 +2490,7 @@ let rec GetGenericParamAsGenericParamRow cenv _env idx owner gp = (if gp.HasReferenceTypeConstraint then 0x0004 else 0x0000) ||| (if gp.HasNotNullableValueTypeConstraint then 0x0008 else 0x0000) ||| (if gp.HasDefaultConstructorConstraint then 0x0010 else 0x0000) + let mdVersionMajor, _ = metadataSchemaVersionSupportedByCLRVersion cenv.desiredMetadataVersion if (mdVersionMajor = 1) then diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 29c753396a..e711ec7e26 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -2139,6 +2139,10 @@ and EnforceConstraintConsistency (csenv: ConstraintSolverEnv) ndeep m2 trace ret | TyparConstraint.IsNonNullableStruct _, TyparConstraint.IsReferenceType _ | TyparConstraint.IsReferenceType _, TyparConstraint.IsNonNullableStruct _ -> return! ErrorD (Error(FSComp.SR.csStructConstraintInconsistent(), m)) + + | TyparConstraint.IsUnmanaged _, TyparConstraint.IsReferenceType _ + | TyparConstraint.IsReferenceType _, TyparConstraint.IsUnmanaged _ -> + return! ErrorD (Error(FSComp.SR.csUnmanagedConstraintInconsistent(), m)) | TyparConstraint.SupportsComparison _, TyparConstraint.SupportsComparison _ | TyparConstraint.SupportsEquality _, TyparConstraint.SupportsEquality _ @@ -2421,17 +2425,31 @@ and SolveTypeIsNonNullableValueType (csenv: ConstraintSolverEnv) ndeep m2 trace } and SolveTypeIsUnmanaged (csenv: ConstraintSolverEnv) ndeep m2 trace ty = - let g = csenv.g - let m = csenv.m - let denv = csenv.DisplayEnv - match tryDestTyparTy g ty with - | ValueSome destTypar -> - AddConstraint csenv ndeep m2 trace destTypar (TyparConstraint.IsUnmanaged m) - | _ -> - if isUnmanagedTy g ty then - CompleteD - else - ErrorD (ConstraintSolverError(FSComp.SR.csGenericConstructRequiresUnmanagedType(NicePrint.minimalStringOfType denv ty), m, m2)) + trackErrors { + let g = csenv.g + let m = csenv.m + let denv = csenv.DisplayEnv + match tryDestTyparTy g ty with + | ValueSome destTypar -> + return! AddConstraint csenv ndeep m2 trace destTypar (TyparConstraint.IsUnmanaged m) + | _ -> + if isStructAnonRecdTy g ty then + return! destStructAnonRecdTy g ty |> IterateD (SolveTypeIsUnmanaged csenv (ndeep + 1) m2 trace) + else if isStructTupleTy g ty then + return! destStructTupleTy g ty |> IterateD (SolveTypeIsUnmanaged csenv (ndeep + 1) m2 trace) + else if isStructUnionTy g ty then + let tcref = tryTcrefOfAppTy g ty |> ValueOption.get + let tinst = mkInstForAppTy g ty + return! + tcref.UnionCasesAsRefList + |> List.collect (actualTysOfUnionCaseFields tinst) + |> IterateD (SolveTypeIsUnmanaged csenv (ndeep + 1) m2 trace) + else + if isUnmanagedTy g ty then + return! CompleteD + else + return! ErrorD (ConstraintSolverError(FSComp.SR.csGenericConstructRequiresUnmanagedType(NicePrint.minimalStringOfType denv ty), m, m2)) + } and SolveTypeChoice (csenv: ConstraintSolverEnv) ndeep m2 trace ty choiceTys = diff --git a/src/Compiler/Checking/import.fs b/src/Compiler/Checking/import.fs index bf6c9e7300..23edca4faf 100644 --- a/src/Compiler/Checking/import.fs +++ b/src/Compiler/Checking/import.fs @@ -459,16 +459,24 @@ let ImportILGenericParameters amap m scoref tinst (gps: ILGenericParameterDefs) match gps with | [] -> [] | _ -> - let amap = amap() + let amap : ImportMap = amap() let tps = gps |> List.map (fun gp -> Construct.NewRigidTypar gp.Name m) let tptys = tps |> List.map mkTyparTy let importInst = tinst@tptys (tps, gps) ||> List.iter2 (fun tp gp -> - let constraints = gp.Constraints |> List.map (fun ilTy -> TyparConstraint.CoercesTo(ImportILType amap m importInst (rescopeILType scoref ilTy), m) ) - let constraints = if gp.HasReferenceTypeConstraint then (TyparConstraint.IsReferenceType(m) :: constraints) else constraints - let constraints = if gp.HasNotNullableValueTypeConstraint then (TyparConstraint.IsNonNullableStruct(m) :: constraints) else constraints - let constraints = if gp.HasDefaultConstructorConstraint then (TyparConstraint.RequiresDefaultConstructor(m) :: constraints) else constraints + let constraints = + [ if gp.CustomAttrs |> TryFindILAttribute amap.g.attrib_IsUnmanagedAttribute then + TyparConstraint.IsUnmanaged(m) + if gp.HasDefaultConstructorConstraint then + TyparConstraint.RequiresDefaultConstructor(m) + if gp.HasNotNullableValueTypeConstraint then + TyparConstraint.IsNonNullableStruct(m) + if gp.HasReferenceTypeConstraint then + TyparConstraint.IsReferenceType(m) + for ilTy in gp.Constraints do + TyparConstraint.CoercesTo(ImportILType amap m importInst (rescopeILType scoref ilTy), m) ] + tp.SetConstraints constraints) tps diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 704c43d4b2..33b39ce7f7 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -5555,7 +5555,7 @@ and GenGenericParam cenv eenv (tp: Typar) = let refTypeConstraint = tp.Constraints |> List.exists (function - | TyparConstraint.IsReferenceType _ -> true + | TyparConstraint.IsReferenceType _ | TyparConstraint.SupportsNull _ -> true | _ -> false) @@ -5571,6 +5571,13 @@ and GenGenericParam cenv eenv (tp: Typar) = | TyparConstraint.RequiresDefaultConstructor _ -> true | _ -> false) + let emitUnmanagedInIlOutput = + cenv.g.langVersion.SupportsFeature(LanguageFeature.UnmanagedConstraintCsharpInterop) + && tp.Constraints + |> List.exists (function + | TyparConstraint.IsUnmanaged _ -> true + | _ -> false) + let tpName = // use the CompiledName if given // Inference variables get given an IL name "TA, TB" etc. @@ -5598,16 +5605,31 @@ and GenGenericParam cenv eenv (tp: Typar) = else nm - let tpAttrs = mkILCustomAttrs (GenAttrs cenv eenv tp.Attribs) + let attributeList = + let defined = GenAttrs cenv eenv tp.Attribs + + if emitUnmanagedInIlOutput then + (GetIsUnmanagedAttribute g) :: defined + else + defined + + let tpAttrs = mkILCustomAttrs (attributeList) + + let modreqValueType () = + ILType.Modified(true, g.iltyp_UnmanagedType.TypeRef, g.iltyp_ValueType) { Name = tpName - Constraints = subTypeConstraints + Constraints = + if emitUnmanagedInIlOutput then + (modreqValueType () :: subTypeConstraints) + else + subTypeConstraints Variance = NonVariant CustomAttrsStored = storeILCustomAttrs tpAttrs MetadataIndex = NoMetadataIdx HasReferenceTypeConstraint = refTypeConstraint - HasNotNullableValueTypeConstraint = notNullableValueTypeConstraint + HasNotNullableValueTypeConstraint = notNullableValueTypeConstraint || emitUnmanagedInIlOutput HasDefaultConstructorConstraint = defaultConstructorConstraint } diff --git a/src/Compiler/CodeGen/IlxGenSupport.fs b/src/Compiler/CodeGen/IlxGenSupport.fs index f90371d7e4..6fd2074c4e 100644 --- a/src/Compiler/CodeGen/IlxGenSupport.fs +++ b/src/Compiler/CodeGen/IlxGenSupport.fs @@ -155,10 +155,16 @@ let mkLocalPrivateInt32Enum (g: TcGlobals, tref: ILTypeRef, values: (string * in // Generate Local embeddable versions of framework types when necessary //-------------------------------------------------------------------------- -let GetReadOnlyAttribute (g: TcGlobals) = - let tref = g.attrib_IsReadOnlyAttribute.TypeRef +let private getPotentiallyEmbedableAttribute (g: TcGlobals) (info: BuiltinAttribInfo) = + let tref = info.TypeRef g.TryEmbedILType(tref, (fun () -> mkLocalPrivateAttributeWithDefaultConstructor (g, tref.Name))) - mkILCustomAttribute (g.attrib_IsReadOnlyAttribute.TypeRef, [], [], []) + mkILCustomAttribute (info.TypeRef, [], [], []) + +let GetReadOnlyAttribute (g: TcGlobals) = + getPotentiallyEmbedableAttribute g g.attrib_IsReadOnlyAttribute + +let GetIsUnmanagedAttribute (g: TcGlobals) = + getPotentiallyEmbedableAttribute g g.attrib_IsUnmanagedAttribute let GenReadOnlyAttributeIfNecessary g ty = if isInByrefTy g ty then diff --git a/src/Compiler/CodeGen/IlxGenSupport.fsi b/src/Compiler/CodeGen/IlxGenSupport.fsi index 4e2d9351c8..24968a25ec 100644 --- a/src/Compiler/CodeGen/IlxGenSupport.fsi +++ b/src/Compiler/CodeGen/IlxGenSupport.fsi @@ -21,3 +21,4 @@ val GetDynamicDependencyAttribute: g: TcGlobals -> memberTypes: int32 -> ilType: val GenReadOnlyModReqIfNecessary: g: TcGlobals -> ty: TypedTree.TType -> ilTy: ILType -> ILType val GenReadOnlyAttributeIfNecessary: g: TcGlobals -> ty: TypedTree.TType -> ILAttribute option val GetReadOnlyAttribute: g: TcGlobals -> ILAttribute +val GetIsUnmanagedAttribute: g: TcGlobals -> ILAttribute diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index d9ba6d6c7a..9f786ad9b1 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -318,6 +318,7 @@ csTypeDoesNotSupportConversion,"The type '%s' does not support a conversion to t csMethodFoundButIsStatic,"The type '%s' has a method '%s' (full name '%s'), but the method is static" csMethodFoundButIsNotStatic,"The type '%s' has a method '%s' (full name '%s'), but the method is not static" 472,csStructConstraintInconsistent,"The constraints 'struct' and 'not struct' are inconsistent" +473,csUnmanagedConstraintInconsistent,"The constraints 'unmanaged' and 'not struct' are inconsistent" csTypeDoesNotHaveNull,"The type '%s' does not have 'null' as a proper value" csNullableTypeDoesNotHaveNull,"The type '%s' does not have 'null' as a proper value. To create a null value for a Nullable type use 'System.Nullable()'." csTypeDoesNotSupportComparison1,"The type '%s' does not support the 'comparison' constraint because it has the 'NoComparison' attribute" @@ -1712,4 +1713,5 @@ featureAccessorFunctionShorthand,"underscore dot shorthand for accessor only fun 3570,tcStaticBindingInExtrinsicAugmentation,"Static bindings cannot be added to extrinsic augmentations. Consider using a 'static member' instead." 3571,pickleFsharpCoreBackwardsCompatible,"Newly added pickle state cannot be used in FSharp.Core, since it must be working in older compilers+tooling as well. The time window is at least 3 years after feature introduction. Violation: %s . Context: \n %s " 3577,tcOverrideUsesMultipleArgumentsInsteadOfTuple,"This override takes a tuple instead of multiple arguments. Try to add an additional layer of parentheses at the method definition (e.g. 'member _.Foo((x, y))'), or remove parentheses at the abstract method declaration (e.g. 'abstract member Foo: 'a * 'b -> 'c')." -3578,chkCopyUpdateSyntaxInAnonRecords,"This expression is an anonymous record, use {{|...|}} instead of {{...}}." \ No newline at end of file +featureUnmanagedConstraintCsharpInterop,"Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq)" +3578,chkCopyUpdateSyntaxInAnonRecords,"This expression is an anonymous record, use {{|...|}} instead of {{...}}." diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index b158a501b1..42aeccd5af 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -74,6 +74,7 @@ type LanguageFeature = | DiagnosticForObjInference | StaticLetInRecordsDusEmptyTypes | WarningWhenTailRecAttributeButNonTailRecUsage + | UnmanagedConstraintCsharpInterop | WhileBang /// LanguageVersion management @@ -172,6 +173,7 @@ type LanguageVersion(versionText) = LanguageFeature.WarningWhenTailRecAttributeButNonTailRecUsage, previewVersion LanguageFeature.StaticLetInRecordsDusEmptyTypes, previewVersion LanguageFeature.StrictIndentation, previewVersion + LanguageFeature.UnmanagedConstraintCsharpInterop, previewVersion LanguageFeature.WhileBang, previewVersion ] @@ -302,6 +304,7 @@ type LanguageVersion(versionText) = | LanguageFeature.StaticLetInRecordsDusEmptyTypes -> FSComp.SR.featureStaticLetInRecordsDusEmptyTypes () | LanguageFeature.StrictIndentation -> FSComp.SR.featureStrictIndentation () | LanguageFeature.WarningWhenTailRecAttributeButNonTailRecUsage -> FSComp.SR.featureChkNotTailRecursive () + | LanguageFeature.UnmanagedConstraintCsharpInterop -> FSComp.SR.featureUnmanagedConstraintCsharpInterop () | LanguageFeature.WhileBang -> FSComp.SR.featureWhileBang () /// Get a version string associated with the given feature. diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 06880bb600..a3eaf39a6c 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -64,6 +64,7 @@ type LanguageFeature = | DiagnosticForObjInference | StaticLetInRecordsDusEmptyTypes | WarningWhenTailRecAttributeButNonTailRecUsage + | UnmanagedConstraintCsharpInterop | WhileBang /// LanguageVersion management diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs index f8e27c8da0..01bd1a1283 100755 --- a/src/Compiler/TypedTree/TcGlobals.fs +++ b/src/Compiler/TypedTree/TcGlobals.fs @@ -165,6 +165,8 @@ let tname_CompilerGeneratedAttribute = "System.Runtime.CompilerServices.Compiler [] let tname_ReferenceAssemblyAttribute = "System.Runtime.CompilerServices.ReferenceAssemblyAttribute" [] +let tname_UnmanagedType = "System.Runtime.InteropServices.UnmanagedType" +[] let tname_DebuggableAttribute = "System.Diagnostics.DebuggableAttribute" [] let tname_AsyncCallback = "System.AsyncCallback" @@ -336,6 +338,7 @@ type TcGlobals( static let isInEmbeddableKnownSet name = match name with | "System.Runtime.CompilerServices.IsReadOnlyAttribute" + | "System.Runtime.CompilerServices.IsUnmanagedAttribute" | "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute" | "System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes" -> true | _ -> false @@ -1415,6 +1418,7 @@ type TcGlobals( member val iltyp_RuntimeMethodHandle = findSysILTypeRef tname_RuntimeMethodHandle |> mkILNonGenericValueTy member val iltyp_RuntimeTypeHandle = findSysILTypeRef tname_RuntimeTypeHandle |> mkILNonGenericValueTy member val iltyp_ReferenceAssemblyAttributeOpt = tryFindSysILTypeRef tname_ReferenceAssemblyAttribute |> Option.map mkILNonGenericBoxedTy + member val iltyp_UnmanagedType = findSysILTypeRef tname_UnmanagedType |> mkILNonGenericValueTy member val attrib_AttributeUsageAttribute = findSysAttrib "System.AttributeUsageAttribute" member val attrib_ParamArrayAttribute = findSysAttrib "System.ParamArrayAttribute" member val attrib_IDispatchConstantAttribute = tryFindSysAttrib "System.Runtime.CompilerServices.IDispatchConstantAttribute" @@ -1423,6 +1427,7 @@ type TcGlobals( // We use 'findSysAttrib' here because lookup on attribute is done by name comparison, and can proceed // even if the type is not found in a system assembly. member val attrib_IsReadOnlyAttribute = findOrEmbedSysPublicType "System.Runtime.CompilerServices.IsReadOnlyAttribute" + member val attrib_IsUnmanagedAttribute = findOrEmbedSysPublicType "System.Runtime.CompilerServices.IsUnmanagedAttribute" member val attrib_DynamicDependencyAttribute = findOrEmbedSysPublicType "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute" member val enum_DynamicallyAccessedMemberTypes = findOrEmbedSysPublicType "System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes" diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 774f994aaa..8ca916e9ae 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -809,6 +809,10 @@ let destAnyParTy g ty = ty |> stripTyEqns g |> (function TType_var (v, _) -> v | let destMeasureTy g ty = ty |> stripTyEqns g |> (function TType_measure m -> m | _ -> failwith "destMeasureTy: not a unit-of-measure type") +let destAnonRecdTy g ty = ty |> stripTyEqns g |> (function TType_anon (anonInfo, tys) -> anonInfo, tys | _ -> failwith "destAnonRecdTy: not an anonymous record type") + +let destStructAnonRecdTy g ty = ty |> stripTyEqns g |> (function TType_anon (anonInfo, tys) when evalAnonInfoIsStruct anonInfo -> tys | _ -> failwith "destAnonRecdTy: not a struct anonymous record type") + let isFunTy g ty = ty |> stripTyEqns g |> (function TType_fun _ -> true | _ -> false) let isForallTy g ty = ty |> stripTyEqns g |> (function TType_forall _ -> true | _ -> false) @@ -825,6 +829,8 @@ let isStructAnonRecdTy g ty = ty |> stripTyEqns g |> (function TType_anon (anonI let isUnionTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref, _, _) -> tcref.IsUnionTycon | _ -> false) +let isStructUnionTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref, _, _) -> tcref.IsUnionTycon && tcref.Deref.entity_flags.IsStructRecordOrUnionType | _ -> false) + let isReprHiddenTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref, _, _) -> tcref.IsHiddenReprTycon | _ -> false) let isFSharpObjModelTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref, _, _) -> tcref.IsFSharpObjectModelTycon | _ -> false) @@ -1957,22 +1963,23 @@ let isForallFunctionTy g ty = let _, tau = tryDestForallTy g ty isFunTy g tau -// 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 // 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 isUnmanagedRecordField tinst rf = + isUnmanagedTy g (actualTyOfRecdField tinst rf) + let ty = stripTyEqnsAndMeasureEqns g ty match tryTcrefOfAppTy g ty with | ValueSome tcref -> - let isEq tcref2 = tyconRefEq g tcref tcref2 + let isEq tcref2 = tyconRefEq g tcref tcref2 if isEq g.nativeptr_tcr || isEq g.nativeint_tcr || - isEq g.sbyte_tcr || isEq g.byte_tcr || + isEq g.sbyte_tcr || isEq g.byte_tcr || isEq g.int16_tcr || isEq g.uint16_tcr || isEq g.int32_tcr || isEq g.uint32_tcr || isEq g.int64_tcr || isEq g.uint64_tcr || @@ -1984,15 +1991,24 @@ let rec isUnmanagedTy g ty = true else let tycon = tcref.Deref - if tycon.IsEnumTycon then + if tycon.IsEnumTycon then true + elif isStructUnionTy g ty then + let tinst = mkInstForAppTy g ty + tcref.UnionCasesAsRefList + |> List.forall (fun c -> c |> actualTysOfUnionCaseFields tinst |> List.forall (isUnmanagedTy g)) elif tycon.IsStructOrEnumTycon then - match tycon.TyparsNoRange with - | [] -> tycon.AllInstanceFieldsAsList |> List.forall (fun r -> isUnmanagedTy g r.rfield_type) - | _ -> false // generic structs are never + let tinst = mkInstForAppTy g ty + tycon.AllInstanceFieldsAsList + |> List.forall (isUnmanagedRecordField tinst) else false | ValueNone -> - false + if isStructTupleTy g ty then + (destStructTupleTy g ty) |> List.forall (isUnmanagedTy g) + else if isStructAnonRecdTy g ty then + (destStructAnonRecdTy g ty) |> List.forall (isUnmanagedTy g) + else + false let isInterfaceTycon x = isILInterfaceTycon x || x.IsFSharpInterfaceTycon diff --git a/src/Compiler/TypedTree/TypedTreeOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index 8ba51ea5ee..4bffdcbde5 100755 --- a/src/Compiler/TypedTree/TypedTreeOps.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.fsi @@ -633,6 +633,10 @@ val destAnyParTy: TcGlobals -> TType -> Typar val destMeasureTy: TcGlobals -> TType -> Measure +val destAnonRecdTy: TcGlobals -> TType -> AnonRecdTypeInfo * TTypes + +val destStructAnonRecdTy: TcGlobals -> TType -> TTypes + val tryDestForallTy: TcGlobals -> TType -> Typars * TType val isFunTy: TcGlobals -> TType -> bool @@ -651,6 +655,8 @@ val isAnonRecdTy: TcGlobals -> TType -> bool val isUnionTy: TcGlobals -> TType -> bool +val isStructUnionTy: TcGlobals -> TType -> bool + val isReprHiddenTy: TcGlobals -> TType -> bool val isFSharpObjModelTy: TcGlobals -> TType -> bool diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 228d79015d..eafe702269 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -167,6 +167,11 @@ Argument {0} neodpovídá + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Všechny větve výrazu if musí vracet hodnoty implicitně převoditelné na typ první větve, což je řazená kolekce členů o délce {0} typu\n {1} \nTato větev vrací řazenou kolekci členů o délce {2} typu\n {3} \n @@ -452,6 +457,11 @@ Podpora try-with ve výrazech pořadí + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Vyvolá upozornění, když výraz záznamu kopírování a aktualizace změní všechna pole záznamu. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index bd7798f2d3..181d79f304 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -167,6 +167,11 @@ Das Argument "{0}" stimmt nicht überein. + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Alle Verzweigungen eines „If-Ausdrucks“ müssen Werte zurückgeben, die implizit in den Typ der ersten Verzweigung konvertierbar sind. In diesem Fall handelt es sich dabei um ein Tupel der Länge {0} des Typs.\n {1} \nDiese Verzweigung gibt ein Tupel der Länge {2} des Typs\n {3} \nzurück. @@ -452,6 +457,11 @@ Unterstützung für "try-with" in Sequenzausdrücken + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Löst Warnungen aus, wenn ein Ausdruck zum Kopieren und Aktualisieren von Datensätzen alle Felder eines Datensatzes ändert. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index a6de2ed63a..b0d9d82dc9 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -167,6 +167,11 @@ El argumento "{0}" no coincide. + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Todas las ramas de una expresión 'if' deben devolver valores implícitamente convertibles al tipo de la primera rama, que aquí es una tupla de longitud {0} de tipo\n {1} \nEsta rama devuelve una tupla de longitud {2} de tipo\n {3} \n @@ -452,6 +457,11 @@ Compatibilidad con try-with en expresiones secuenciales + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Emite advertencias cuando una expresión de copiar y actualizar registros cambia todos los campos de un registro. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 6c6230ad97..e80c6033a5 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -167,6 +167,11 @@ L'argument '{0}' ne correspond pas + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Toutes les branches d’une expression « if » doivent retourner des valeurs implicitement convertibles au type de la première branche, qui est ici un tuple de longueur {0} de type\n {1} \nCette branche renvoie un tuple de longueur {2} de type\n {3} \n @@ -452,6 +457,11 @@ Prise en charge de try-with dans les expressions de séquence + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Génère des avertissements lorsqu'une expression d'enregistrement de copie et de mise à jour modifie tous les champs d'un enregistrement. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index c58c13a6e0..e40b57dae1 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -167,6 +167,11 @@ L'argomento '{0}' non corrisponde + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Tutti i rami di un'espressione 'if' devono restituire valori convertibili in modo implicito nel tipo del primo ramo, che è una tupla di lunghezza {0} di tipo\n {1} \nQuesto ramo restituisce una tupla di lunghezza {2} di tipo\n {3} \n @@ -452,6 +457,11 @@ Supporto per try-with nelle espressioni di sequenza + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Genera avvisi quando un'espressione di record di copia e aggiornamento modifica tutti i campi di un record. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 7d79034951..9f9830e214 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -167,6 +167,11 @@ 引数 '{0}' が一致しません + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n 'if' 式のすべての分岐は、最初の分岐の型に暗黙的に変換できる値を返す必要があります。これは、型の長さ {0} のタプルです\n {1} \nこの分岐は、型の長さ {2} のタプルを返します\n {3} \n @@ -452,6 +457,11 @@ シーケンス式内の try-with のサポート + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. copy-and-update レコード式によってレコードのすべてのフィールドが変更されたときに警告を表示します。 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 9f8c5930a0..f5b2130327 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -167,6 +167,11 @@ '{0}' 인수가 일치하지 않습니다. + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n 'if' 식의 모든 분기는 첫 번째 분기의 유형으로 암시적으로 변환 가능한 값을 반환해야 합니다. 여기서는 형식이 \n {1}이고 길이가 {0}인 튜플입니다. \n이 분기는 형식이 \n {3}이고 길이가 {2}인 튜플을 반환합니다. \n @@ -452,6 +457,11 @@ 시퀀스 식에서 try-with 지원 + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. 레코드 복사 및 업데이트 식이 레코드의 모든 필드를 변경할 때 경고를 발생합니다. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 580e507aea..8ba98da9f2 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -167,6 +167,11 @@ Argument „{0}” nie jest zgodny + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Wszystkie gałęzie wyrażenia „if” muszą zwracać wartości niejawnie konwertowalne na typ pierwszej gałęzi, która tutaj jest krotką o długości {0} typu\n {1} \nTa gałąź zwraca krotkę o długości {2} typu\n {3} \n @@ -452,6 +457,11 @@ Obsługa instrukcji try-with w wyrażeniach sekwencji + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Zgłasza ostrzeżenia, gdy wyrażenie rekordu kopiowania i aktualizacji zmieni wszystkie pola rekordu. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 00fb4aabaa..a9c5544273 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -167,6 +167,11 @@ O argumento '{0}' não corresponde + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Todas as ramificações de uma expressão 'if' devem retornar valores implicitamente conversíveis ao tipo da primeira ramificação, que aqui é uma tupla de comprimento {0} do tipo\n {1} \nEsta ramificação retorna uma tupla de comprimento {2} do tipo\n {3} \n @@ -452,6 +457,11 @@ Suporte para try-with em expressões de sequência + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Gera avisos quando uma expressão de registro de cópia e atualização altera todos os campos de um registro. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index ceb37f8bc9..46e5d7ba3d 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -167,6 +167,11 @@ Аргумент "{0}" не соответствует + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Все ветви выражения "if" должны возвращать значения, поддерживающие неявное преобразование в тип первой ветви, которым здесь является кортеж длиной {0} типа\n {1} \nЭта ветвь возвращает кортеж длиной {2} типа\n {3} \n @@ -452,6 +457,11 @@ Поддержка try-with в выражениях последовательности + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Создает предупреждения, когда выражение копирования и обновления записи изменяет все поля записи. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index e579717bd1..0bf209b346 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -167,6 +167,11 @@ '{0}' bağımsız değişkeni eşleşmiyor + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n Bir 'if' ifadesinin tüm dalları, örtük olarak ilk dalın türüne dönüştürülebilir değerler döndürmelidir. Burada ilk dal {0} uzunluğunda türü\n {1} olan bir demet \nBu dal {2} uzunluğunda türü\n {3} \nolan bir demet döndürüyor @@ -452,6 +457,11 @@ Dizi ifadelerinde try-with desteği + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. Bir kopyalama ve güncelleştirme kayıt ifadesi bir kaydın tüm alanlarını değiştirdiğinde uyarı oluşturur. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 9c40f47609..978497120a 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -167,6 +167,11 @@ 参数 "{0}" 不匹配 + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n “if” 表达式的所有分支必须返回可隐式转换为第一个分支类型的值,这是一个长度为 {0} 的类型的元组\n {1} \n此分支会返回长度为 {2} 的类型的元组\n {3} \n @@ -452,6 +457,11 @@ 支持在序列表达式中试用 + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. 复制和更新记录表达式更改记录的所有字段时引发警告。 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 99294c90c0..770e440ac6 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -167,6 +167,11 @@ 引數 '{0}' 不相符 + + The constraints 'unmanaged' and 'not struct' are inconsistent + The constraints 'unmanaged' and 'not struct' are inconsistent + + All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is a tuple of length {0} of type\n {1} \nThis branch returns a tuple of length {2} of type\n {3} \n 'if' 運算式的所有分支都傳回可隱含轉換為第一個分支的類型的值,這是類型為\n {1} \n的元組長度 {0}此分支傳回的是類型為\n {3} \n的元組長度 {2} @@ -452,6 +457,11 @@ 支援循序運算式中的 try-with + + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + Interop between C#'s and F#'s unmanaged generic constraint (emit additional modreq) + + Raises warnings when an copy-and-update record expression changes all fields of a record. 當複製和更新記錄運算式變更記錄的所有欄位時引發警告。 diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Constraints/Unmanaged.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Constraints/Unmanaged.fs new file mode 100644 index 0000000000..8abe1f6c70 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Constraints/Unmanaged.fs @@ -0,0 +1,550 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance.Constraints + +open Xunit +open FSharp.Test.Compiler +open FSharp.Test + +module Unmanaged = + + [] + let ``Struct with private field can be unmanaged`` () = + Fsx """ +[] +type Test<'T when 'T: unmanaged> = + val element: 'T +type DoubleType<'T> = + struct + val x: float + val private y: 'T + end +let _ = Test>() +let _ = Test>>() + """ + |> typecheck + |> shouldSucceed + + [] + let ``Struct with private managed field cannot be unmanaged`` () = + Fsx """ +[] +type Test<'T when 'T: unmanaged> = + val element: 'T +type DoubleType<'T> = + struct + val x: float + val private y: 'T + end +let _ = Test>() +let _ = Test>>() + """ + |> typecheck + |> shouldFail + |> withDiagnostics [ + Error 1, Line 10, Col 9, Line 10, Col 33, "A generic construct requires that the type 'DoubleType' is an unmanaged type" + Error 1, Line 11, Col 9, Line 11, Col 49, "A generic construct requires that the type 'DoubleType>' is an unmanaged type" ] + + [] + let ``voption considered unmanaged when inner type is unmanaged`` () = + Fsx """ +let test (x: 'T when 'T : unmanaged) = () +test (ValueSome 15) +test (ValueSome (ValueSome 42)) +test (ValueSome (struct {|Field = 42|})) + """ + |> typecheck + |> shouldSucceed + + [] + let ``voption not considered unmanaged when inner type is reference type`` () = + Fsx """ +let test (x: 'T when 'T : unmanaged) = () +test (ValueSome "xxx") +test (ValueSome (ValueSome "xxx")) +test (ValueSome (struct {|Field = Some 42|})) + """ + |> typecheck + |> shouldFail + |> withDiagnostics [ + Error 1, Line 3, Col 17, Line 3, Col 22, "A generic construct requires that the type 'string' is an unmanaged type" + Error 1, Line 4, Col 28, Line 4, Col 33, "A generic construct requires that the type 'string' is an unmanaged type" + Error 1, Line 5, Col 35, Line 5, Col 42, "A generic construct requires that the type ''a option' is an unmanaged type" ] + + [] + let ``Option not considered unmanaged`` () = + Fsx """ +let test (x: 'T when 'T : unmanaged) = () +test (None) +test (Some 42 ) + """ + |> typecheck + |> shouldFail + |> withDiagnostics [ + Error 1, Line 3, Col 7, Line 3, Col 11, "A generic construct requires that the type ''a option' is an unmanaged type" + Error 1, Line 4, Col 7, Line 4, Col 14, "A generic construct requires that the type ''a option' is an unmanaged type" ] + + [] + let ``User-defined struct types considered unmanaged when all members are unmanaged`` () = + Fsx """ +[] +type MyStruct(x: int, y: int) = + member _.X = x + member _.Y = y +[] +type MyStructGeneric<'T when 'T: unmanaged>(x: 'T, y: 'T) = + member _.X = x + member _.Y = y +[] +type MyStructGenericWithNoConstraint<'T>(x: 'T, y: 'T) = + member _.X = x + member _.Y = y +[] +type Test<'T when 'T: unmanaged> = + val element: 'T +[] +type S<'T> = + val X : 'T + new (x) = { X = x } +let test (x: 'T when 'T : unmanaged) = () +test(Unchecked.defaultof>) +test(S(1)) +test(S(MyStruct(1,2))) +test(S>(MyStructGeneric(1,2))) +test(S>(MyStructGenericWithNoConstraint(1,2))) +let _ = Test() +let _ = Test() +let _ = Test>() +let _ = Test>() +let _ = Test>() +let _ = Test>>>() +let _ = Test>() +let _ = Test>>() + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Struct tuples considered unmanaged when all elements are unmanaged`` () = + Fsx """ +[] +type Test<'T when 'T: unmanaged> = + val element: 'T +let test (x: 'T when 'T : unmanaged) = () +let x = struct(1, 2) +test x +test (struct(1, 2)) +test (struct(1, 2, 3)) +test (struct(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, struct(12,13))) + +// test that constraint is being propagated +let functionUsingTestInternally (struct(a,b) as s) = + test s + +functionUsingTestInternally (struct(42,42)) + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``User-defined struct (anonymous)records considered unmanaged when all fields are unmanaged`` () = + Fsx """ +[] +type MyRecd = { X: int; Y: int; } +[] +type MyRecdGeneric<'T when 'T: unmanaged> = { X:'T; Y: 'T; } +[] +type MyRecdGenericWithNoConstraint<'T> = { X:'T; Y: 'T; } +[] +type Test<'T when 'T: unmanaged> = + val element: 'T +[] +type S<'T> = + val X : 'T + new (x) = { X = x } +let test (x: 'T when 'T : unmanaged) = () +test(Unchecked.defaultof>) +test(S(1)) +test(S({ X = 1; Y = 1 })) +test(S>({ X = 1; Y = 1 })) +test(S>({ X = 1; Y = 1 })) +let x = struct {| X = 1; Y = 1 |} +test(x) +test(struct {| X = 1; Y = 1 |}) + +// test that constraint is being propagated +let createAndTestAnonRecd (x) = + let created = struct{|OnlyValue = x|} + test(created) + () + +createAndTestAnonRecd 15 +createAndTestAnonRecd (struct{|Nested = 15|}) +createAndTestAnonRecd (struct{|Nested = struct(15,S>({ X = 1; Y = 1 }))|}) + +let _ = Test() +let _ = Test() +let _ = Test>() +let _ = Test>>>() +let _ = Test>() +let _ = Test>>() + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Struct single- and multi-case unions considered unmanaged when all cases are all unmanaged`` () = + Fsx """ +[] +type SingleCaseUnion = X +[] +type MultiCaseUnion = A | B +[] +type Single<'T> = C of 'T +[] +type SingleC<'T when 'T: unmanaged> = CC of 'T +[] +type Result<'T,'TError> = + | Ok of ok: 'T + | Error of error: 'TError +[] +type ResultC<'T,'TError when 'T: unmanaged and 'TError: unmanaged> = + | OkC of ok: 'T + | ErrorC of error: 'TError +[] +type Test<'T when 'T: unmanaged> = + val element: 'T +[] +type DateOrTimeStampOrUnixOrCustom = + | DuDateTime of dt:System.DateTime + | DuTimeSpan of ts:System.TimeSpan + | DuUnix of i:int +let test (x: 'T when 'T : unmanaged) = () +test(SingleCaseUnion.X) +test(MultiCaseUnion.A) +test(MultiCaseUnion.B) +test(C 1) +test(CC 1) +test(Result.Ok 1) +test(Result.Error 2) +test(ResultC.OkC 1) +test(ResultC.ErrorC 1) +test(DuUnix 132456) +test(DuDateTime System.DateTime.Now) +let _ = Test() +let _ = Test() +let _ = Test>() +let _ = Test>>>() +let _ = Test>>>() +let _ = Test>() +let _ = Test>>() +let _ = Test>() +let _ = Test, Single>>() +let _ = Test>() + +// test that constraint is being propagated +let resultCreatingFunction (x:'a) = + try + let capturedVal = Result<'a,int>.Ok x + test capturedVal + capturedVal + with _ -> Error 15 + +let _ = resultCreatingFunction A + + """ + |> typecheck + |> shouldSucceed + |> ignore + + [] + let ``Typechecker can handle recursive unions passed in and will not stack overflow`` () = + Fsx """ +type RefTree = + | Tip + | Node of i:int * left:RefTree * right:RefTree + +[] +type StructTree = + | Tip + | Node of i:int * left:StructTree * right:StructTree + +[] +type ComboTree = + | Tip + | Node of i:int * left:RefTree * right:RefTree + """ + |> typecheck + |> shouldFail + |> withDiagnostics [Error 954, Line 7, Col 6, Line 7, Col 16, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation"] + + [] + let ``Generic user-defined type with non-unmanaged types is NOT considered unmanaged`` () = + Fsx """ +type NonStructRecd = { X: int } +type NonStructRecdC<'T when 'T : unmanaged> = { X: 'T } +[] +type Test<'T when 'T: unmanaged> = + val element: 'T +[] +type W<'T> = { x: 'T } +[] +type S<'T> = { x: W<'T> } +[] +type X<'T> = + val Z : 'T + new (x) = { Z = x } + +[] +type MyDu<'T1,'T2> = DuA of first:'T1 | DuB of second:'T2 + +[] +type A<'T, 'U> = + [] val X : 'T +let test (x: 'T when 'T : unmanaged) = () +test(Unchecked.defaultof>) +test(X(obj())) +test (A()) +let foo<'T> () = test (A<'T, obj>()) +let _ = Test() +let _ = Test() +let _ = Test>>() + """ + |> typecheck + |> shouldFail + |> withDiagnostics [ + Error 1, Line 23, Col 6, Line 23, Col 33, "A generic construct requires that the type 'S' is an unmanaged type" + Error 193, Line 24, Col 6, Line 24, Col 14, "A generic construct requires that the type 'X<'a>' is an unmanaged type" + Error 193, Line 25, Col 7, Line 25, Col 20, "A generic construct requires that the type 'A' is an unmanaged type" + Error 193, Line 26, Col 24, Line 26, Col 36, "A generic construct requires that the type 'A<'T,obj>' is an unmanaged type" + Error 1, Line 27, Col 9, Line 27, Col 18, "A generic construct requires that the type 'obj' is an unmanaged type" + Error 1, Line 28, Col 9, Line 28, Col 28, "A generic construct requires that the type 'NonStructRecd' is an unmanaged type" + Error 1, Line 29, Col 9, Line 29, Col 49, "A generic construct requires that the type 'string' is an unmanaged type" ] + + [] + let ``Disallow both 'unmanaged' and 'not struct' constraints`` () = + Fsx "type X<'T when 'T: unmanaged and 'T: not struct> = class end" + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 43, Line 1, Col 34, Line 1, Col 48, "The constraints 'unmanaged' and 'not struct' are inconsistent")] + + [] + let ``Multi constraint IL test together with struct and interface constraints`` () = + Fsx "[] type Test<'T when 'T: unmanaged and 'T: struct and 'T:>System.IComparable> = struct end" + |> withLangVersionPreview + |> compile + |> shouldSucceed + |> verifyIL [""" + .class public abstract auto ansi sealed Test + extends [runtime]System.Object + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .class sequential ansi serializable sealed nested public beforefieldinit Test`1 + extends [runtime]System.ValueType + { + .pack 0 + .size 1 + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoEqualityAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoComparisonAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .param type T + .custom instance void System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = ( 01 00 00 00 ) + } + + } """;""" +.class private auto ansi beforefieldinit System.Runtime.CompilerServices.IsUnmanagedAttribute + extends [runtime]System.Attribute"""] + + [] + let ``IsUnmanagedAttribute Attribute is emitted for function with unmanaged constraint`` () = + Fsx "let testMyFunction (x: 'TUnmanaged when 'TUnmanaged : unmanaged) = struct(x,1)" + |> withLangVersionPreview + |> compile + |> shouldSucceed + |> verifyIL [""" + .method public static valuetype [runtime]System.ValueTuple`2 + testMyFunction(!!TUnmanaged x) cil managed + { + .param type TUnmanaged + .custom instance void System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: newobj instance void valuetype [runtime]System.ValueTuple`2::.ctor(!0, + !1) + IL_0007: ret + }"""] + + + + [] + let ``Consume C#-defined unmanaged constraint incorrectly in F# - report error`` () = + let csLib = + CSharp "namespace CsLib{ public record struct CsharpStruct(T item) where T:unmanaged;}" + |> withCSharpLanguageVersion CSharpLanguageVersion.Preview + |> withName "csLib" + + let app = FSharp """module MyFsharpApp +open CsLib +let y = new CsharpStruct(struct(1,"this is string")) + """ |> withReferences [csLib] + |> withLangVersionPreview + + app + |> compile + |> shouldFail + |> withDiagnostics [(Error 1, Line 3, Col 13, Line 3, Col 45, "A generic construct requires that the type 'string' is an unmanaged type")] + + [] + let ``F# can consume C#-defined unmanaged constraint and call method with modreq`` () = + let csLib = + CSharp """ +namespace CsLib +{ + public record struct CsharpStruct(T item) where T:unmanaged + { + public static string Hi() where TOther:unmanaged + { + return typeof(TOther).Name; + }}}""" + |> withCSharpLanguageVersion CSharpLanguageVersion.Preview + |> withName "csLib" + + let app = FSharp """module MyFsharpApp +open CsLib +[] +type MultiCaseUnion = A | B of i:int +let _ = new CsharpStruct(B 42) +printf "%s" (CsharpStruct.Hi()) + """ |> withReferences [csLib] + + app + |> withLangVersionPreview + |> asExe + |> compile + |> run + |> verifyOutput "MultiCaseUnion" + + [] + let ``FSharp generates modreq for CSharp to consume in preview`` () = + Fsx "let testMyFunction (x: 'TUnmanaged when 'TUnmanaged : unmanaged) = ()" + |> withLangVersionPreview + |> compile + |> shouldSucceed + |> verifyIL [""" + .method public static void testMyFunction(!!TUnmanaged x) cil managed + { + .param type TUnmanaged + .custom instance void System.Runtime.CompilerServices.IsUnmanagedAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ret + } """] + + [] + let ``FSharp does not generate modreq for VBNET to consume in v7`` () = + Fsx "let testMyFunction (x: 'TUnmanaged when 'TUnmanaged : unmanaged) = ()" + |> withLangVersion70 + |> compile + |> shouldSucceed + |> verifyIL [""" + .method public static void testMyFunction(!!TUnmanaged x) cil managed + { + + .maxstack 8 + IL_0000: ret + } """] + + + [] + let ``C# can consume F#-defined struct with unmanaged constraint - valid`` () = + let fsharpLib = + Fs """namespace MyFsLib +[] +type FsharpStructWrapper<'TInner when 'TInner: unmanaged> = + val Item : 'TInner + with static member Hi() = typeof<'TInner>.Name""" + |> asLibrary + |> withLangVersionPreview + |> withName "fsLib" + + let app = + CSharp """ + using System; + using MyFsLib; + public record struct CSharpStruct(int first, long second); + public class C { + public static void Main() { + var text = FsharpStructWrapper.Hi(); + Console.Write(text); + } + } + """ + |> withReferences [fsharpLib] + |> withCSharpLanguageVersion CSharpLanguageVersion.Preview + |> asExe + |> withName "myCsharpApp" + + app + |> compile + |> run + |> verifyOutput "CSharpStruct" + + [] + let ``C# can consume F#-defined struct with unmanaged constraint - and report error when invalid`` () = + let fsharpLib = + Fs """namespace MyFsLib +[] +type FsharpStructWrapper<'T when 'T: unmanaged> = + val Item : 'T + with static member Hi() = typeof<'T>.Name + +[] +type MultiDu = A | B of s:string + +module FsharpFunc = + let test (x: 'T when 'T : unmanaged) = () + +module FsharpPreparedData = + let structTuple = struct(1,2,"42") + let structDuExample1 = A + let structDuExample2 = B "42" + """ + |> asLibrary + |> withLangVersionPreview + |> withName "fsLib" + + let app = + CSharp """ + using System; + using MyFsLib; + public record struct CSharpStructWithString(int first, string second); + public class C { + public static void Main() { + var text = FsharpStructWrapper.Hi(); + FsharpFunc.test(FsharpPreparedData.structTuple); + FsharpFunc.test(FsharpPreparedData.structDuExample1); + FsharpFunc.test(FsharpPreparedData.structDuExample2); + Console.WriteLine(text); + } + } + """ + |> withReferences [fsharpLib] + |> withCSharpLanguageVersion CSharpLanguageVersion.Preview + |> asExe + |> withName "myCsharpApp" + + app + |> compile + |> shouldFail + |> withDiagnostics [ + Error 8377, Line 6, Col 44, Line 6, Col 66, "The type 'CSharpStructWithString' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'FsharpStructWrapper'" + Error 8377, Line 7, Col 24, Line 7, Col 28, "The type '(int, int, string)' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'FsharpFunc.test(T)'" + Error 8377, Line 8, Col 24, Line 8, Col 28, "The type 'MultiDu' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'FsharpFunc.test(T)'" + Error 8377, Line 9, Col 24, Line 9, Col 28, "The type 'MultiDu' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'FsharpFunc.test(T)'" ] \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 9a9c99c347..c279c9d4d7 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -24,7 +24,7 @@ FsUnit.fs - + @@ -66,6 +66,7 @@ + diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index f9af77086d..ac593abf49 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -290,6 +290,9 @@ module rec CompilerAssertHelpers = AppDomain.CurrentDomain.add_AssemblyResolve(ResolveEventHandler(fun _ args -> deps |> Array.tryFind (fun (x: string) -> Path.GetFileNameWithoutExtension x = args.Name) + |> Option.orElseWith (fun () -> + deps + |> Array.tryFind (fun (x: string) -> args.Name.StartsWith(Path.GetFileNameWithoutExtension(x) + ", Version"))) |> Option.bind (fun x -> if FileSystem.FileExistsShim x then Some x else None) |> Option.map Assembly.LoadFile |> Option.defaultValue null)) diff --git a/tests/fsharp/typecheck/sigs/neg30.bsl b/tests/fsharp/typecheck/sigs/neg30.bsl index b958db2e36..b57ca6bcb8 100644 --- a/tests/fsharp/typecheck/sigs/neg30.bsl +++ b/tests/fsharp/typecheck/sigs/neg30.bsl @@ -11,8 +11,6 @@ neg30.fs(46,7,46,26): typecheck error FS0001: A generic construct requires that neg30.fs(47,6,47,8): typecheck error FS0001: A generic construct requires that the type 'FSharpUnion' is an unmanaged type -neg30.fs(48,7,48,26): typecheck error FS0001: A generic construct requires that the type 'SGeneric' is an unmanaged type - neg30.fs(59,13,59,24): typecheck error FS0001: A generic construct requires that the type 'SManaged' is an unmanaged type neg30.fs(60,13,60,33): typecheck error FS0001: A generic construct requires that the type 'SManagedRecursive' is an unmanaged type @@ -23,8 +21,6 @@ neg30.fs(62,13,62,29): typecheck error FS0001: A generic construct requires that neg30.fs(63,13,63,27): typecheck error FS0001: A generic construct requires that the type 'FSharpUnion' is an unmanaged type -neg30.fs(64,13,64,29): typecheck error FS0001: A generic construct requires that the type 'SGeneric' is an unmanaged type - neg30.fs(71,12,71,35): typecheck error FS0120: hello! neg30.fs(71,12,71,35): typecheck error FS0120: hello! diff --git a/tests/fsharpqa/Source/Conformance/TypesAndTypeConstraints/CheckingSyntacticTypes/E_UnmanagedConstraint01.fs b/tests/fsharpqa/Source/Conformance/TypesAndTypeConstraints/CheckingSyntacticTypes/E_UnmanagedConstraint01.fs index a913ce9a79..ccbb5da258 100644 --- a/tests/fsharpqa/Source/Conformance/TypesAndTypeConstraints/CheckingSyntacticTypes/E_UnmanagedConstraint01.fs +++ b/tests/fsharpqa/Source/Conformance/TypesAndTypeConstraints/CheckingSyntacticTypes/E_UnmanagedConstraint01.fs @@ -1,8 +1,7 @@ // #Conformance #TypeConstraints // Simple baselines for unmanaged constraint -//A generic construct requires that the type ''a S' is an unmanaged type -//A generic construct requires that the type 'D' is an unmanaged type -//A generic construct requires that the type 'C' is an unmanaged type +//A generic construct requires that the type 'D' is an unmanaged type +//A generic construct requires that the type 'C' is an unmanaged type let testFunc<'a when 'a:unmanaged> (x : 'a) = let z = x