-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Undetected illegal assignment of nested array types #52912
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
Comments
The root cause here is that we need to bail out at some point during nested evaluation of a type, since we have no way to know that we're not encountering an infinitely generatively-nested recursive type, e.g.: type Nested<T> = {
a: T;
b?: Nested<Nested<T>>;
} "Ah, but this type is just an ever-deeper instantiation of the same thing over and over again, which would be trivial to detect, whereas OP is totally different" you say. But the stack here looks just like the stack in the original report, because we keep visiting ever-deeper instantiations of the same type -- type Box<T> = { value: T };
type Source1 = { array: Box<Source2> };
type Source2 = { array: Box<Source3> };
type Source3 = { array: Box<Source4> };
type Source4 = {};
// same as Source types but with "someNewProperty"
type Target1 = {
array: Box<Target2>;
// someNewProperty: string // error in target1 assignment as expected if enabled
};
type Target2 = {
array: Box<Target3>;
// someNewProperty: string // error in target1 assignment as expected if enabled
};
type Target3 = {
array: Box<Target4>;
// someNewProperty: string // error in target1 assignment as expected if enabled
};
type Target4 = {
someNewProperty: string; // not existing in Source4 => no error in target1 assignment
};
declare const source1: Source1;
declare const source2: Source2;
declare const source3: Source3;
declare const source4: Source4;
// this should not compile:
const target1: Target1 = source1; // comment this line to get errors in target2, target3 assignments or move this line after target2 assignment
// this should not compile:
const target2: Target2 = source2; // error if target1 assignment is commented or after this assignment So if anyone wants to try to fix this they're welcome to come up with some new detection mechanism, but any fix here needs to a) not tank performance in normal scenarios and b) not introduce new circularity or complexity errors in real code either. Note that it's also not sufficient to detect |
Hi @RyanCavanaugh, thank you for your quick response! I think "Cursed?" is the right label for this. What actually surprised me was that this problem has not been reported yet. When using Typescript-based ORMs like @prisma, you can run into this kind of problem very quickly when joining some tables. In our case, the following data structure triggered the error (simplified): I am not familiar with the TS codebase, but given your example of the infinitely recursive type Ping = { pong: Pong };
type Pong = { ping: Ping }; Naively, isn't it trivial to discover the circle here too? But anyway, I find both your Source-Box and my Source-Array example scary. Typescript promises to be "A Result You Can Trust" and this is a very simple and not so far-fetched example where typescript breaks that promise. If you could give me some starting points within the codebase to investigate this problem, I would be happy to help find a solution. |
That's not what's happening in the provided example. You're talking about detecting cycles, but we're not talking about cycles here, but rather infinite descent of novel types the whole way down. In other words, although there are referential cycles, there are not cycles of the same types - we're talking about increasingly-deep instantiations of the same types.
You can add this line: main...RyanCavanaugh:TypeScript:stackDemo#diff-d9ab6589e714c71e657f601cf30ff51dfc607fc98419bf72e04f6b0fa92cc4b8R22654 to remove the depth limit. Then start trying to figure out how to make all the newly-failing tests in the same commit pass again, e.g. this one: main...RyanCavanaugh:TypeScript:stackDemo#diff-b92eac93adfa5c33adb53f3e9689088003bc273d7ec04382d0573f6944055897R25 This testcase, shown here failing if the depth limit didn't exist, is a good demonstration. In order to see if Fair warning: if we had any idea how to fix this, it wouldn't be Cursed (and would have been fixed already). PRs surely welcomed but this isn't a case of us simply not having tried to fix it before. |
Bug Report
Typescript does not allow the assignment of
Source4
toTarget4
which is expected because they are incompatible. After 3 or more levels of nested array properties this error is not detected anymore. Even the errors on the nested types may disappear depending on the order of the assignments.Note: every type is explicitly named and there are no cycles.
(This actually caused problems in our production codebase)
🔎 Search Terms
nested array assignment, undetected illegal assignment of nested array types, undetected illegal assignment, illegal assignment
🕗 Version & Regression Information
isDeeplyNestedType
maxDepth check?⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
Source1
is assignable toTarget1
Source2
is assignable toTarget2
Source3
is assignable toTarget3
source1
assignment is skipped or moved aftersource2
assignment the expected errors show🙂 Expected behavior
Source1
is not assignable toTarget1
Source1
is a fixed/static tree (without any self references) I expect the error to be found by the compiler.Related issues
#42070
The text was updated successfully, but these errors were encountered: