Skip to content
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

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

Open
Andarist opened this issue Feb 20, 2023 · 2 comments Β· May be fixed by #52866
Open

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

Andarist opened this issue Feb 20, 2023 · 2 comments Β· May be fixed by #52866
Labels
Bug A bug in TypeScript
Milestone

Comments

@Andarist
Copy link
Contributor

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

@gvergnaud
Copy link

gvergnaud commented Feb 20, 2023

Thanks @Andarist for looking into this!

I'm preparing a new version of TS-Pattern that uses the const T type parameter modifier to get even better inference and it turns out that const T doesn't work with our little IsNever hack, so I would really like to see this fixed in the type checker :)

@Andarist
Copy link
Contributor Author

I'm preparing a new version of TS-Pattern that uses the const T type parameter modifier to get even better inference and it turns out that const T doesn't work with our little IsNever hack, so I would really like to see this fixed in the type checker :)

This is interesting! could you prepare a repro case for this problem? :P I wonder how const modifier affects this stuff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
3 participants