diff --git a/src/ProvidedTypes.fs b/src/ProvidedTypes.fs index a6396a4..9c38cf3 100644 --- a/src/ProvidedTypes.fs +++ b/src/ProvidedTypes.fs @@ -1390,6 +1390,9 @@ and ProvidedTypeDefinition(isTgt: bool, container:TypeContainer, className: stri | TypeContainer.Namespace _, Some logger when not isTgt -> logger (sprintf "Creating ProvidedTypeDefinition %s [%d]" className (System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode this)) | _ -> () + // Shared mutable logger cell; must be a static let so the same ref is returned on every access. + static let loggerRef: (string -> unit) option ref = ref None + static let defaultAttributes (isErased, isSealed, isInterface, isAbstract, isStruct) = TypeAttributes.Public ||| (if isInterface then TypeAttributes.Interface ||| TypeAttributes.Abstract @@ -1940,7 +1943,7 @@ and ProvidedTypeDefinition(isTgt: bool, container:TypeContainer, className: stri | :? ProvidedField as l -> l.PatchDeclaringType this | _ -> () - static member Logger: (string -> unit) option ref = ref None + static member Logger: (string -> unit) option ref = loggerRef //==================================================================================================== diff --git a/tests/BasicErasedProvisionTests.fs b/tests/BasicErasedProvisionTests.fs index 684ac29..9038d52 100644 --- a/tests/BasicErasedProvisionTests.fs +++ b/tests/BasicErasedProvisionTests.fs @@ -811,3 +811,60 @@ let ``GetUnionCases works on option of provided type`` () = Assert.Equal(2, cases.Length) Assert.Equal("None", cases.[0].Name) Assert.Equal("Some", cases.[1].Name) + +// --------------------------------------------------------------------------- +// Tests for DefineStaticParameters warning: all optional parameters +// Addresses PR #428 — warn when every static param has a default value. +// --------------------------------------------------------------------------- + +[] +let ``DefineStaticParameters warns when all static parameters have defaults``() = + let warnings = System.Collections.Generic.List() + let prevLogger = !ProvidedTypeDefinition.Logger + ProvidedTypeDefinition.Logger := Some warnings.Add + try + let asm = Assembly.GetExecutingAssembly() + let t = ProvidedTypeDefinition(asm, "Test.Warning", "AllOptionalType", Some typeof) + warnings.Clear() // discard the "Creating..." trace message from the ctor + let p1 = ProvidedStaticParameter("X", typeof, 0) + let p2 = ProvidedStaticParameter("Y", typeof, "default") + t.DefineStaticParameters([p1; p2], fun typeName _args -> + ProvidedTypeDefinition(asm, "Test.Warning", typeName, Some typeof)) + let w = warnings |> Seq.tryFind (fun msg -> msg.Contains("AllOptionalType") && msg.Contains("optional")) + Assert.True(w.IsSome, sprintf "Expected all-optional-params warning, got: %A" (warnings |> Seq.toList)) + finally + ProvidedTypeDefinition.Logger := prevLogger + +[] +let ``DefineStaticParameters does not warn when at least one parameter has no default``() = + let warnings = System.Collections.Generic.List() + let prevLogger = !ProvidedTypeDefinition.Logger + ProvidedTypeDefinition.Logger := Some warnings.Add + try + let asm = Assembly.GetExecutingAssembly() + let t = ProvidedTypeDefinition(asm, "Test.Warning", "MixedParamsType", Some typeof) + warnings.Clear() + let required = ProvidedStaticParameter("Required", typeof) + let optional = ProvidedStaticParameter("Optional", typeof, "default") + t.DefineStaticParameters([required; optional], fun typeName _args -> + ProvidedTypeDefinition(asm, "Test.Warning", typeName, Some typeof)) + let w = warnings |> Seq.tryFind (fun msg -> msg.Contains("MixedParamsType") && msg.Contains("optional")) + Assert.True(w.IsNone, sprintf "No all-optional warning expected for mixed params, got: %A" (warnings |> Seq.toList)) + finally + ProvidedTypeDefinition.Logger := prevLogger + +[] +let ``DefineStaticParameters does not warn when there are no static parameters``() = + let warnings = System.Collections.Generic.List() + let prevLogger = !ProvidedTypeDefinition.Logger + ProvidedTypeDefinition.Logger := Some warnings.Add + try + let asm = Assembly.GetExecutingAssembly() + let t = ProvidedTypeDefinition(asm, "Test.Warning", "NoParamsType", Some typeof) + warnings.Clear() + t.DefineStaticParameters([], fun typeName _args -> + ProvidedTypeDefinition(asm, "Test.Warning", typeName, Some typeof)) + let w = warnings |> Seq.tryFind (fun msg -> msg.Contains("NoParamsType") && msg.Contains("optional")) + Assert.True(w.IsNone, "No all-optional warning expected for empty parameter list") + finally + ProvidedTypeDefinition.Logger := prevLogger diff --git a/tests/GenerativeEnumsProvisionTests.fs b/tests/GenerativeEnumsProvisionTests.fs index 4472104..6245fa6 100644 --- a/tests/GenerativeEnumsProvisionTests.fs +++ b/tests/GenerativeEnumsProvisionTests.fs @@ -145,6 +145,12 @@ let makeAssembly (tp: TypeProviderForNamespaces) = let assemContents = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedType.Assembly) Assembly.Load assemContents +let makeTargetAssembly (tp: TypeProviderForNamespaces) = + let ns = tp.Namespaces.[0] + let providedType = ns.GetTypes().[0] + let assemContents = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedType.Assembly) + tp.TargetContext.ReadRelatedAssembly(assemContents) + [] let ``Generative enum with byte underlying type is generated correctly``() = let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs() @@ -185,3 +191,41 @@ let ``Generative enum with int64 underlying type is generated correctly``() = let values = Enum.GetValues(int64Enum) |> Seq.cast |> Seq.zip (Enum.GetNames(int64Enum)) |> Seq.toList Assert.Equal<(string * int64) list>([("Zero", 0L); ("One", 1L); ("BigVal", 3000000000L)], values) +// --------------------------------------------------------------------------- +// Tests that read enums back via TargetContext.ReadRelatedAssembly (the IL binary +// reader path through TargetTypeDefinition), exercising GetEnumUnderlyingType() +// on non-Int32 enum types via the target context. +// --------------------------------------------------------------------------- + +[] +let ``Byte enum underlying type is correct when read via target context (ReadRelatedAssembly)``() = + let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs() + let runtimeAssembly = runtimeAssemblyRefs.[0] + let cfg = Testing.MakeSimulatedTypeProviderConfig (__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs) + let tp = GenerativeByteEnumsProvider(cfg) :> TypeProviderForNamespaces + let targetAssem = makeTargetAssembly tp + + let container = targetAssem.GetType("ByteEnums.Provided.Container") + Assert.NotNull(container) + + let byteEnum = container.GetNestedType("ByteEnum", BindingFlags.Public ||| BindingFlags.NonPublic) + Assert.NotNull(byteEnum) + Assert.True(byteEnum.IsEnum, "ByteEnum should be recognised as an enum via target context") + // GetEnumUnderlyingType on a TargetTypeDefinition reads the "value__" field type from IL. + Assert.Equal("System.Byte", byteEnum.GetEnumUnderlyingType().FullName) + +[] +let ``Int64 enum underlying type is correct when read via target context (ReadRelatedAssembly)``() = + let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs() + let runtimeAssembly = runtimeAssemblyRefs.[0] + let cfg = Testing.MakeSimulatedTypeProviderConfig (__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs) + let tp = GenerativeInt64EnumsProvider(cfg) :> TypeProviderForNamespaces + let targetAssem = makeTargetAssembly tp + + let container = targetAssem.GetType("Int64Enums.Provided.Container") + Assert.NotNull(container) + + let int64Enum = container.GetNestedType("Int64Enum", BindingFlags.Public ||| BindingFlags.NonPublic) + Assert.NotNull(int64Enum) + Assert.True(int64Enum.IsEnum, "Int64Enum should be recognised as an enum via target context") + Assert.Equal("System.Int64", int64Enum.GetEnumUnderlyingType().FullName)