Skip to content

Commit cab9948

Browse files
[Repo Assist] tests: add GenerativeInheritanceTests; prepare release 8.8.0 (#507)
🤖 *This is an automated pull request from Repo Assist.* ## Summary Adds `GenerativeInheritanceTests.fs` — 5 focused tests verifying that one generative `ProvidedTypeDefinition` can inherit from another, with virtual-method overrides dispatched correctly at runtime. ### What's tested | Test | What it checks | |------|---------------| | `Dog is subclass of Animal` | `IsSubclassOf` holds in the loaded assembly | | `Animal.Speak is abstract` | Auto-applied `Abstract\|Virtual` attributes on no-invokeCode method in abstract class | | `Dog.Speak returns expected string` | Constructor sets `_name` field; `Speak()` reads it and formats the result | | `Cat.Speak returns expected string` | Same for Cat | | `Polymorphic dispatch via Animal ref` | Invoking via the base-class `MethodInfo` exercises virtual dispatch | ### Root cause / motivation `DefineMethodOverride` is exercised by `BasicGenerativeProvisionTests` (for interfaces) but there was no dedicated test for **provided-type-to-provided-type** inheritance with runtime invocation. The new file gives explicit, focused coverage of that code path. ### Test status ``` Passed! - Failed: 0, Passed: 152, Skipped: 0, Total: 152 ``` Closes no issue — this is a proactive testing improvement (Tasks 3 & 9). > Generated by 🌈 Repo Assist, see [workflow run](https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/24697828234). [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: 24697828234, workflow_id: repo-assist, run: https://github.com/fsprojects/FSharp.TypeProviders.SDK/actions/runs/24697828234 --> <!-- 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 8e38b56 commit cab9948

3 files changed

Lines changed: 166 additions & 0 deletions

File tree

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
#### 8.8.0 - April 21, 2026
2+
3+
- Tests: Add `GenerativeInheritanceTests` — 5 tests for generative type inheritance: abstract base class, concrete derived classes, virtual method override dispatch verified at runtime
4+
15
#### 8.7.0 - April 19, 2026
26

37
- Tests: Add `GenerativeMethodsTests` covering instance/static methods and method-count in generative types #505

tests/FSharp.TypeProviders.SDK.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<Compile Include="GenerativeDelegateTests.fs" />
2626
<Compile Include="GenerativeMethodsTests.fs" />
2727
<Compile Include="GenerativeCustomAttributeTests.fs" />
28+
<Compile Include="GenerativeInheritanceTests.fs" />
2829
<Compile Include="ReferencedAssemblies.fs" />
2930
</ItemGroup>
3031
<ItemGroup>
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
module TPSDK.GenerativeInheritanceTests
2+
3+
// Tests for generative type inheritance: one ProvidedTypeDefinition deriving from
4+
// another, with virtual/abstract method dispatch verified at runtime.
5+
//
6+
// Scenario:
7+
// abstract class Animal { abstract Speak() : string }
8+
// class Dog(_name:string) extends Animal { override Speak() = "Woof! I am " + _name }
9+
// class Cat(_name:string) extends Animal { override Speak() = "Meow! I am " + _name }
10+
11+
#nowarn "760" // IDisposable needs new
12+
13+
open System
14+
open System.Reflection
15+
open Microsoft.FSharp.Core.CompilerServices
16+
open Microsoft.FSharp.Quotations
17+
open Xunit
18+
open ProviderImplementation.ProvidedTypes
19+
open ProviderImplementation.ProvidedTypesTesting
20+
open UncheckedQuotations
21+
22+
[<TypeProvider>]
23+
type GenerativeInheritanceProvider (config: TypeProviderConfig) as this =
24+
inherit TypeProviderForNamespaces (config)
25+
26+
let ns = "Inheritance.Provided"
27+
let tempAssembly = ProvidedAssembly()
28+
let container = ProvidedTypeDefinition(tempAssembly, ns, "Animals", Some typeof<obj>, isErased = false)
29+
30+
do
31+
// Abstract base class: Animal
32+
let animalType =
33+
ProvidedTypeDefinition("Animal", Some typeof<obj>,
34+
isErased = false, isAbstract = true, isSealed = false)
35+
36+
// Abstract method - no invokeCode; the TPSDK auto-applies Abstract|Virtual when class is abstract
37+
let speakMethod = ProvidedMethod("Speak", [], typeof<string>)
38+
animalType.AddMember speakMethod
39+
// Default constructor so derived classes can chain to it
40+
animalType.AddMember (ProvidedConstructor([], invokeCode = fun _ -> <@@ () @@>))
41+
42+
// Concrete class: Dog
43+
let dogType =
44+
ProvidedTypeDefinition("Dog", Some (animalType :> Type),
45+
isErased = false, isSealed = false)
46+
47+
let dogNameField = ProvidedField("_name", typeof<string>)
48+
dogType.AddMember dogNameField
49+
50+
dogType.AddMember
51+
(ProvidedConstructor(
52+
[ProvidedParameter("name", typeof<string>)],
53+
invokeCode = fun args ->
54+
Expr.FieldSetUnchecked(args.[0], dogNameField, args.[1])))
55+
56+
let dogSpeak =
57+
ProvidedMethod("Speak", [], typeof<string>, isStatic = false,
58+
invokeCode = fun args ->
59+
let nameExpr = Expr.FieldGetUnchecked(args.[0], dogNameField)
60+
<@@ "Woof! I am " + (%%nameExpr : string) @@>)
61+
dogSpeak.AddMethodAttrs(MethodAttributes.Virtual ||| MethodAttributes.HideBySig)
62+
dogType.AddMember dogSpeak
63+
dogType.DefineMethodOverride(dogSpeak, speakMethod)
64+
65+
// Concrete class: Cat
66+
let catType =
67+
ProvidedTypeDefinition("Cat", Some (animalType :> Type),
68+
isErased = false, isSealed = false)
69+
70+
let catNameField = ProvidedField("_name", typeof<string>)
71+
catType.AddMember catNameField
72+
73+
catType.AddMember
74+
(ProvidedConstructor(
75+
[ProvidedParameter("name", typeof<string>)],
76+
invokeCode = fun args ->
77+
Expr.FieldSetUnchecked(args.[0], catNameField, args.[1])))
78+
79+
let catSpeak =
80+
ProvidedMethod("Speak", [], typeof<string>, isStatic = false,
81+
invokeCode = fun args ->
82+
let nameExpr = Expr.FieldGetUnchecked(args.[0], catNameField)
83+
<@@ "Meow! I am " + (%%nameExpr : string) @@>)
84+
catSpeak.AddMethodAttrs(MethodAttributes.Virtual ||| MethodAttributes.HideBySig)
85+
catType.AddMember catSpeak
86+
catType.DefineMethodOverride(catSpeak, speakMethod)
87+
88+
container.AddMembers [animalType; dogType; catType]
89+
tempAssembly.AddTypes [container]
90+
this.AddNamespace(ns, [container])
91+
92+
let loadTestAssembly () =
93+
let runtimeAssemblyRefs = Targets.DotNetStandard20FSharpRefs()
94+
let runtimeAssembly = runtimeAssemblyRefs.[0]
95+
let cfg = Testing.MakeSimulatedTypeProviderConfig(__SOURCE_DIRECTORY__, runtimeAssembly, runtimeAssemblyRefs)
96+
let tp = GenerativeInheritanceProvider(cfg) :> TypeProviderForNamespaces
97+
let providedType = tp.Namespaces.[0].GetTypes().[0]
98+
let bytes = (tp :> ITypeProvider).GetGeneratedAssemblyContents(providedType.Assembly)
99+
let assem = Assembly.Load bytes
100+
assem.GetType("Inheritance.Provided.Animals")
101+
102+
[<Fact>]
103+
let ``Generative Dog type is a subclass of generative Animal type``() =
104+
let animals = loadTestAssembly()
105+
let animalType = animals.GetNestedType("Animal")
106+
let dogType = animals.GetNestedType("Dog")
107+
Assert.NotNull(animalType)
108+
Assert.NotNull(dogType)
109+
Assert.True(dogType.IsSubclassOf(animalType), "Dog should be a subclass of Animal")
110+
111+
[<Fact>]
112+
let ``Generative Animal Speak method is abstract``() =
113+
let animals = loadTestAssembly()
114+
let animalType = animals.GetNestedType("Animal")
115+
let speakMethod = animalType.GetMethod("Speak", BindingFlags.Instance ||| BindingFlags.Public)
116+
Assert.NotNull(speakMethod)
117+
Assert.True(speakMethod.IsAbstract, "Animal.Speak should be abstract")
118+
Assert.True(speakMethod.IsVirtual, "Animal.Speak should be virtual")
119+
120+
[<Fact>]
121+
let ``Generative Dog Speak method returns expected string``() =
122+
let animals = loadTestAssembly()
123+
let dogType = animals.GetNestedType("Dog")
124+
Assert.NotNull(dogType)
125+
let ctor = dogType.GetConstructor([| typeof<string> |])
126+
Assert.NotNull(ctor)
127+
let dog = ctor.Invoke([| box "Buddy" |])
128+
let speakMethod = dogType.GetMethod("Speak", BindingFlags.Instance ||| BindingFlags.Public)
129+
Assert.NotNull(speakMethod)
130+
let result = speakMethod.Invoke(dog, [||]) :?> string
131+
Assert.Equal("Woof! I am Buddy", result)
132+
133+
[<Fact>]
134+
let ``Generative Cat Speak method returns expected string``() =
135+
let animals = loadTestAssembly()
136+
let catType = animals.GetNestedType("Cat")
137+
Assert.NotNull(catType)
138+
let ctor = catType.GetConstructor([| typeof<string> |])
139+
Assert.NotNull(ctor)
140+
let cat = ctor.Invoke([| box "Whiskers" |])
141+
let speakMethod = catType.GetMethod("Speak", BindingFlags.Instance ||| BindingFlags.Public)
142+
let result = speakMethod.Invoke(cat, [||]) :?> string
143+
Assert.Equal("Meow! I am Whiskers", result)
144+
145+
[<Fact>]
146+
let ``Generative Speak override is dispatched polymorphically via Animal base reference``() =
147+
let animals = loadTestAssembly()
148+
let animalType = animals.GetNestedType("Animal")
149+
let dogType = animals.GetNestedType("Dog")
150+
let catType = animals.GetNestedType("Cat")
151+
let speakViaBase = animalType.GetMethod("Speak", BindingFlags.Instance ||| BindingFlags.Public)
152+
Assert.NotNull(speakViaBase)
153+
154+
let dog = dogType.GetConstructor([| typeof<string> |]).Invoke([| box "Rex" |])
155+
let cat = catType.GetConstructor([| typeof<string> |]).Invoke([| box "Luna" |])
156+
157+
// Invoke via the base-class MethodInfo — exercises virtual dispatch
158+
let dogResult = speakViaBase.Invoke(dog, [||]) :?> string
159+
let catResult = speakViaBase.Invoke(cat, [||]) :?> string
160+
Assert.Equal("Woof! I am Rex", dogResult)
161+
Assert.Equal("Meow! I am Luna", catResult)

0 commit comments

Comments
 (0)