Skip to content

Commit d997d5f

Browse files
tests: add GenerativePropertiesTests; prepare release 8.6.0
- Add GenerativePropertiesTests.fs: 5 new tests covering instance read-only, read-write, static, multi-property name-lookup (exercises ILPropertyDefs lazy FindByName dictionary from #502), and property count on a generative type - 136/136 tests pass - Prepare RELEASE_NOTES.md for 8.6.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 75ac611 commit d997d5f

3 files changed

Lines changed: 154 additions & 0 deletions

File tree

RELEASE_NOTES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#### 8.6.0 - April 15, 2026
2+
3+
- Bug fix: Fix `ProvidedTypeDefinition.Logger` creating a new delegate reference on each call #501
4+
- Refactor/Performance: Convert `ILFieldDefs`/`ILEventDefs`/`ILPropertyDefs` to concrete classes with lazy `O(1)` name-lookup caches #502
5+
- Tests: Add `GenerativePropertiesTests` covering instance/static/read-write properties and name-lookup
6+
17
#### 8.5.0 - April 7, 2026
28

39
- Performance: O(1) assembly-name dictionary lookup in `convTypeRef` #493

tests/FSharp.TypeProviders.SDK.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<Compile Include="GenerativeEqualityComparisonTests.fs" />
2222
<Compile Include="GenerativeStructProvisionTests.fs" />
2323
<Compile Include="GenerativeEventsTests.fs" />
24+
<Compile Include="GenerativePropertiesTests.fs" />
2425
<Compile Include="GenerativeDelegateTests.fs" />
2526
<Compile Include="ReferencedAssemblies.fs" />
2627
</ItemGroup>

tests/GenerativePropertiesTests.fs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
module TPSDK.GenerativePropertiesTests
2+
3+
#nowarn "760" // IDisposable needs new
4+
5+
open System
6+
open System.Reflection
7+
open Microsoft.FSharp.Core.CompilerServices
8+
open Microsoft.FSharp.Quotations
9+
open Xunit
10+
open ProviderImplementation.ProvidedTypes
11+
open ProviderImplementation.ProvidedTypesTesting
12+
open UncheckedQuotations
13+
14+
/// Generative type provider with a variety of property kinds:
15+
/// - instance read-only property
16+
/// - instance read-write property
17+
/// - static read-only property
18+
/// - multiple properties on the same nested type (exercises ILPropertyDefs lazy name-lookup cache)
19+
[<TypeProvider>]
20+
type GenerativePropertiesProvider (config: TypeProviderConfig) as this =
21+
inherit TypeProviderForNamespaces (config)
22+
23+
let ns = "Properties.Provided"
24+
let tempAssembly = ProvidedAssembly()
25+
let container = ProvidedTypeDefinition(tempAssembly, ns, "Container", Some typeof<obj>, isErased = false)
26+
27+
do
28+
let widgetType = ProvidedTypeDefinition("Widget", Some typeof<obj>, isErased = false)
29+
30+
// A backing field for the read-write property
31+
let labelField = ProvidedField("_label", typeof<string>)
32+
widgetType.AddMember labelField
33+
34+
// Instance read-only property: returns a constant
35+
let readOnlyProp =
36+
ProvidedProperty(
37+
"Kind", typeof<string>,
38+
isStatic = false,
39+
getterCode = fun _args -> <@@ "widget" @@>)
40+
widgetType.AddMember readOnlyProp
41+
42+
// Instance read-write property backed by _label field
43+
let readWriteProp =
44+
ProvidedProperty(
45+
"Label", typeof<string>,
46+
isStatic = false,
47+
getterCode = (fun args -> Expr.FieldGetUnchecked(args.[0], labelField)),
48+
setterCode = (fun args -> Expr.FieldSetUnchecked(args.[0], labelField, args.[1])))
49+
widgetType.AddMember readWriteProp
50+
51+
// Static read-only property
52+
let staticProp =
53+
ProvidedProperty(
54+
"DefaultKind", typeof<string>,
55+
isStatic = true,
56+
getterCode = fun _args -> <@@ "default-widget" @@>)
57+
widgetType.AddMember staticProp
58+
59+
// A second property to verify the name-lookup dictionary handles multiple entries
60+
let countProp =
61+
ProvidedProperty(
62+
"Index", typeof<int>,
63+
isStatic = false,
64+
getterCode = fun _args -> <@@ 42 @@>)
65+
widgetType.AddMember countProp
66+
67+
widgetType.AddMember (ProvidedConstructor([], invokeCode = fun _args -> <@@ () @@>))
68+
container.AddMember widgetType
69+
tempAssembly.AddTypes [container]
70+
this.AddNamespace(ns, [container])
71+
72+
let loadTestAssembly () =
73+
let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs()
74+
let runtimeAssembly = runtimeAssemblyRefs.[0]
75+
let cfg = Testing.MakeSimulatedTypeProviderConfig (__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs)
76+
let tp = GenerativePropertiesProvider(cfg) :> TypeProviderForNamespaces
77+
let providedNamespace = tp.Namespaces.[0]
78+
let providedType = providedNamespace.GetTypes().[0] :?> ProvidedTypeDefinition
79+
Assert.Equal("Container", providedType.Name)
80+
let bytes = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedType.Assembly)
81+
Assembly.Load bytes
82+
83+
[<Fact>]
84+
let ``Instance read-only property is present in generated assembly``() =
85+
let assembly = loadTestAssembly ()
86+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
87+
let widgetType = containerType.GetNestedType("Widget")
88+
Assert.NotNull(widgetType)
89+
90+
let prop = widgetType.GetProperty("Kind", BindingFlags.Instance ||| BindingFlags.Public)
91+
Assert.NotNull(prop)
92+
Assert.Equal(typeof<string>, prop.PropertyType)
93+
Assert.True(prop.CanRead, "Kind should be readable")
94+
Assert.False(prop.CanWrite, "Kind should not be writable")
95+
Assert.False(prop.GetMethod.IsStatic, "Kind getter should be an instance method")
96+
97+
[<Fact>]
98+
let ``Instance read-write property is present and has getter and setter``() =
99+
let assembly = loadTestAssembly ()
100+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
101+
let widgetType = containerType.GetNestedType("Widget")
102+
Assert.NotNull(widgetType)
103+
104+
let prop = widgetType.GetProperty("Label", BindingFlags.Instance ||| BindingFlags.Public)
105+
Assert.NotNull(prop)
106+
Assert.Equal(typeof<string>, prop.PropertyType)
107+
Assert.True(prop.CanRead, "Label should be readable")
108+
Assert.True(prop.CanWrite, "Label should be writable")
109+
Assert.Equal("get_Label", prop.GetMethod.Name)
110+
Assert.Equal("set_Label", prop.SetMethod.Name)
111+
112+
[<Fact>]
113+
let ``Static read-only property is present in generated assembly``() =
114+
let assembly = loadTestAssembly ()
115+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
116+
let widgetType = containerType.GetNestedType("Widget")
117+
Assert.NotNull(widgetType)
118+
119+
let prop = widgetType.GetProperty("DefaultKind", BindingFlags.Static ||| BindingFlags.Public)
120+
Assert.NotNull(prop)
121+
Assert.Equal(typeof<string>, prop.PropertyType)
122+
Assert.True(prop.CanRead, "DefaultKind should be readable")
123+
Assert.True(prop.GetMethod.IsStatic, "DefaultKind getter should be static")
124+
125+
[<Fact>]
126+
let ``All expected properties are found by name (exercises ILPropertyDefs lazy name-lookup cache)``() =
127+
let assembly = loadTestAssembly ()
128+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
129+
let widgetType = containerType.GetNestedType("Widget")
130+
Assert.NotNull(widgetType)
131+
132+
let allFlags = BindingFlags.Instance ||| BindingFlags.Static ||| BindingFlags.Public
133+
// Each property must be findable by exact name (uses FindByName dictionary)
134+
for name in ["Kind"; "Label"; "DefaultKind"; "Index"] do
135+
let prop = widgetType.GetProperty(name, allFlags)
136+
Assert.NotNull(prop)
137+
Assert.Equal(name, prop.Name)
138+
139+
[<Fact>]
140+
let ``Widget type has expected number of properties``() =
141+
let assembly = loadTestAssembly ()
142+
let containerType = assembly.ExportedTypes |> Seq.find (fun t -> t.Name = "Container")
143+
let widgetType = containerType.GetNestedType("Widget")
144+
Assert.NotNull(widgetType)
145+
146+
let allProps = widgetType.GetProperties(BindingFlags.Instance ||| BindingFlags.Static ||| BindingFlags.Public)
147+
Assert.Equal(4, allProps.Length)

0 commit comments

Comments
 (0)