Typescript: Type checking mismatch between anonymous and named object

Created on 17 Oct 2018  路  3Comments  路  Source: microsoft/TypeScript


I'm trying to create a helper function to simplify self-assignment of values from an object provided by my applications rxjs + redux based store.
The helper function should cause a compile error if the object picked from the store contains properties that do not exist on the target object. Code and details can be found below.


TypeScript Version: 3.1.3
All available flags extending strictness are enabled except strictPropertyInitialization, and suppressImplicitAnyIndexErrors suppresses some errors.


Search Terms: type checking

Code
A stripped-down example of what I'm trying to achieve:

class A {
    gammel: string = "42";

    methodB() {
        // [1] Fails correctly for property "test" - does not exists on class.
        selfExtend(this)({
            gammel: "5",
            test: 14
        });

        // [2] Does not fail, even though it's the same data.
        const o = {
            gammel: "5",
            test: 14
        };
        selfExtend(this)(o);
    }
}

// Cheap trick to get around an issue with Partial<this>,
// which is acceptable in our case.
type Identity<T> = {
    [P in keyof T]: T[P];
};

export function selfExtend<T extends object>(target: Identity<T>) {
  return function(data: Partial<Identity<T>> | null) {
    if (data) {
        // In case the type-check is correct, the keys from `Object.getOwnPropertyNames`
        // should exist in both `data` and `target` - thus, assignment is safe. 
        Object.getOwnPropertyNames(data).forEach(k => (target[k] = data[k]));
    }
  };
}

Expected behavior:
In the code above, both case [1] and [2] (see comments) should cause an error that test does not exist on the target object.

Actual behavior:
Only case [1] fails, case [2] is accepted by the type checker. I.e.: Assigning the malformed value to a variable before using it causes the type check to fail, while providing the object anonymously works fine.

Playground Link:
Playground can be found here.

Related Issues:
Found some with similar, but not identical problems:
https://github.com/Microsoft/TypeScript/issues/27497
https://github.com/Microsoft/TypeScript/issues/26045
https://github.com/Microsoft/TypeScript/issues/17667

Question

Most helpful comment

I think this is down to excess property checks. In [1] the literal has a contextual type from the function parameter. In [2] the literal is created without a contextual type, so there are no excess properties to look for. If you put an annotation on the declaration of o you get a similar error.

        const o: Partial<Identity<A>> = {
            gammel: "5",
            test: 14
//          ^^^^^^^^
//          Type '{ gammel: string; test: number; }' is not assignable to type 'Partial<Identity<A>>'.
//            Object literal may only specify known properties, and 'test' does not exist in type 'Partial<Identity<A>>'.

        };
        selfExtend(this)(o);

All 3 comments

I think this is down to excess property checks. In [1] the literal has a contextual type from the function parameter. In [2] the literal is created without a contextual type, so there are no excess properties to look for. If you put an annotation on the declaration of o you get a similar error.

        const o: Partial<Identity<A>> = {
            gammel: "5",
            test: 14
//          ^^^^^^^^
//          Type '{ gammel: string; test: number; }' is not assignable to type 'Partial<Identity<A>>'.
//            Object literal may only specify known properties, and 'test' does not exist in type 'Partial<Identity<A>>'.

        };
        selfExtend(this)(o);

That is precisely what is happening. By omittig the type annotation the compiler will infer the type for o, and that type is: { gammel: string; test: number; }. This has no excess properties. When passing it to the selfExtend(this) it will then just check if the argument o is compatible with the argument type, which it is.

Thanks for pointing me in this direction - didn't have the excess property check in mind.
Tried to minimize to code used for that assignment as much as possible, but it seems this need some additional checks or a different way to properly determine the types and achieve useful errors.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

quantuminformation picture quantuminformation  路  273Comments

rwyborn picture rwyborn  路  210Comments

kimamula picture kimamula  路  147Comments

tenry92 picture tenry92  路  146Comments

RyanCavanaugh picture RyanCavanaugh  路  205Comments