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: 5 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#### 8.9.0 - April 23, 2026

- Tests: Add `GenerativeNestedTypesTests` β€” 5 tests for generative nested types: constructors, instance/static properties, instance methods inside nested classes
- Refactor: `GetNestedTypes`/`GetMembers` on `ProvidedTypeDefinition` now use `canBindNestedType` for consistent BindingFlags-aware filtering of nested types

#### 8.8.0 - April 21, 2026

- Tests: Add `GenerativeInheritanceTests` β€” 5 tests for generative type inheritance: abstract base class, concrete derived classes, virtual method override dispatch verified at runtime
Expand Down
4 changes: 2 additions & 2 deletions src/ProvidedTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1613,7 +1613,7 @@ and ProvidedTypeDefinition(isTgt: bool, container:TypeContainer, className: stri
override __.GetNestedTypes bindingFlags =
(//save ("nested", bindingFlags, None) (fun () ->
getMembers()
|> Array.choose (function :? Type as m when memberBinds true bindingFlags false m.IsPublic || m.IsNestedPublic -> Some m | _ -> None)
|> Array.choose (function :? Type as m when canBindNestedType bindingFlags m -> Some m | _ -> None)
|> (if hasFlag bindingFlags BindingFlags.DeclaredOnly || isNull this.BaseType then id else (fun mems -> Array.append mems (this.ErasedBaseType.GetNestedTypes(bindingFlags)))))

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

override this.GetMember(name, mt, _bindingFlags) =
Expand Down
1 change: 1 addition & 0 deletions tests/FSharp.TypeProviders.SDK.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<Compile Include="GenerativeMethodsTests.fs" />
<Compile Include="GenerativeCustomAttributeTests.fs" />
<Compile Include="GenerativeInheritanceTests.fs" />
<Compile Include="GenerativeNestedTypesTests.fs" />
<Compile Include="ReferencedAssemblies.fs" />
</ItemGroup>
<ItemGroup>
Expand Down
172 changes: 172 additions & 0 deletions tests/GenerativeNestedTypesTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
module TPSDK.GenerativeNestedTypesTests

// Tests for generative nested types: one ProvidedTypeDefinition hosting other
// ProvidedTypeDefinitions as nested (inner) types.
//
// Scenario:
// class Library
// nested class Book { Title: string; Author: string; ctor(t,a); GetSummary(): string }
// nested class Shelf { Label: string; ctor(label); MaxItems: int (static) }
//
// Tests verify that nested types are emitted correctly, that their constructors,
// fields, instance methods, and static members are all accessible at runtime.

#nowarn "760" // IDisposable needs new

open System
open System.Reflection
open Microsoft.FSharp.Core.CompilerServices
open Microsoft.FSharp.Quotations
open Xunit
open ProviderImplementation.ProvidedTypes
open ProviderImplementation.ProvidedTypesTesting
open UncheckedQuotations

[<TypeProvider>]
type GenerativeNestedProvider (config: TypeProviderConfig) as this =
inherit TypeProviderForNamespaces (config)

let ns = "Nested.Provided"
let tempAssembly = ProvidedAssembly()
let container = ProvidedTypeDefinition(tempAssembly, ns, "Library", Some typeof<obj>, isErased = false)

do
// ── nested class: Book ───────────────────────────────────────────────
let bookType = ProvidedTypeDefinition("Book", Some typeof<obj>, isErased = false)

let titleField = ProvidedField("_title", typeof<string>)
let authorField = ProvidedField("_author", typeof<string>)
bookType.AddMember titleField
bookType.AddMember authorField

// constructor: Book(title: string, author: string)
bookType.AddMember
(ProvidedConstructor(
[ ProvidedParameter("title", typeof<string>)
ProvidedParameter("author", typeof<string>) ],
invokeCode = fun args ->
Expr.Sequential(
Expr.FieldSetUnchecked(args.[0], titleField, args.[1]),
Expr.FieldSetUnchecked(args.[0], authorField, args.[2]))))

// property: Title : string
let titleProp =
ProvidedProperty("Title", typeof<string>,
getterCode = fun args -> Expr.FieldGetUnchecked(args.[0], titleField))
bookType.AddMember titleProp

// instance method: GetSummary() : string β†’ "Β«titleΒ» by Β«authorΒ»"
let getSummaryMethod =
ProvidedMethod("GetSummary", [], typeof<string>, isStatic = false,
invokeCode = fun args ->
let t = Expr.FieldGetUnchecked(args.[0], titleField)
let a = Expr.FieldGetUnchecked(args.[0], authorField)
<@@ (%%t : string) + " by " + (%%a : string) @@>)
bookType.AddMember getSummaryMethod

// ── nested class: Shelf ──────────────────────────────────────────────
let shelfType = ProvidedTypeDefinition("Shelf", Some typeof<obj>, isErased = false)

let labelField = ProvidedField("_label", typeof<string>)
shelfType.AddMember labelField

// constructor: Shelf(label: string)
shelfType.AddMember
(ProvidedConstructor(
[ ProvidedParameter("label", typeof<string>) ],
invokeCode = fun args ->
Expr.FieldSetUnchecked(args.[0], labelField, args.[1])))

// property: Label : string
let labelProp =
ProvidedProperty("Label", typeof<string>,
getterCode = fun args -> Expr.FieldGetUnchecked(args.[0], labelField))
shelfType.AddMember labelProp

// static property: MaxItems : int
let maxItemsProp =
ProvidedProperty("MaxItems", typeof<int>, isStatic = true,
getterCode = fun _args -> <@@ 100 @@>)
shelfType.AddMember maxItemsProp

// ── register both nested types ───────────────────────────────────────
container.AddMember bookType
container.AddMember shelfType

tempAssembly.AddTypes [container]
this.AddNamespace(ns, [container])

let loadTestAssembly () =
let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs()
let runtimeAssembly = runtimeAssemblyRefs.[0]
let cfg = Testing.MakeSimulatedTypeProviderConfig (__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs)
let tp = GenerativeNestedProvider(cfg) :> TypeProviderForNamespaces
let providedType = tp.Namespaces.[0].GetTypes().[0] :?> ProvidedTypeDefinition
Assert.Equal("Library", providedType.Name)
let bytes = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedType.Assembly)
let assem = Assembly.Load bytes
assem.ExportedTypes |> Seq.find (fun t -> t.Name = "Library")

[<Fact>]
let ``Nested types Book and Shelf are present in generated assembly``() =
let library = loadTestAssembly ()
let book = library.GetNestedType("Book", BindingFlags.Public)
let shelf = library.GetNestedType("Shelf", BindingFlags.Public)
Assert.NotNull(book)
Assert.NotNull(shelf)
Assert.Equal("Book", book.Name)
Assert.Equal("Shelf", shelf.Name)

[<Fact>]
let ``Book nested type has Title property and GetSummary method``() =
let library = loadTestAssembly ()
let book = library.GetNestedType("Book", BindingFlags.Public)
Assert.NotNull(book)

let titleProp = book.GetProperty("Title", BindingFlags.Instance ||| BindingFlags.Public)
let summaryMeth = book.GetMethod ("GetSummary", BindingFlags.Instance ||| BindingFlags.Public)
Assert.NotNull(titleProp)
Assert.NotNull(summaryMeth)
Assert.Equal(typeof<string>, titleProp.PropertyType)
Assert.Equal(typeof<string>, summaryMeth.ReturnType)

[<Fact>]
let ``Book nested type constructor sets fields; GetSummary returns expected string``() =
let library = loadTestAssembly ()
let book = library.GetNestedType("Book", BindingFlags.Public)
Assert.NotNull(book)

let ctor = book.GetConstructor([| typeof<string>; typeof<string> |])
Assert.NotNull(ctor)
let instance = ctor.Invoke([| box "FSharp for Fun"; box "Don Syme" |])
Assert.NotNull(instance)

let summaryMeth = book.GetMethod("GetSummary", BindingFlags.Instance ||| BindingFlags.Public)
let result = summaryMeth.Invoke(instance, [||]) :?> string
Assert.Equal("FSharp for Fun by Don Syme", result)

[<Fact>]
let ``Shelf nested type Label property reflects constructor argument``() =
let library = loadTestAssembly ()
let shelf = library.GetNestedType("Shelf", BindingFlags.Public)
Assert.NotNull(shelf)

let ctor = shelf.GetConstructor([| typeof<string> |])
Assert.NotNull(ctor)
let instance = ctor.Invoke([| box "Science Fiction" |])

let labelProp = shelf.GetProperty("Label", BindingFlags.Instance ||| BindingFlags.Public)
Assert.NotNull(labelProp)
let value = labelProp.GetValue(instance) :?> string
Assert.Equal("Science Fiction", value)

[<Fact>]
let ``Shelf nested type static property MaxItems returns 100``() =
let library = loadTestAssembly ()
let shelf = library.GetNestedType("Shelf", BindingFlags.Public)
Assert.NotNull(shelf)

let maxItemsProp = shelf.GetProperty("MaxItems", BindingFlags.Static ||| BindingFlags.Public)
Assert.NotNull(maxItemsProp)
let value = maxItemsProp.GetValue(null) :?> int
Assert.Equal(100, value)
Loading