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