Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/ProvidedTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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


//====================================================================================================
Expand Down
57 changes: 57 additions & 0 deletions tests/BasicErasedProvisionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
// ---------------------------------------------------------------------------

[<Fact>]
let ``DefineStaticParameters warns when all static parameters have defaults``() =
let warnings = System.Collections.Generic.List<string>()
let prevLogger = !ProvidedTypeDefinition.Logger
ProvidedTypeDefinition.Logger := Some warnings.Add
try
let asm = Assembly.GetExecutingAssembly()
let t = ProvidedTypeDefinition(asm, "Test.Warning", "AllOptionalType", Some typeof<obj>)
warnings.Clear() // discard the "Creating..." trace message from the ctor
let p1 = ProvidedStaticParameter("X", typeof<int>, 0)
let p2 = ProvidedStaticParameter("Y", typeof<string>, "default")
t.DefineStaticParameters([p1; p2], fun typeName _args ->
ProvidedTypeDefinition(asm, "Test.Warning", typeName, Some typeof<obj>))
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

[<Fact>]
let ``DefineStaticParameters does not warn when at least one parameter has no default``() =
let warnings = System.Collections.Generic.List<string>()
let prevLogger = !ProvidedTypeDefinition.Logger
ProvidedTypeDefinition.Logger := Some warnings.Add
try
let asm = Assembly.GetExecutingAssembly()
let t = ProvidedTypeDefinition(asm, "Test.Warning", "MixedParamsType", Some typeof<obj>)
warnings.Clear()
let required = ProvidedStaticParameter("Required", typeof<int>)
let optional = ProvidedStaticParameter("Optional", typeof<string>, "default")
t.DefineStaticParameters([required; optional], fun typeName _args ->
ProvidedTypeDefinition(asm, "Test.Warning", typeName, Some typeof<obj>))
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

[<Fact>]
let ``DefineStaticParameters does not warn when there are no static parameters``() =
let warnings = System.Collections.Generic.List<string>()
let prevLogger = !ProvidedTypeDefinition.Logger
ProvidedTypeDefinition.Logger := Some warnings.Add
try
let asm = Assembly.GetExecutingAssembly()
let t = ProvidedTypeDefinition(asm, "Test.Warning", "NoParamsType", Some typeof<obj>)
warnings.Clear()
t.DefineStaticParameters([], fun typeName _args ->
ProvidedTypeDefinition(asm, "Test.Warning", typeName, Some typeof<obj>))
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
44 changes: 44 additions & 0 deletions tests/GenerativeEnumsProvisionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)

[<Fact>]
let ``Generative enum with byte underlying type is generated correctly``() =
let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs()
Expand Down Expand Up @@ -185,3 +191,41 @@ let ``Generative enum with int64 underlying type is generated correctly``() =
let values = Enum.GetValues(int64Enum) |> Seq.cast<int64> |> 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.
// ---------------------------------------------------------------------------

[<Fact>]
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)

[<Fact>]
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)
Loading