microsoft / microsoft/TypeScript

Conditional + annotation causes erroneous typing

Open
#39,409 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Needs Investigation
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

TypeScript Version: 33.9.4 & 4.0.0@beta

Search Terms: Type annotation wrongly overrides generic with errors

Code

const createMatrix = <D extends number, T>(
    dimensions: D,
    initialValues: T | null = null
): Matrix<D, T> => {
    const currentDimensionLength = dimensions;
    const remainingDimensions = dimensions - 1;
    const needsRecursion = remainingDimensions > 0;

    const currentMatrix = Array(currentDimensionLength).fill(initialValues);

    const finalMatrix = needsRecursion
        ? currentMatrix.map(() =>
              createMatrix(remainingDimensions, initialValues)
          )
        : currentMatrix;

    return finalMatrix as Matrix<D, T>;
};
type Matrix<D extends number, T> = D extends 1
    ? T[]
    : D extends 2
    ? T[][]
    : D extends 3
    ? T[][][]
    : any[][][][];

Expected behavior:
Adding type annotations to variables should be compatible with the functions return signature, causing no type errors

Actual behavior:
Assigning an annotated variable to the return value of the function causes an error. The generic T is wrongly inferred to be any of array where depth < D where it should be inferred to only the base type of the nested array. Removing the annotation solves the problem but removes type safety when variable assignment happens after creation.

The problem seems to be that (when annotated) the type is inferred to be the union of each type in Matrix that's preceding the conditional that returns true.

const n1: number[] = createMatrix(1); // <- works - generic T is set to `number`
const n2: number[][] = createMatrix(2); // <- doesn't work - generic T is set to `number | number[]`
const n3: number[][][] = createMatrix(3); // <- doesn't work - generic T is set to `number | number[] | number[][]`

const n4 = createMatrix(2); // <- regular inference works - generic T is `unknown`, return type is unknown[][]
const n5: number[][] = createMatrix(2, 0); // <- works when - initialValues is set - generic T is `number`, return type is number[][]
const n6: unknown[][] = createMatrix(2); // <- works fine, as type T is unknown. A more specific annotation should constrain `unknown` to `number`

Playground Link: Link

Related Issues:
None

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Use the linked Playground reproduction with TypeScript 3.9.4 and 4.0.0-beta, focusing on the annotated n2 and n3 calls versus the unannotated and explicitly initialized cases. Trace the type inference and conditional return-type behavior until the source of the accumulated union is identified; done means the annotated calls produce the expected nested array types without errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.