-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Circular reference error when defining Record type #41164
Comments
Certain circularities are allowed (e.g. T1) but other circularities aren't, e.g. type Identity<T> = T;
type T3 = Identity<T3>; Generic instantiation is deferred, so at the point TS analyzes |
@RyanCavanaugh thanks for the explanation. Not that it's TypeScript team's responsibility, but just as an FYI this may become a more popular issue as teams use the |
@RyanCavanaugh The behaviour seems a bit inconsistent to me (or at least not very transparent) In the following example a code change somewhere deep down resulted in a failure.
|
@RyanCavanaugh not sure what's under the hood of type A<T> = T | Array<A<T>> // This line works fine
type R = Record<string, R> // this one doesn't |
I don't understand how what you're saying applies to OP? Because OP doesn't have an |
I guess what you're saying is that a type alias can't reference itself if it's used in a type parameter of an ordinary generic type instantiation: type MyRecord<V> = {[key: string]: V}
type Stuff = MyRecord<string | Stuff> // circular reference error I don't get why this is technically impossible to support though. Let's say we're checking that My layperson imagination of how that could work is:
Additionally, Flow supports the |
To add to what @nscarcella was saying, type A<T> = T | Array<A<T>> // okay
type MyArray<T> = T[]
type B<T> = T | MyArray<B<T>> // errors |
Actually, I was wrong about Flow, it fails to produce expected errors on a similar recursive generic type alias: // @flow
type Record<K, V> = {[K]: V}
type Stuff<T> = Record<string, T | Stuff<T>>
const foo: Stuff<number> = { a: 'not a number' } // should error, but doesn't Maybe there is something fundamentally hard about supporting such types...I wish I had a more concrete understanding of why, because mathematically speaking, this type definition seems perfectly coherent |
Imagine I told you that an integer was a flarpnumber if it was either prime or one of its digits was a flarpnumber. Is 6 a flarpnumber? |
@RyanCavanaugh I mean obviously that seems undecidable, but I still don't see what it has to do with OP's example of |
The thing that's frustrating about this is if we define type MyRecord<V> = {[key: string]: V} Because of the type T = {[key: string]: T} // okay And then try to substitute the RHS with what should be equivalent: type T = MyRecord<T> // error, even though this means the same thing So |
BTW, the issue I mentioned with typescript-eslint was fixed (typescript-eslint/typescript-eslint#2687) |
@jedwards1211 good point, also what you've described is a workaround for this very issue! type ThisFails = number | Record<string, ThisFails>
type ThisWorks = number | {[key: string]: ThisWorks} |
Hi,
yes, I was expecting this to work... but it does not :( Regards |
This seems work: // eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ThisWorks extends Record<string, ThisWorks> {} { [key: string]: T } may not be very useful since |
Why is it so painful to write recursive programs with TypeScript? type texpr =
| number
| string
| Array<texpr>
| Promise<texpr>
| Record<string, texpr> // circular reference error 😓 async function evaluate(t: texpr): Promise<texpr> {
switch (t.constructor) {
case Number: ...
case String: ...
case Array: ...
case Object: ...
case Promise: ...
}
} What's the fix without a bunch of goofy business? |
What do you mean it doesn't work? It works for me. The first one errors the second one doesn't. |
@Pyrolistical: exactly, the first one does not work |
My apologies for replying here. But figured I would double check before creating a possible issue. Extending the playground @Pyrolistical posted above, I get an error while trying to delete a subkey. type ThisFails = number | Record<string, ThisFails>;
type ThisWorks = number | { [key: string]: ThisWorks };
type ButThisErrors =
| { [key: string]: ButThisErrors | string | number }
| string
| number;
const test1TopLevel: ButThisErrors = {
l2: { l3: { l4: { l5: { l6: "endkey" } } } },
};
delete test1TopLevel.l2.l3.l4.l5.l6;
interface AndSoDoesThis {
[key: string]: AndSoDoesThis | string | number
}
const test2TopLevel: AndSoDoesThis = {
l2: { l3: { l4: { l5: { l6: "endkey" } } } },
};
delete test2TopLevel.l2.l3.l4.l5.l6; I'm guessing that this is due to the cut-off for recursive parsing? |
Why is this closed as completed? Where is the solution and what is the version where it works as expected? |
TypeScript Version: 4.0.2 (and 4.1.0-dev.20201015)
Search Terms: circularly, Record
Code
Expected behavior:
No compilation errors
Actual behavior:
While type T1 compiles properly, type T2 reports
Type alias 'T2' circularly references itself.
even though they are inherently the same type. This was actually found by autofixing a new @typescript-eslint rule consistent-indexed-object-stylePlayground Link: https://www.typescriptlang.org/play?ts=4.0.2#code/C4TwDgpgBAKgjFAvFA3lA2gawiAXFAZ2ACcBLAOwHMBdfeKAXwFgAoUSWAJiSgCUIAxgHtiAEwA8RMlQA0XAHxA
Related Issues: None found
The text was updated successfully, but these errors were encountered: