-
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
Conditional types break with property chaining #32735
Comments
I know, I know, recursive type alias, bad. Interfaces break, too, interface Droste<T extends {x:number|string}> {
value: T,
/**
* Should alternate between string and number
*/
droste: Droste<{x:(T["x"] extends number ? string : number)}>
};
declare const droste: Droste<{x:number}>;
//number
const _0 = droste.droste.droste.droste.droste.droste.droste.droste.droste.droste.droste
.droste.droste.droste.droste.droste.droste.droste.droste.droste.droste
.droste.droste;
const _1 = _0.droste; //string
const _2 = _1.droste; //number
const _3 = _2.droste; //string
const _4 = _3.droste; //number
const _5 = _4.droste; //Expected string, actual number, ???
const _6 = _5.droste; //number
const _7 = _6.droste; //number
const _8 = _7.droste; //number
const _9 = _8.droste; //number
//number forever and ever. Where is `string`? It breaks in a different way, though. |
Looks like it's falling afoul of the instantiation limit type check<T> = T extends Droste<{ x: string }> ? true : false;
type a = check<typeof _1>; // true
type b = check<typeof _2>; // false
type c = check<typeof _3>; // Type instantiation is excessively deep and possibly infinite. |
It seems like the workaround outlined in #32707 (comment) works, /**
* Uses the workaround discovered here,
* https://github.com/microsoft/TypeScript/issues/32707#issuecomment-518347966
*
* Now, everything resolves correctly.
*/
type NextDrosteFix<X extends number|string> = (
Droste<{x:(X extends number ? string : number)}>
);
type NextDroste<T extends {x:number|string}> = (
NextDrosteFix<T["x"]>
);
interface Droste<T extends {x:number|string}> {
value: T,
/**
* Should alternate between string and number
*/
droste: NextDroste<T>
};
declare const droste: Droste<{x:number}>;
//number
const _0 = droste.droste.droste.droste.droste.droste.droste.droste.droste.droste.droste
.droste.droste.droste.droste.droste.droste.droste.droste.droste.droste
.droste.droste;
const _1 = _0.droste; //string
const _2 = _1.droste; //number
const _3 = _2.droste; //string
const _4 = _3.droste; //number
const _5 = _4.droste; //string
const _6 = _5.droste; //number
const _7 = _6.droste; //string
const _8 = _7.droste; //number
const _9 = _8.droste; //string And, as always, I know it works but I don't know why it works. |
If I had to guess (and it really is a guess), there is a caching of instantiations done on type id. With your original example you create a new object literal type (with a new id) for each recursive instantiation that will have no cached value. In the modified example each the recursion goes through |
Are array types also considered primitive types for interning purposes? Array types never seem to give problems. Only object literal types. [Edit] Object literal types and interfaces and classes (when used as a type param constraint) |
I think arrays and tuples are cached, but I would wait on confirmation. |
TypeScript Version: 3.5.1
Search Terms:
property chaining, generic, object type param, conditional type
Code
Modified version of @fatcerberus ' attempt to break TS.
Expected behavior:
Each access to
.droste
should alternate betweenstring
andnumber
.Or give a max instantiation depth/count error.
Actual behavior:
It alternates between
string
andnumber
and then breaks after while.From that point, it just sticks with
string
.No errors given.
Playground Link:
Playground
Related Issues:
It is similar to #32707
Except, it gives up and resolves the type to
string
, rather thanany
.The text was updated successfully, but these errors were encountered: