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

Assignment of a partial type member to an intersection type including the partial does not compile in 2.7.0-dev.20180112 #21170

Closed
athasach opened this issue Jan 12, 2018 · 6 comments
Labels
Breaking Change Would introduce errors in existing code Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@athasach
Copy link

TypeScript Version: 2.7.0-dev.20180112

Code

interface IX {
    x: boolean;
}

function fn<T>(obj: T, props: Partial<T>): (T & IX) {
    const xobj = obj as T & IX;
    xobj.x = true;
    for (const p in props) {
        // The following line causes a compilation error:
        // TS2322: Type 'Partial<T>[keyof T]' is not assignable to type '(T & IX)[keyof T]'.
        xobj[p] = props[p];
    }
    return xobj;
}

Expected behavior:
This should compile since the members of Partial<T> is a subset of the members of (T & IX).
This does compile in 2.7.0-dev.20180108.

Actual behavior:
The following error is emitted when compiled:

index.ts(9,9): error TS2322: Type 'Partial<T>[keyof T]' is not assignable to type '(T & IX)[keyof T]'.
  Type 'T[keyof T]' is not assignable to type '(T & IX)[keyof T]'.
    Type 'T' is not assignable to type 'T & IX'.
      Type 'T' is not assignable to type 'IX'.

Related:

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Jan 12, 2018

I think this is appropriate. If IX has a property in common with T, then (IX & T)[keyof T] will result in a property that might be more-derived than in T.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 12, 2018

It is worth nothing that this is a result of #17912. As @DanielRosenwasser noted, the current behavior is correct. since kyeof T can overlap with keyof IX and thus the type of (T & IX)[keyof T] can be more specialized than that of T[keyof T]. in this example if T had a property x :number. the result would be number & boolean which is not just a number.

@mhegazy mhegazy added Working as Intended The behavior described is the intended behavior; this is not a bug Breaking Change Would introduce errors in existing code labels Jan 12, 2018
@mhegazy mhegazy added this to the TypeScript 2.7.1 milestone Jan 12, 2018
@DanielRosenwasser
Copy link
Member

Here's what I think would be a better workaround:

interface X {
    x: boolean;
}

function fn<T extends Partial<X>>(obj: T, props: Partial<T>): (T & X) {
    const xobj = obj;
    xobj.x = true;
    for (const p in props) {
        xobj[p] = props[p];
    }
    return xobj as T & X;
}

Here, you ensure that common properties of T must be compatible with those of X. The type assertion at the end is basically a way for you to say "this is not just T anymore, the properties of X have been set."

@athasach
Copy link
Author

Ah, I overlooked the case of overlapping keys in the type intersection. Thanks for the explanation and the workaround, @DanielRosenwasser and @mhegazy.

@athasach
Copy link
Author

@DanielRosenwasser The workaround suggested fails with strictNullChecks and gives the following compilation error:

index.ts(45,9): error TS2322: Type 'Partial<T>[keyof T]' is not assignable to type 'T[keyof T]'.
  Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'.
    Type 'undefined' is not assignable to type 'T[keyof T]'.

To get past this problem, I cast props[p] to T[keyof T]:

interface X {
    x: boolean;
}

function fn<T extends Partial<X>>(obj: T, props: Partial<T>): (T & X) {
    const xobj = obj;
    xobj.x = true;
    for (const p in props) {
        xobj[p] = props[p] as T[keyof T]; // <---
    }
    return xobj as T & X;
}

@mhegazy
Copy link
Contributor

mhegazy commented Jan 16, 2018

This is another instance of #13195. Partial<T> adds undefined to the domain of the property types. the type system really does not distinguish between an undefined property and one that is optional (meaning either defined or not, but not defined to the value undefined). #13195 tracks that.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Breaking Change Would introduce errors in existing code Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants