tassign icon indicating copy to clipboard operation
tassign copied to clipboard

Interfaces with all optional fields mess things up

Open fljot opened this issue 8 years ago • 2 comments

Hello. Bad news: I found use case when your implementation fails to notify about incompatible types =( Good news: there is solution =)

In our project we had some interface automatically generated (from protobuf files), so it happened that one interface had all fields optional.

Here's demo (reproducible on https://www.typescriptlang.org/play/index.html):

function tassign<T extends U, U>(target: T, ...source: U[]): T {
    return Object.assign({}, target, ...source);
}

// via https://stackoverflow.com/a/41159188
function tassign2<T, K extends keyof T>(target: T, ...source: Pick<T, K>[]): T {
    return Object.assign({}, target, ...source);
}

interface MyState {
    field: string;
}

const state: MyState = {
    field: "value"
};

interface ProtocolThing {
    all?: string;
    fields?: number;
    optional?: boolean;
}
const valueFromProtocol: ProtocolThing = {
    all: 'fields are optional'
};

// No error :( it assumes everything is fine
tassign(state, {
    field: valueFromProtocol
});

// Argument of type '{ field: ProtocolThing; }' is not assignable to parameter of type 'Pick<MyState, "field">'.
//  Types of property 'field' are incompatible.
//    Type 'ProtocolThing' is not assignable to type 'string'.
tassign2(state, {
    field: valueFromProtocol
});

fljot avatar May 29 '17 18:05 fljot

Also issue with union types:

function tassign<T extends U, U>(target: T, ...source: U[]): T {
    return Object.assign({}, target, ...source);
}

// via https://stackoverflow.com/a/41159188
function tassign2<T, K extends keyof T>(target: T, ...source: Pick<T, K>[]): T {
    return Object.assign({}, target, ...source);
}

type MyUnionType = 'foo' | 'bar';

interface MyState {
    unionField: MyUnionType;
}

const state: MyState = {
    unionField: 'foo'
};

// No error :( it assumes everything is fine
tassign(state, {
    unionField: 'brr'
});

// Argument of type '{ unionField: "brr"; }' is not assignable to parameter of type 'Pick<MyState, "unionField">'.
//   Types of property 'unionField' are incompatible.
//     Type '"brr"' is not assignable to type 'MyUnionType
tassign2(state, {
    unionField: 'brr'
});

fljot avatar May 31 '17 08:05 fljot

@fljot hey - I don't keep my eye on this repo very much, and didn't notice your issue - we were debating deprecating this repo at some point as TypeScript added features that might make this obsolete, but liking what you've done with tassign2 - will look into updating this with your changes. Just need to see if this will be a possibly breaking change for some people and if it'd be a major bump, or a minor.

Thanks for your insight, and sorry for the long delay in response.

e-schultz avatar Jul 25 '17 19:07 e-schultz