microsoft / microsoft/TypeScript

Inner inference doesn't inherit the contextual type from the outer one in argument position

Offen
#52,864 2 Kommentare 1 Reaktion 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bug Domain: check: Type Inference
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.4k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

Bug Report

🔎 Search Terms

contextual type nested inference

🕗 Version & Regression Information
  • This is the behavior in every version I tried
⏯ Playground Link

Playground link with relevant code

💻 Code
const matcher = Symbol("@ts-pattern/matcher");

type MatcherProtocol<input> = {
  match: <I>(value: I | input) => void;
};

interface Matcher<input> {
  [matcher](): MatcherProtocol<input>;
}

type Pattern<a> =
  | Matcher<a>
  | (a extends readonly [any, ...any]
      ? { readonly [index in keyof a]: Pattern<a[index]> }
      : a extends object
      ? { readonly [k in keyof a]: Pattern<a[k]> }
      : a);

type Match<i> = {
  with<p extends Pattern<i>>(pattern: p): void;
};

declare function match<input>(value: input): Match<input>;
declare function union<input, ps extends [Pattern<input>, ...Pattern<input>[]]>(
  ...patterns: ps
): Matcher<input>;
declare function when<input, p extends (value: input) => unknown>(
  predicate: p
): Matcher<input>;

// those have broken inferences
match<"a" | "b">("a").with(union("a"));

match<"a" | "b">("a")
  // @ts-expect-error
  .with(union("this is wrong"));

match<"a" | "b">("a").with(
  // this should not be an error, since `x` should be `'a' | 'b'` and not `unknown`
  when((x) => { let a: "a" | "b" = x; return x; })
);

// those have correct inferences
match<{ type: "a" | "b" }>({ type: "a" }).with({
  type: union("a"),
});

match<{ type: "a" | "b" }>({ type: "a" }).with({
  // @ts-expect-error
  type: union("this is wrong"),
});

match<{ type: "a" | "b" }>({ type: "a" }).with({
  type: when((x) => { let a: "a" | "b" = x; return x; }),
});
🙁 Actual behavior

In cases with match<"a" | "b">("a").with(...) the inference is broken whereas in cases with match<{ type: "a" | "b" }>({ type: "a" }).with(...) things work as expected.

🙂 Expected behavior

Both cases should work the same as the position in the with's argument should not matter.


This is a distilled case from ts-pattern by @gvergnaud . The real thing (equivalent of this repro case) can be tested out in this TS playground.

In the broken case, in inferTypeParameters (for the nested call):

In the working case, within the same inferTypeParameters we get those:

  • inferenceTargetType -> Matcher<input>
  • contextualType -> Pattern<"a" | "b">
  • instantiatedType: Pattern<"a" | "b">

It's worth noting down that outerMapper is the same in both cases (p -> never) but in the working case it's simply not used because the contextualType has no type variables so instantiateTypeWithAlias returns early with the supplied argument here (Pattern<"a" | "b">).

Looking at the previous steps we can learn that the returned contextualType here is better in the working case because getContextualTypeForObjectLiteralElement checks getApparentTypeOfContextualType and there:

Thanks to that the getTypeOfPropertyOfContextualType can return Pattern<"a" | "b"> here in the getContextualTypeForObjectLiteralElement.

It's also worth noting that the current version of ts-pattern works OK because we can "observe" this whole silentNeverType in the userland (which, I think, shouldn't be possible) here and we can return there what we originally expected there to be computed for us. The minimal repro case from this issue with this "hack" being applied can be found here

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginnen Sie in src/compiler/checker.ts bei inferTypeParameters und der damit verknüpften Behandlung kontextueller Typen rund um getContextualTypeForObjectLiteralElement. Reproduzieren Sie das Problem mit dem bereitgestellten TypeScript Playground-Code und verfolgen Sie dann, warum der verschachtelte Aufruf unknown statt Pattern<'a' | 'b'> erhält; abgeschlossen ist die Arbeit, wenn sowohl primitive als auch Objektargumentformen konsistent inferiert werden, ohne den Userland-Workaround.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
typescript
Bereich
compilers
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
42/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.