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

Typescript fail to unify type variables #45639

Open
xieyuheng opened this issue Aug 30, 2021 · 2 comments
Open

Typescript fail to unify type variables #45639

xieyuheng opened this issue Aug 30, 2021 · 2 comments
Labels
Needs Investigation This issue needs a team member to investigate its status.
Milestone

Comments

@xieyuheng
Copy link


name: Bug
about: Create a report to help us improve TypeScript
title: Typescript fail to unify type variables
labels: ''
assignees: ''

Bug Report

🔎 Search Terms

unify type variables, unification, type inference.

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about Generics.

⏯ Playground Link

Playground link with relevant code

💻 Code

class Var {
  id: number
  name: string

  static counter = 0

  constructor(name: string) {
    this.id = Var.counter++
    this.name = name
  }
}

function v(strs: TemplateStringsArray): Var {
  const [name] = strs
  return new Var(name)
}

type Logical<T> = Var | { [P in keyof T]: Logical<T[P]> }

type List<T> = null | { head: T; tail: List<T> }

function cons<T>(head: Logical<T>, tail: Logical<List<T>>): Logical<List<T>> {
  return { head, tail }
}

function test(element: Logical<string>, list: Logical<List<string>>): void {

}

// This is Ok.
test("a", cons<string>(v`element`, v`tail`))

// But this is not Ok.
test("a", cons(v`element`, v`tail`))

🙁 Actual behavior

When I do not write the annotation for cons, an error occured:

Argument of type 'Logical<List<{ id: { toString: ...; toFixed: ...; toExponential: ...; toPrecision: ...; valueOf: ...; toLocaleString: ...; }; name: { toString: ...; charAt: ...; charCodeAt: ...; concat: ...; indexOf: ...; lastIndexOf: ...; ... 43 more ...; at: ...; }; }>>' is not assignable to parameter of type 'Logical<List<string>>'.
  Type '{ head: Logical<{ id: { toString: ...; toFixed: ...; toExponential: ...; toPrecision: ...; valueOf: ...; toLocaleString: ...; }; name: { toString: ...; charAt: ...; charCodeAt: ...; concat: ...; indexOf: ...; lastIndexOf: ...; ... 43 more ...; at: ...; }; }>; tail: Logical<...>; }' is not assignable to type 'Logical<List<string>>'.
    Type '{ head: Logical<{ id: { toString: ...; toFixed: ...; toExponential: ...; toPrecision: ...; valueOf: ...; toLocaleString: ...; }; name: { toString: ...; charAt: ...; charCodeAt: ...; concat: ...; indexOf: ...; lastIndexOf: ...; ... 43 more ...; at: ...; }; }>; tail: Logical<...>; }' is not assignable to type '{ head: Logical<string>; tail: Logical<List<string>>; }'.
      Types of property 'head' are incompatible.
        Type 'Logical<{ id: { toString: ...; toFixed: ...; toExponential: ...; toPrecision: ...; valueOf: ...; toLocaleString: ...; }; name: { toString: ...; charAt: ...; charCodeAt: ...; concat: ...; indexOf: ...; lastIndexOf: ...; ... 43 more ...; at: ...; }; }>' is not assignable to type 'Logical<string>'.
          Type '{ id: Logical<{ toString: unknown; toFixed: unknown; toExponential: unknown; toPrecision: unknown; valueOf: unknown; toLocaleString: unknown; }>; name: Logical<{ toString: unknown; charAt: unknown; charCodeAt: unknown; concat: unknown; indexOf: unknown; lastIndexOf: unknown; localeCompare: unknown; ... 42 more ...; ...' is not assignable to type 'Logical<string>'.
            Type '{ id: Logical<{ toString: unknown; toFixed: unknown; toExponential: unknown; toPrecision: unknown; valueOf: unknown; toLocaleString: unknown; }>; name: Logical<{ toString: unknown; charAt: unknown; charCodeAt: unknown; concat: unknown; indexOf: unknown; lastIndexOf: unknown; localeCompare: unknown; ... 42 more ...; ...' is not assignable to type 'Var'.
              Types of property 'id' are incompatible.
                Type 'Logical<{ toString: unknown; toFixed: unknown; toExponential: unknown; toPrecision: unknown; valueOf: unknown; toLocaleString: unknown; }>' is not assignable to type 'number'.
                  Type 'Var' is not assignable to type 'number'.

37 test("a", cons(v`element`, v`tail`))
             ~~~~~~~~~~~~~~~~~~~~~~~~~

🙂 Expected behavior

I expect type checker inference the type annotation for me, so I can write:

test("a", cons(v`element`, v`tail`))

Instead of:

test("a", cons<string>(v`element`, v`tail`))
@andrewbranch
Copy link
Member

There are two problems here, both of which might be bugs but I’m not confident that either is. It’s easiest to demonstrate minimal versions of each.

The first is that head and/or tail is acting as an inference source for T in cons. You might expect that neither would be, since each matches the Var constituent of Logical, we shouldn’t have to try to infer to the { [P in keyof T]: Logical<T[P]> } constituent at all. If this were the case, the only remaining inference target would be the return type Logical<List<T>> which would match up with the contextual type Logical<List<string>> from test. However, we clearly are trying to infer from head and/or tail to { [P in keyof T]: Logical<T[P]> } (and we succeed at that), so the inference is fixed before the weaker return type inference can play a role. You can see a simplified example of this happening here.

The second problem is why the error messages are so awful: we are forming reverse mapped types for the properties of primitives. A generic mapped type applied to a primitive returns just the primitive, so you might expect a reverse mapped type inferred from a primitive to just be the primitive as well. This is a bit hard to reason about, as it's hard to come up with a meaningful mapped type that a primitive would be assignable to in the first place, but there is clearly an asymmetry. You can see it in action here.

@andrewbranch andrewbranch added the Needs Investigation This issue needs a team member to investigate its status. label Sep 2, 2021
@andrewbranch andrewbranch added this to the Backlog milestone Sep 2, 2021
@xieyuheng
Copy link
Author

xieyuheng commented Sep 6, 2021

@andrewbranch Thanks for your reply, and your simplified examples :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

2 participants