Skip to content

TypeScript 4.1+: Generic binding too broad in recursive conditional types#41380

@ddprrt

Description

@ddprrt

TypeScript Version: 4.2.0-dev.20201103

Search Terms: Recursive Conditional types, generics, tuple types

Code

// A test objectconstobj={a: '2',b: {c: 3,d: {e: 'string',f: {g: {h: 4}}}}}asconst// its typetypeStruct=typeofobj;/** * First recursive conditional type  *  * CheckArguments gets  * Obj - A nested object * Arugments - A tuple of keys to go down a nested path *  * The type is recursive, I check if the current argument lists extends keyof Obj --> Then the recursion ends * Otherwise, I check if the first value in the tuple is keyof Obj, infer the rest, and go down the same type * again */typeCheckArguments<Obj,Arguments>=Argumentsextends[keyofObj] ? Obj[Arguments[number]] : Argumentsextends[keyofObj, ...infer U] ? CheckArguments<Exclude<Obj[keyofObj],string|number>,U> : never;/** * Tests, all 👍 */typeFoo=CheckArguments<Struct,['a']>// "2"typeFoo2=CheckArguments<Struct,['b','d','e']>// "string"typeFoo3=CheckArguments<Struct,['b','d','f','g','h']>// 4/** * Second recursive conditional type *  * Arguments gets * Obj - A nested Obj *  * The recursive conditional type creates a union type of possible nested arguments in a tuple * This is based on Anders example from TSConf: https://github.com/ahejlsberg/tsconf2020-demos/blob/master/template/main.ts * (Dotted paths) */typeArguments<Obj>=Objextendsobject ? [keyofObj]|SubArguments<Obj,keyofObj> : never;// A helper typetypeSubArguments<Obj,Key>=KeyextendskeyofObj ? [Key, ...Arguments<Obj[Key]>] : never;// For example, the possible tuples of Struct 👍typeBar=Arguments<Struct>;// equals to this union typetypeBar2=["a"|"b"]|["b","c"|"d"]|["b","d","e"|"f"]|["b","d","f","g"]|["b","d","f","g","h"]/** * So both recursive conditional types work on their own. A problem is once I want to combine * them in a function, where I expect the first argument to bind to a value type within the Arguments union *  * Instead of having just one value type passed to CheckArguments (the one that is bound through the generic Keys), * TypeScript passes all parts of the union to CheckArguments. This leads CheckArguments to return all possible values */declarefunctionget<Objextendsobject,KeysextendsArguments<Obj>>(o: Obj, ...keys: Keys): CheckArguments<Obj,Keys>/**  * Tests 💥 * */constfoo=get(obj,'a')// Should be "2" 😢 constfoo1=get(obj,'b','c')// Should be 3 😢 constfoo2=get(obj,'b','d','e')// Should be "string" 😢 constfoo3=get(obj,'b','d','f','g','h')// Should be 4 😢 

Expected behavior:Keys gets bound to the value type passed as an argument to the function. This value type is then used for CheckArguments

Actual behavior:Keys is the entire union type Arguments<Obj>, not the subset. This leads to CheckArguments returning a too broad return type (and taking very long to evaluate ;-))

Playground Link:Click here

Related Issues: Did not find any.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs InvestigationThis issue needs a team member to investigate its status.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions