microsoft / microsoft/TypeScript

Type inference lost after spreading array with `ArrayLike`

Open
#58,286 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Domain: check: Type Inference Help Wanted Possible Improvement
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

🔎 Search Terms

array inference, type inference, ArrayLike, lodash compact, _.compact

🕗 Version & Regression Information
  • This changed between versions 4.9.5 and 5.0.4
⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.0.4#code/C4TwDgpgBAsiAq4IB55QgD2BAdgEwGcoBXHAaxwHsB3HAPigF4oBvAKCigFsQBpCEAC4o8ANwduIAGKkAxsACWlHMIAUAQwBOAcwCMw+AEomDAG6UFecQF9xbWcoLAo23BE3rsTKKsPCAgpoeIMhwiJDITpoKONp0DIwM7JyaEMDEmjhQgcEAdABmmpRcqixQADa42sAAFsIArFDWhrlc6mChCEiRwNGx8aqqAPoANFAxeJjGiawSKWkZWcmcKzz8QlAABgAkLBOY1psjc6vScorKalp600knK1Cp6ZniD5zWxw+2Es02dg44JxQLTBAAyCjIEAAkjh8u5cLIIDIcPIlFlmKh0FhcIQSOQqLQ6KoJCCAkF1CBwZDMQAfKA4YjlcpQOmkSb5GIQPAsqD5dTlAjQOkAImFPIADAw6QymTy2RAOTguWw-CIANoAXRMs3mzyymr+bHsjmcIIpVOhsPcqRR0GYZspEMtcJtiORqOUqjVEjKawEwmFugATABmYVjHjui4qHymfnECC3VjWJqfKC9BNQABkWdYknWAYALPUAGzhyRRtFqOPlBNJlgpj4SPkC6A5vN+jbC4tliNnFHR6vxxPahupiS5SeuJUebC+NMAegX6aQ4ytQQgiKglHyFfOaPGRHKlCcEgzbdzvr4-qg3dL5cj+8useH9cbaZbguzl-zN7vvb3AcqxfWsRxmMcm04K8C1vHsH37D0Y1UGs61Hd82A1Qx-hNYFyRAGEXQRJEn3RKBMUwbB8CIUgKBoehiU4Ulsjw2l6UZZlWXwBVOW5OlPyFW8xTpSUeRlDi8XZHiVQMTVRwkJ5FigA02G+Y1AVNPCCPhW1vAdLTXWIoDPW9KDfy7YMwz7Stn2Q180PHThz2-Dtry7OCrJIodQLfBzeX5L922gv93MAxCvNQ8D0M4SdcmndxPAgecJCXFdIDXF1Ui3HdQujQ8oEhMBgDPTRM0Csyi3vDyjKQlCwKSKK-NbZygrcyqcuA2zvPsyCXJg-94Osmq7Mi8dMPEIA

💻 Code
type MyType<T extends unknown> = {
  myKey: T;
  myFunction: (arg1: T) => void;
};

const generate = (): Array<MyType<string>> => {
  return Array.from({ length: 5 }).map<MyType<string>>((_, index) => {
    return {
      myKey: `${index}`,
      myFunction: (arg1) => {
        return;
      },
    };
  });
};

const arrayLikeInferenceFunction = <T extends unknown>(
  arr: ArrayLike<T | null | undefined | false | "" | 0> | null | undefined
): T[] => {
  return [];
};

const arrayLikeInference = arrayLikeInferenceFunction([
  { myKey: "123", myFunction: (value) => {} }, // value is correctly inferred as string
  true && { myKey: "456", myFunction: (value) => {} },
  false && { myKey: "456", myFunction: (value) => {} },
  ...generate(),
  // type inference of myFunction is lost
  true && { myKey: "456", myFunction: (value) => {} }, // value is inferred as any
  false && { myKey: "456", myFunction: (value) => {} },
  { myKey: "456", myFunction: (value) => {} },
]);

const arrayInferenceFunction = <T extends unknown>(
  arr: Array<T | null | undefined | false | "" | 0> | null | undefined
): T[] => {
  return [];
};

const arrayInference = arrayInferenceFunction([
  { myKey: "123", myFunction: (value) => {} }, // value is correctly inferred as string
  true && { myKey: "456", myFunction: (value) => {} },
  false && { myKey: "456", myFunction: (value) => {} },
  ...generate(),
  // type inference of myFunction is kept
  true && { myKey: "456", myFunction: (value) => {} }, // value is correctly inferred as string
  false && { myKey: "456", myFunction: (value) => {} },
  { myKey: "456", myFunction: (value) => {} },
]);
🙁 Actual behavior

The typings of the arguments of myFunction are lost after the array spread when using ArrayLike. When using Array, the type inference is kept.

🙂 Expected behavior

The arguments of myFunction should be inferred as string

Additional information about the issue

I have opened a similar issue in the DefinitelyTyped repository for the lodash typings and they suggested opening an issue in this repository. https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/69410

The issue originated from using lodash.compact() with code similar to the code below:

import _ from "lodash";

type MyType<T extends unknown> = {
  myKey: T;
  myFunction: (arg1: T) => void;
};

const generate = (): Array<MyType<string>> => {
  return Array.from({ length: 5 }).map<MyType<string>>((_, index) => {
    return {
      myKey: `${index}`,
      myFunction: (arg1) => {
        return;
      },
    };
  });
};

const lodashInferred = _.compact([
  { myKey: "123", myFunction: (value) => {} }, // infers value as string
  true && { myKey: "456", myFunction: (value) => {} },
  false && { myKey: "456", myFunction: (value) => {} },
  ...generate(),
  { myKey: "123", myFunction: (value) => {} },  // infers value as any
  true && { myKey: "456", myFunction: (value) => {} },
  false && { myKey: "456", myFunction: (value) => {} },
]);

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

Start with the linked Playground reproduction using TypeScript 5.0.4 and compare the ArrayLike and Array cases around the spread of generate(). Trace the type inference behavior for the callback parameters; done means the arguments of myFunction are inferred as string after the ArrayLike spread, with the reported regression covered by a test.

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.