-
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
Recursive conditional types are aliased #22575
Comments
I'm seeing this too with a fairly similar recursive type. My use cases have only 1-2 levels of recursion. The compiler infers the recursive types correctly, but intellisense shows aliases after a single level of recursion. I'm not sure the OP's quote from #22011 applies, since it's not clear that is talking about intellisense rather than the actual type within the compiler. Regardless, it would be helpful when working in VSCode (or other IDEs) if intellisense didn't abbreviate the type quite so soon. An example from my case: // the full type as inferred inside the compiler:
{
methods: {};
modules: {
user: {
methods: {
bar: {
in: t.string;
out: t.number;
};
};
modules: {};
};
};
};
// the same type as reported by intellisense:
{
methods: {};
modules: {
user: Flatten;
};
};
If I inline |
@yortus TypeScript Version: 3.2.2 |
@BetterCallSKY have you tried just simply flattening the type? Look at this: type DeepConvert<T> = T extends object ? { [P in keyof DeepConvertObject<T>]: DeepConvertObject<T>[P] } : T; I know, it's showing following error Type 'P' cannot be used to index type 'ConvertObject<{ [P in keyof T]: DeepConvert<T[P]>; }>'. but surprisingly it works as it should. Take a look on hovering effect: |
If anyone can figure out a type alias printing strategy that doesn't regress current scenarios or generate stack overflows, we'd be happy to get a PR. The current rules are our best effort at heuristics that result in overall good results. |
@lchimaru @RyanCavanaugh |
As per your request for suggestions on heuristic improvements, would the TS team be open to user supplied compiler hints for when the user is dealing with complex recursive conditional types? The work around for this issue is the same for #28508 (comment) which is to flatten the hierarchy with another recursive conditional. That actually put the user at even more risk of hitting #34933. (I'm running typescript@next and keep hitting the infinite recursion problem.) Similar to the the example here: #28508 (comment), and above, my code follows the same pattern: //NOTE: Details of this don't matter for this thread, but users are seemingly converging on this pattern
// for mapping hierarchies.
export type ShapeMapper<T> = { [P in keyof T]: Shape<T[P]> };
export type Shape<TDefinition> =
TDefinition extends LiteralType<infer LiteralKind>
? LiteralKind
: TDefinition extends Type<'string'>
? string
: TDefinition extends Type<'number'>
? number
: TDefinition extends Type<'boolean'>
? boolean
: TDefinition extends Type<'bigint'>
? bigint
: TDefinition extends Type<'null'>
? null
: TDefinition extends Type<'undefined'>
? undefined
: TDefinition extends ArrayType<infer ElementKind>
? Array<Shape<ElementKind>>
: TDefinition extends UnionType<infer KeyKind>
? Shape<KeyKind>
: TDefinition extends ObjType<infer TShapeDefinition>
? ShapeMapper<TShapeDefinition>
: never; Then we use the template on our actual type schemas. The following is the pattern used by many if not all the TypeScript validation libs, like io-ts, zod, etc. All of our application types are defined in this inverted way. //Define schema
const Person = t.obj({
name: t.str,
});
const Address = t.obj({
owner: Person
});
//Infer the TypeScript type
type PersonShape = Shape<typeof Person>;
type AddressShape = Shape<typeof Address>; But, this destroys intellisense as relations emerge. (The export type FlattenForIntellisense<T> = T extends object ? {} & { [P in keyof T]: FlattenForIntellisense<T[P]> } : T;
type PersonShape = FlattenForIntellisense<Shape<typeof Person>>;
type AddressShape = FlattenForIntellisense<Shape<typeof Address>>; Proposal?As the hierarchies start to grow deeper, the problem gets worse while the domain types like Person, and Address are natural caching points for the compiler to heuristically prune the search space. These domain objects are also where we want intellisense to simplify generics. The compiler doesn't know that Person and Address are where it can cache. Instead, what if we give the compiler a hint to know when to fully compute and simplify a type, and then treat that type like an explicit type declaration from now on? From then on, the compiler and IDE treats Person and Address like as if the user had entered them as text in a .ts file. Intellisense would also ignore the underlying templates that defined the template driven domain type, and always show the most simplified form with recursion fully computed. Some options: Option 1: A new intrinsic. Pick a good name, I suck at naming. type PersonShape = ResolveTypeFully<Shape<typeof Person>>;
type AddressShape = ResolveTypeFully<Shape<typeof Address>>; Option 2: Whenever a type alias derives from an interface, then the type is fully inferred, and the compiler fully resolves the type as described above. Since I believe an interface can only extend a type that can be fully computed, then maybe this a natural spot. However, this is very verbose, and makes a weird empty interface in the code without clear purpose. Also, if the compiler ever does relax this requirement, then it breaks. interface PersonShape extends Shape<typeof Person> { } |
I am trying to create a conditional type that converts
to
When I hover on
After
in the below codeit shows
instead of properly converted type.
According to #22011
only more complex types should be aliased, but I always face this issue.
Also for simple types:
TypeScript Version: 2.8.0-dev.201180314
Full Code
Expected behavior:
The tooltip should be shown without aliases.
Actual behavior:
The tooltip is shown with aliases.
The text was updated successfully, but these errors were encountered: