Fable icon indicating copy to clipboard operation
Fable copied to clipboard

Generic Base type parameterized with its inheritor => Stack Overflow on .GetType() of inheritor value

Open DunetsNM opened this issue 2 years ago • 5 comments

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

  • Operating system Windows 11 Pro

but probably will reproduce elsewhere

DunetsNM avatar Nov 22 '23 02:11 DunetsNM

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)

DunetsNM avatar Nov 22 '23 02:11 DunetsNM

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.

MangelMaxime avatar Nov 22 '23 13:11 MangelMaxime

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

DunetsNM avatar Nov 26 '23 11:11 DunetsNM

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

MangelMaxime avatar Nov 26 '23 17:11 MangelMaxime

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

DunetsNM avatar Nov 26 '23 23:11 DunetsNM