Skip to content

Intersection types involving constrained generic type placeholders do not include members of the placeholder type #28752

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
jumpinjackie opened this issue Nov 30, 2018 · 3 comments
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@jumpinjackie
Copy link

TypeScript Version: 3.2.1

Search Terms: intersection / intersection placeholder

Code

interface IFoo {
    name: string;
}

interface IBar {
    aNumber: number;
    anArray: string[];
}

function test<T extends IFoo>() {
    const obj: T & IBar & { anotherProp: boolean } = {
        name: "Testing", //Errors here (TS 3.2.1). Not in TS 3.1.4
        aNumber: 1,
        anArray: ["a", "b", "c"],
        anotherProp: true
    };
}

function test2<T extends IFoo = IFoo>() {
    const obj: T & IBar & { anotherProp: boolean } = {
        name: "Testing", //Errors here (TS 3.2.1). Not in TS 3.1.4
        aNumber: 1,
        anArray: ["a", "b", "c"],
        anotherProp: true
    };
}

Expected behavior:

name being a property of the generic placeholder T (because it's constrained to something that must be or extend from IFoo) should be a valid property of the above intersection type.

Actual behavior:

TS compiler error on the name property (Object literal may only specify known properties and 'name' does not exist ... forgot the actual TS error code here).

This did not happen in TS 3.1.4 (which I had upgraded from) and possibly older versions too

Playground Link: (https://bit.ly/2rfqphb) Had to use bit.ly to shorten the playground link because playground because the < and > characters mess up markdown link formatting here.

Related Issues:

@ahejlsberg
Copy link
Member

When the target of an assignment is an intersection that includes higher order types (such as type parameters) and the source has no higher order types, the assignment will always be an error. So, we definitely shouldn't be doing excess property checking in that case.

@ahejlsberg
Copy link
Member

So, what I'm saying is that (a) your example is definitely an error, but (b) the way we report the error is confusing. To understand why the example is an error, consider this simple case:

function foo<T extends { name: string }>() {
    let obj: T = { name: 'test' };  // Error
}

This is an error because T may refer to a type that has additional properties beyond name, and those properties are obviously missing in the assignment.

@jumpinjackie
Copy link
Author

That is disappointing, but understandable.

In case anyone else has this problem, I found 2 workarounds.

  1. Use Pick<> to be explicit about what properties of the constrained T you're after
interface IFoo {
    name: string;
}

interface IBar {
    aNumber: number;
    anArray: string[];
}

function test<T extends IFoo>() {
    const obj: Pick<T, "name"> & IBar & { anotherProp: boolean } = {
        name: "Testing", //No more errors
        aNumber: 1,
        anArray: ["a", "b", "c"],
        anotherProp: true
    };
}

function test2<T extends IFoo = IFoo>() {
    const obj: Pick<T, "name"> & IBar & { anotherProp: boolean } = {
        name: "Testing", //No more errors
        aNumber: 1,
        anArray: ["a", "b", "c"],
        anotherProp: true
    };
}
  1. Rework the code so that you spread the result of a function that returns T
interface IFoo {
    name: string;
}

interface IBar {
    aNumber: number;
    anArray: string[];
}

function test<T extends IFoo>(factory: () => T) {
    const obj: T & IBar & { anotherProp: boolean } = {
        aNumber: 1,
        anArray: ["a", "b", "c"],
        anotherProp: true,
        ...factory()
    };
}

function test2<T extends IFoo = IFoo>(factory: () => T) {
    const obj: T & IBar & { anotherProp: boolean } = {
        aNumber: 1,
        anArray: ["a", "b", "c"],
        anotherProp: true,
        ...factory()
    };
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests

4 participants