Skip to content

Typescript 4.1+: Nested template type deduction error #41931

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

Closed
YunHsiao opened this issue Dec 11, 2020 · 3 comments · Fixed by #41972
Closed

Typescript 4.1+: Nested template type deduction error #41931

YunHsiao opened this issue Dec 11, 2020 · 3 comments · Fixed by #41972
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@YunHsiao
Copy link

YunHsiao commented Dec 11, 2020

TypeScript Version: 4.2.0-dev.20201211

Search Terms: nested templates, extract

Code

type Enum = Record<string, string | number>;
type TypeMap<E extends Enum> = { [key in E[keyof E]]: number | boolean | string | number[] };

class BufferPool<E extends Enum, M extends TypeMap<E>> {
    setArray1<K extends keyof M>(_: K, array: Extract<M[K], ArrayLike<any>>) {
        array.length; // correct
    }
    setArray2<K extends E[keyof E]>(_: K, array: Extract<M[K], ArrayLike<any>>) {
        array.length; // compile-time error, which is NOT expected: 'length' does not exist on type 'Extract<M[K], ArrayLike<any>>'
    }
}

Expected behavior:
Code compiles without error.

Actual behavior:
Compiler throws Property 'length' does not exist on type 'Extract<M[K], ArrayLike<any>>'.
Behavior starts at 4.1 I think, the exact same code works prior to that.

Note that if we change the definition on the first line to:

type Enum = Record<string, string | number | any>;

Then it works fine.

Playground Link:
here

Related Issues:
maybe related to #41380, if so this is a relatively simpler test case.

@YunHsiao
Copy link
Author

YunHsiao commented Dec 11, 2020

The test case is extracted from our main codebase, boiled down to its essence.
We got a little creative about type checking the memory pool system, and it has been incredibly helpful when using these low-level interfaces:

enum Enum1 {
    data1,
    data2,
    data3,
}
interface TypeMap1 extends TypeMap<typeof Enum1> {
    [Enum1.data1]: number;
    [Enum1.data2]: boolean;
    [Enum1.data3]: number[];
}
const pool = new BufferPool<typeof Enum1, TypeMap1>();

pool.setArray2(Enum1.data3, [3]); // correct
pool.setArray2(Enum1.data2, [3]); // compile-time error, which is expected: 'number' is not assignable to parameter of type 'never'
pool.setArray2(Enum1.data3, 3); // compile-time error, which is expected: 'number' is not assignable to parameter of type 'number[]'

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Dec 11, 2020
@ahejlsberg
Copy link
Member

This looks to be caused by #40971. Once we get to seven nested levels of constraint exploration, the depth limiter cuts off further exploration. Here's the progression:

0: Extract<M[K], ArrayLike<any>>
1: Extract<M[E[keyof E]], ArrayLike<any>>
2: Extract<M[E[string | number | symbol]], ArrayLike<any>>
3: Extract<M[E[string]], ArrayLike<any>> | Extract<M[E[number]], ArrayLike<any>> | Extract<M[E[symbol]], ArrayLike<any>>
4: Extract<M[E[string]], ArrayLike<any>>
5: Extract<M[string | number], ArrayLike<any>>
6: Extract<M[string], ArrayLike<any>> | Extract<M[number], ArrayLike<any>>
7: Extract<M[string], ArrayLike<any>

The issue is that all of the constraint breakdown happens in the check type of the conditional type, but all the depth limiter sees is a deep sequence of instantiations of the same conditional type.

@ahejlsberg ahejlsberg added Bug A bug in TypeScript and removed Needs Investigation This issue needs a team member to investigate its status. labels Dec 15, 2020
@ahejlsberg ahejlsberg added this to the TypeScript 4.2.0 milestone Dec 15, 2020
@ahejlsberg
Copy link
Member

We're probably a bit too aggressive in cutting off constraint exploration after only five levels, particularly given this fairly reasonable example. Shouldn't be too hard to increase the threshold.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants