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

Type checking mismatch between anonymous and named object #27938

Closed
DorianGrey opened this issue Oct 17, 2018 · 3 comments
Closed

Type checking mismatch between anonymous and named object #27938

DorianGrey opened this issue Oct 17, 2018 · 3 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@DorianGrey
Copy link

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:
#27497
#26045
#17667

@jack-williams
Copy link
Collaborator

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);

@MartinJohns
Copy link
Contributor

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.

@DorianGrey
Copy link
Author

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.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Oct 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants