|
| 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