Skip to content

Compile error using interface with string literal type inside function #8131

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

Closed
OliverJAsh opened this issue Apr 17, 2016 · 2 comments
Closed
Labels
Duplicate An existing issue was already created

Comments

@OliverJAsh
Copy link
Contributor

OliverJAsh commented Apr 17, 2016

TS version 1.8.7

interface Parent {
    children: Array<Child>
}

interface Child {
    type: 'child';
}

// the line below fails
const p1: Parent[] = [1].map(x => ({ children: [{ type: 'child'}] }))

// the line below succeeds
const p2: Parent = ({ children: [{ type: 'child'}] });

Error:

main.ts(66,7): error TS2322: Type '{ children: { type: string; }[]; }[]' is not assignable to type 'Parent[]'.
  Type '{ children: { type: string; }[]; }' is not assignable to type 'Parent'.
    Types of property 'children' are incompatible.
      Type '{ type: string; }[]' is not assignable to type 'Child[]'.
        Type '{ type: string; }' is not assignable to type 'Child'.
          Types of property 'type' are incompatible.
            Type 'string' is not assignable to type '"child"'.

How come p1 fails but p2 succeeds?

@OliverJAsh
Copy link
Contributor Author

If I define the return type of the inner function, it works:

const p1: Parent[] = [1].map((x): Parent => ({ children: [{ type: 'child'}] }))

I would love to understand why TS can't infer this!

@DanielRosenwasser
Copy link
Member

The two relevant processes at work here are type argument inference and contextual typing. The long story short of them is that

  • Type argument inference is the process of figuring out which generics you meant to pass by diving into the arguments in a call.
  • Contextual typing is reaching up the tree from an expression to figure out where that expression might flow, in order to figure out what its type should be.

The way string literal types work is that a string literal expression must be contextually typed by a string literal type (or a union containing a string literal type) in order to type check as a string literal type instead of as string.

This means that in this example:

let x = "foo";
let y: "foo" = "foo";

x will have type string but y will have type "foo" because the programmer made its type explicit and we can tell that "foo" will flow to a variable of type "foo".

In this case, what happens is that type argument inference occurs first. In your call to map, the compiler needs to figure out its type arguments. Recall that map is defined as something close to map<U>(callbackfn: (value: T) => U): U[]; for some Array<T>, so in order to figure out the type of U, we dive into its arguments. The only argument you gave map was the callback, so we try to type check the callback.

At this point, the callback is contextually typed, so we can infer the type of its parameter (which is great - that means you get intellisense that you want). We then kind of jump in and get the type of the returned expression which is { type: string }. That's when you get into a bad spot.

Now, the way you fixed your issue was by adding a return type to your callback. Pretty surprising, right? Basically one thing I didn't mention was that any returned expressions in functions are also contextually typed by the return type if it's known. In the last case it wasn't (we were trying to figure it out by type checking the callback, remember?), but in this case you made it explicit.

The fortunate part is that you can omit your type annotation for p1 since we'll have inferred it on our own.

I believe that if we had #3423, then this would work.

@mhegazy mhegazy added the Duplicate An existing issue was already created label Apr 18, 2016
@mhegazy mhegazy closed this as completed May 20, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants