-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Regression: type guard function returning recursive utility type fails to apply the second time #54246
Comments
Regression introduced by #52984 |
@jcalz could you also share the code with hardcoded types that works? |
declare function guardPathX<T>(x: T): x is T & { a: { b: { x: { c: string; }; }; }; }
declare function guardPathY<T>(x: T): x is T & { a: { b: { y: { c: string; }; }; }; }
function foo(obj: {}) {
if (guardPathX(obj) && guardPathY(obj)) {
obj.a.b.x.c.toUpperCase(); // okay
obj.a.b.y.c.toUpperCase(); // okay
}
} |
So the problem is that the resulting types are classified as "maybe" related and thus t // { a: { b: { x: { c: string; }; }; }; }
c // { a: { b: { x: { c: string; }; }; }; } & { a: { b: { y: { c: string; }; }; }; } You might ask, why those types are "maybe" related? This is assessed based on
// [...] It is possible, though highly unlikely, for
// the deeply nested check to be true in a situation where a chain of instantiations is not infinitely expanding.
// Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth levels,
// but unequal at some level beyond that. And this example here hits exactly the "highly unlikely" case π
All of the objects on the All of those objects share this information as they are computed based on the same recursive type (the declaration of this symbol is cc @ahejlsberg |
@Andarist is there a workaround for this? The problem I'm running into in #55535 is actually worse than I thought -- as far as I can tell, the type checker silently turns off past a depth of 3 for types that are recursively generated. This is happening silently -- which makes me think that the change you linked (where the check for It's also quite difficult to identify that type-checking is not happening at all, which makes me think that this could be a serious issue (since even when a runtime exception is thrown, which is the only way you'd discover that there might be a problem, there's still no indication by looking at the types that they don't actually check anything). Which makes me think that any bugs that this is creating are not getting reported, or are being reported as something else. |
To clarify, I'm trying to figure out how to write a recursively mapped type so that the output still type-checks past a depth of 3. If I don't have a way to do this, I'm not sure what to explore next... possibly writing an eslint rule that bans recursively mapped types altogether? Otherwise, I'm not sure how to trust that the type my IDE shows me is actually what gets type checked. |
This one is really tricky. We lack the ability to recognize that the One way to extend the depth we explore recursive type instantiations is to have a circular ladder: type NestedRecord<K extends string, V> =
K extends `${infer K0}.${infer KR}` ?
{ [P in K0]: NestedRecord1<KR, V> } :
{ [P in K]: V };
type NestedRecord1<K extends string, V> =
K extends `${infer K0}.${infer KR}` ?
{ [P in K0]: NestedRecord2<KR, V> } :
{ [P in K]: V };
type NestedRecord2<K extends string, V> =
K extends `${infer K0}.${infer KR}` ?
{ [P in K0]: NestedRecord<KR, V> } :
{ [P in K]: V }; Each type adds another three levels of possible nesting. That's probably the best workaround. |
Bug Report
π Search Terms
recursive nested conditional type guard function
π Version & Regression Information
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
obj
has been narrowed only to{a:{b:{x:{c: string}}}}
.π Expected behavior
obj
should be narrowed to{a:{b:{x:{c: string}}}} & {a:{b:{y:{c: string}}}}
.This is a weird one. Seems to be caused by the recursive
NestedRecord
utility type (if I replace that with hardcoded types it works) interacting with multiple type guard applications. Probably hitting some limit somewhere where it gives up? Or an inaccurate variance shortcut? Not sure. Anyway this broke some Stack Overflow answer I was working on and it would be nice to know what's up with it.The text was updated successfully, but these errors were encountered: