Skip to content

Commit a01af9c

Browse files
[Repo Assist] tests: add GenerativeNestedTypesTests; refactor: use canBindNestedType in ProvidedTypeDefinition; prepare release 8.9.0 (#508)
🤖 *This PR was created by Repo Assist, an automated AI assistant.* ## Summary Two improvements bundled together (Tasks 9/5): ### Task 10 — Forward Progress: `GenerativeNestedTypesTests.fs` Adds 5 focused tests for generative type providers that host nested provided types: | Test | What it verifies | |---|---| | `Book and Shelf are present` | Both nested types are emitted into the generated assembly | | `Book has Title property and GetSummary method` | Member presence and return types | | `Book constructor sets fields; GetSummary returns expected string` | Constructor wires fields; instance method produces correct result | | `Shelf Label property reflects constructor argument` | Constructor + property round-trip | | `Shelf static property MaxItems returns 100` | Static property on a nested type | The provider sets up a `Library` container with two nested classes (`Book`, `Shelf`), each with fields, constructors, instance/static properties, and instance methods. ### Task 5 — Coding Improvement: `canBindNestedType` in `ProvidedTypeDefinition` `GetNestedTypes` and `GetMembers` on `ProvidedTypeDefinition` previously filtered nested types using the inconsistent condition: ```fsharp memberBinds true bindingFlags false m.IsPublic || m.IsNestedPublic ``` Because `|| m.IsNestedPublic` is appended outside the `memberBinds` call, a nested public type (`IsNestedPublic = true`) was always included regardless of `BindingFlags` — e.g. even when only `BindingFlags.NonPublic` was set. The `canBindNestedType` utility (already used in `TypeSymbol.GetNestedTypes` and `TargetTypeDefinition.GetNestedTypes`) correctly handles this: ````fsharp hasFlag bindingFlags BindingFlags.Public && c.IsNestedPublic || hasFlag bindingFlags BindingFlags.NonPublic && not c.IsNestedPublic ``` This PR replaces the two inconsistent uses with `canBindNestedType`, making the behaviour consistent and correct. ## Test Status ``` Passed! - Failed: 0, Passed: 157, Skipped: 0, Total: 157, Duration: 7 s ```` All 157 tests pass (152 before + 5 new `GenerativeNestedTypesTests`). > Generated by 🌈 Repo Assist, see [workflow run](https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/24810212439). [Learn more](https://github.com/githubnext/agentics/blob/main/docs/repo-assist.md). > > To install this [agentic workflow](https://github.com/githubnext/agentics/blob/96b9d4c39aa22359c0b38265927eadb31dcf4e2a/workflows/repo-assist.md), run > ``` > gh aw add githubnext/agentics@96b9d4c > ``` <!-- gh-aw-agentic-workflow: Repo Assist, engine: copilot, model: auto, id: 24810212439, workflow_id: repo-assist, run: https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/24810212439 --> <!-- gh-aw-workflow-id: repo-assist --> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent cab9948 commit a01af9c

4 files changed

Lines changed: 180 additions & 2 deletions

File tree

RELEASE_NOTES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#### 8.9.0 - April 23, 2026
2+
3+
- Tests: Add `GenerativeNestedTypesTests` — 5 tests for generative nested types: constructors, instance/static properties, instance methods inside nested classes
4+
- Refactor: `GetNestedTypes`/`GetMembers` on `ProvidedTypeDefinition` now use `canBindNestedType` for consistent BindingFlags-aware filtering of nested types
5+
16
#### 8.8.0 - April 21, 2026
27

38
- Tests: Add `GenerativeInheritanceTests` — 5 tests for generative type inheritance: abstract base class, concrete derived classes, virtual method override dispatch verified at runtime

src/ProvidedTypes.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,7 +1613,7 @@ and ProvidedTypeDefinition(isTgt: bool, container:TypeContainer, className: stri
16131613
override __.GetNestedTypes bindingFlags =
16141614
(//save ("nested", bindingFlags, None) (fun () ->
16151615
getMembers()
1616-
|> Array.choose (function :? Type as m when memberBinds true bindingFlags false m.IsPublic || m.IsNestedPublic -> Some m | _ -> None)
1616+
|> Array.choose (function :? Type as m when canBindNestedType bindingFlags m -> Some m | _ -> None)
16171617
|> (if hasFlag bindingFlags BindingFlags.DeclaredOnly || isNull this.BaseType then id else (fun mems -> Array.append mems (this.ErasedBaseType.GetNestedTypes(bindingFlags)))))
16181618

16191619
override this.GetConstructorImpl(bindingFlags, _binder, _callConventions, _types, _modifiers) =
@@ -1687,7 +1687,7 @@ and ProvidedTypeDefinition(isTgt: bool, container:TypeContainer, className: stri
16871687
| :? FieldInfo as m when memberBinds false bindingFlags m.IsStatic m.IsPublic -> yield (m :> _)
16881688
| :? PropertyInfo as m when memberBinds false bindingFlags m.IsStatic m.IsPublic -> yield (m :> _)
16891689
| :? EventInfo as m when memberBinds false bindingFlags m.IsStatic m.IsPublic -> yield (m :> _)
1690-
| :? Type as m when memberBinds true bindingFlags false m.IsPublic || m.IsNestedPublic -> yield (m :> _)
1690+
| :? Type as m when canBindNestedType bindingFlags m -> yield (m :> _)
16911691
| _ -> () |]
16921692

16931693
override this.GetMember(name, mt, _bindingFlags) =

tests/FSharp.TypeProviders.SDK.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<Compile Include="GenerativeMethodsTests.fs" />
2727
<Compile Include="GenerativeCustomAttributeTests.fs" />
2828
<Compile Include="GenerativeInheritanceTests.fs" />
29+
<Compile Include="GenerativeNestedTypesTests.fs" />
2930
<Compile Include="ReferencedAssemblies.fs" />
3031
</ItemGroup>
3132
<ItemGroup>
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
module TPSDK.GenerativeNestedTypesTests
2+
3+
// Tests for generative nested types: one ProvidedTypeDefinition hosting other
4+
// ProvidedTypeDefinitions as nested (inner) types.
5+
//
6+
// Scenario:
7+
// class Library
8+
// nested class Book { Title: string; Author: string; ctor(t,a); GetSummary(): string }
9+
// nested class Shelf { Label: string; ctor(label); MaxItems: int (static) }
10+
//
11+
// Tests verify that nested types are emitted correctly, that their constructors,
12+
// fields, instance methods, and static members are all accessible at runtime.
13+
14+
#nowarn "760" // IDisposable needs new
15+
16+
open System
17+
open System.Reflection
18+
open Microsoft.FSharp.Core.CompilerServices
19+
open Microsoft.FSharp.Quotations
20+
open Xunit
21+
open ProviderImplementation.ProvidedTypes
22+
open ProviderImplementation.ProvidedTypesTesting
23+
open UncheckedQuotations
24+
25+
[<TypeProvider>]
26+
type GenerativeNestedProvider (config: TypeProviderConfig) as this =
27+
inherit TypeProviderForNamespaces (config)
28+
29+
let ns = "Nested.Provided"
30+
let tempAssembly = ProvidedAssembly()
31+
let container = ProvidedTypeDefinition(tempAssembly, ns, "Library", Some typeof<obj>, isErased = false)
32+
33+
do
34+
// ── nested class: Book ───────────────────────────────────────────────
35+
let bookType = ProvidedTypeDefinition("Book", Some typeof<obj>, isErased = false)
36+
37+
let titleField = ProvidedField("_title", typeof<string>)
38+
let authorField = ProvidedField("_author", typeof<string>)
39+
bookType.AddMember titleField
40+
bookType.AddMember authorField
41+
42+
// constructor: Book(title: string, author: string)
43+
bookType.AddMember
44+
(ProvidedConstructor(
45+
[ ProvidedParameter("title", typeof<string>)
46+
ProvidedParameter("author", typeof<string>) ],
47+
invokeCode = fun args ->
48+
Expr.Sequential(
49+
Expr.FieldSetUnchecked(args.[0], titleField, args.[1]),
50+
Expr.FieldSetUnchecked(args.[0], authorField, args.[2]))))
51+
52+
// property: Title : string
53+
let titleProp =
54+
ProvidedProperty("Title", typeof<string>,
55+
getterCode = fun args -> Expr.FieldGetUnchecked(args.[0], titleField))
56+
bookType.AddMember titleProp
57+
58+
// instance method: GetSummary() : string → "«title» by «author»"
59+
let getSummaryMethod =
60+
ProvidedMethod("GetSummary", [], typeof<string>, isStatic = false,
61+
invokeCode = fun args ->
62+
let t = Expr.FieldGetUnchecked(args.[0], titleField)
63+
let a = Expr.FieldGetUnchecked(args.[0], authorField)
64+
<@@ (%%t : string) + " by " + (%%a : string) @@>)
65+
bookType.AddMember getSummaryMethod
66+
67+
// ── nested class: Shelf ──────────────────────────────────────────────
68+
let shelfType = ProvidedTypeDefinition("Shelf", Some typeof<obj>, isErased = false)
69+
70+
let labelField = ProvidedField("_label", typeof<string>)
71+
shelfType.AddMember labelField
72+
73+
// constructor: Shelf(label: string)
74+
shelfType.AddMember
75+
(ProvidedConstructor(
76+
[ ProvidedParameter("label", typeof<string>) ],
77+
invokeCode = fun args ->
78+
Expr.FieldSetUnchecked(args.[0], labelField, args.[1])))
79+
80+
// property: Label : string
81+
let labelProp =
82+
ProvidedProperty("Label", typeof<string>,
83+
getterCode = fun args -> Expr.FieldGetUnchecked(args.[0], labelField))
84+
shelfType.AddMember labelProp
85+
86+
// static property: MaxItems : int
87+
let maxItemsProp =
88+
ProvidedProperty("MaxItems", typeof<int>, isStatic = true,
89+
getterCode = fun _args -> <@@ 100 @@>)
90+
shelfType.AddMember maxItemsProp
91+
92+
// ── register both nested types ───────────────────────────────────────
93+
container.AddMember bookType
94+
container.AddMember shelfType
95+
96+
tempAssembly.AddTypes [container]
97+
this.AddNamespace(ns, [container])
98+
99+
let loadTestAssembly () =
100+
let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs()
101+
let runtimeAssembly = runtimeAssemblyRefs.[0]
102+
let cfg = Testing.MakeSimulatedTypeProviderConfig (__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs)
103+
let tp = GenerativeNestedProvider(cfg) :> TypeProviderForNamespaces
104+
let providedType = tp.Namespaces.[0].GetTypes().[0] :?> ProvidedTypeDefinition
105+
Assert.Equal("Library", providedType.Name)
106+
let bytes = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedType.Assembly)
107+
let assem = Assembly.Load bytes
108+
assem.ExportedTypes |> Seq.find (fun t -> t.Name = "Library")
109+
110+
[<Fact>]
111+
let ``Nested types Book and Shelf are present in generated assembly``() =
112+
let library = loadTestAssembly ()
113+
let book = library.GetNestedType("Book", BindingFlags.Public)
114+
let shelf = library.GetNestedType("Shelf", BindingFlags.Public)
115+
Assert.NotNull(book)
116+
Assert.NotNull(shelf)
117+
Assert.Equal("Book", book.Name)
118+
Assert.Equal("Shelf", shelf.Name)
119+
120+
[<Fact>]
121+
let ``Book nested type has Title property and GetSummary method``() =
122+
let library = loadTestAssembly ()
123+
let book = library.GetNestedType("Book", BindingFlags.Public)
124+
Assert.NotNull(book)
125+
126+
let titleProp = book.GetProperty("Title", BindingFlags.Instance ||| BindingFlags.Public)
127+
let summaryMeth = book.GetMethod ("GetSummary", BindingFlags.Instance ||| BindingFlags.Public)
128+
Assert.NotNull(titleProp)
129+
Assert.NotNull(summaryMeth)
130+
Assert.Equal(typeof<string>, titleProp.PropertyType)
131+
Assert.Equal(typeof<string>, summaryMeth.ReturnType)
132+
133+
[<Fact>]
134+
let ``Book nested type constructor sets fields; GetSummary returns expected string``() =
135+
let library = loadTestAssembly ()
136+
let book = library.GetNestedType("Book", BindingFlags.Public)
137+
Assert.NotNull(book)
138+
139+
let ctor = book.GetConstructor([| typeof<string>; typeof<string> |])
140+
Assert.NotNull(ctor)
141+
let instance = ctor.Invoke([| box "FSharp for Fun"; box "Don Syme" |])
142+
Assert.NotNull(instance)
143+
144+
let summaryMeth = book.GetMethod("GetSummary", BindingFlags.Instance ||| BindingFlags.Public)
145+
let result = summaryMeth.Invoke(instance, [||]) :?> string
146+
Assert.Equal("FSharp for Fun by Don Syme", result)
147+
148+
[<Fact>]
149+
let ``Shelf nested type Label property reflects constructor argument``() =
150+
let library = loadTestAssembly ()
151+
let shelf = library.GetNestedType("Shelf", BindingFlags.Public)
152+
Assert.NotNull(shelf)
153+
154+
let ctor = shelf.GetConstructor([| typeof<string> |])
155+
Assert.NotNull(ctor)
156+
let instance = ctor.Invoke([| box "Science Fiction" |])
157+
158+
let labelProp = shelf.GetProperty("Label", BindingFlags.Instance ||| BindingFlags.Public)
159+
Assert.NotNull(labelProp)
160+
let value = labelProp.GetValue(instance) :?> string
161+
Assert.Equal("Science Fiction", value)
162+
163+
[<Fact>]
164+
let ``Shelf nested type static property MaxItems returns 100``() =
165+
let library = loadTestAssembly ()
166+
let shelf = library.GetNestedType("Shelf", BindingFlags.Public)
167+
Assert.NotNull(shelf)
168+
169+
let maxItemsProp = shelf.GetProperty("MaxItems", BindingFlags.Static ||| BindingFlags.Public)
170+
Assert.NotNull(maxItemsProp)
171+
let value = maxItemsProp.GetValue(null) :?> int
172+
Assert.Equal(100, value)

0 commit comments

Comments
 (0)