'Use of decorator function causes circular reference type errors

My code was working just fine until I tried to wrap a function definition with a decorator (by "decorator" I just mean a higher-order function that takes one function as a param and return another function, I'm not talking about the upcoming decorator syntax). When I use this decorator, I get the error Type alias 'Thing' circularly references itself. Why is this happening?

Below is the broken code sample (This is an extremely simplified version that tries to simply showcase the problem I'm running into).

const decorate = (fn: () => boolean) => fn

const thing = {
    fn: decorate(() => {
        type Thing = typeof thing
        const thing_: Thing = thing
        return 'fn' in thing_
    })
}

Compare that to the following code snippet, which works just fine. I'm just removing the call to decorate (which is just an identity function in this simplified example).

const decorate = (fn: () => boolean) => fn

const thing = {
    fn: () => {
        type Thing = typeof thing
        const thing_: Thing = thing
        return 'fn' in thing_
    }
}

I can even take my broken example and replace the decorate function with the following generic function, and it'll magically start working, even though the two definitions should be functionally equivalent in the specific way they're being used.

const decorate = <T>(fn: T): T => fn

Update: For anyone who's interested, here's how I ended up fixing my issue

I had to explicitly tell TypeScript what the type of my function was, so I put it in an IFEE and assigned it to a variable with a declared type, like this:

const decorate = (fn: () => boolean) => fn

const thing = {
    fn: (() => {
        type MyFnType = () => boolean
        const fn: MyFnType = decorate(() => {
            type Thing = typeof thing
            const thing_: Thing = thing
            return 'fn' in thing_
        })
        return fn
    })()
}

It's not pretty, but it works around this issue.

(Note that this isn't an answer to my own question, as my question is asking why I have to do a workaround like this, I'm not necessarily asking how to do it).



Solution 1:[1]

Let's write in this way

const decorate = (fn: () => boolean) => fn

const t = () => {
        type Thing = typeof thing
        const thing_: Thing = thing
        return 'fn' in thing_
    };

Now if you write

const thing = {
    fn: decorate(t)
}

As this involve invoking decorate with t as argument, the compiler requires t be assignable to ()=>boolean before it can proceed to infer the type of thing. So it must type check t before the type of thing is inferred, causing circular references. i.e. in this case, the type of thing matters to t and the type of t also matters to thing.

In contrast when you write

const thing = {
    fn: t
}

There is no requirement on what type t should be. The compiler can proceed to infer the type of thing, just infer the type of fn to whatever t happens to be, without caring what it actually is. Therefore no need to type check t beforehand. So in this case, the type of thing matters to t but the type of t does NOT matter to thing.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1