Generic Base type parameterized with its inheritor => Stack Overflow on .GetType() of inheritor value
Description
Given a simple generic type hierarchy, where Base<> type is parameterized with its Inheritor type, it is possible to construct and use a value of Inheritor , but attempt to get runtime .GetType() results in stack overflow.
Repro code
Go to https://fable.io/repl/
[<AbstractClass>]
type Base<'Inheritor when 'Inheritor :> Base<'Inheritor>> () =
member _.Dummy() = 1
type Inheritor() = inherit Base<Inheritor>()
let i = new Inheritor()
let t = i.GetType()
printf "hello world"
printf $"{t}"
printf "goodbye world"
Expected and actual results
Expected results:
hello world { some runtime type info printed } goodbye world
Actual results:
Nothing printed, in Chrome dev tools Console following is found:
VM250 e85874de-2120-4703-a727-b32fbb1484f8:29 Uncaught RangeError: Maximum call stack size exceeded at Inheritor_$reflection (VM250 e85874de-2120-4703-a727-b32fbb1484f8:29:5) at Inheritor_$reflection (VM250 e85874de-2120-4703-a727-b32fbb1484f8:29:79) at Inheritor_$reflection (VM250 e85874de-2120-4703-a727-b32fbb1484f8:29:79) at Inheritor_$reflection (VM250 e85874de-2120-4703-a727-b32fbb1484f8:29:79) at Inheritor_$reflection (VM250 e85874de-2120-4703-a727-b32fbb1484f8:29:79) at Inheritor_$reflection (VM250 e85874de-2120-4703-a727-b32fbb1484f8:29:79) ...
Related information
-
Fable version:
dotnet fable --version3.7.20 -
Operating system Windows 11 Pro
but probably will reproduce elsewhere
Note that only attempt to work with runtime type info results in stack overflow. It is still possible to construct and use the value, for example:
[<AbstractClass>]
type Base<'Inheritor when 'Inheritor :> Base<'Inheritor>> () =
member _.Dummy() = 1
type Inheritor() = inherit Base<Inheritor>()
let i = new Inheritor()
printf "hello world"
printf $"Dummy value: {i.Dummy()}"
printf "goodbye world"
prints
hello world Dummy value: 1 goodbye world
as expected
However in my case it is mission critical to have working runtime type info (specifically for Thoth JSON encoding via Encoding.Auto)
This one is tricky.
The problem comes from this function:
export function Inheritor_$reflection() {
return class_type("Test.Inheritor", void 0, Inheritor, Base$1_$reflection(Inheritor_$reflection()));
}
Which as you can see reference itself and this is what is causing the StackOverflow.
Perhaps, we need to look if we can use a Lazy value for the parent parameter or evaluate the function only when needed, I need to look where the parent arguments is used and for what.
why not just pass a null parent and then set it afterwards like this (pseudocode)
export function Inheritor_$reflection() {
var x = class_type("Test.Inheritor", void 0, Inheritor, null);
x.Parent = Base$1_$reflection(x);
return x;
}
Perhaps this can works, I am wondering if there are edges cases where this would not work or not.
I suppose the CI will tells us if this cause a regression or not, if it doesn't we will go with it unless another maintainer has another solutions. I am not yet familiar will the requirements of Fable reflection
sure, I'm not familiar with the code at all, and there might be other code paths that try to traverse reflection type hierarchy without the loop check, which also might stack overflow or hang, but at very least it will happen later