Generic type parameter inference priority in TypeScript

若如初见. 提交于 2019-12-13 01:56:12

问题


I have the following class decorator factory accepting an initializer function as its argument. In this initializer function I would like to return an instance corresponding to the involved class type (or that of a derived one):

function JsonObject<T>(initializer: (json: any) => T) {
    return function (target: { new (...args: any[]): T }) {
        // ...
    }
}
@JsonObject(function (json) {
    return new Animal();
})
class Animal {
    name: string;
}

Returning an instance of the exact class (as above) works correctly, but...

Short Version

Returning an instance of a derived class does not. I can return a base instance, but not a derived one. I can't, for example, return a Cat:

@JsonObject(function (json) {
    return new Cat(); // Error.
})
class Animal{
    name: string;
}

class Cat extends Animal {
    color: string;
}

... even though a Cat is an Animal. I can however, return an Animal instead of a Cat (which is wrong, as an Animal is not necessarily a Cat), for a Cat:

@JsonObject(function (json) {
    return new Animal(); // OK, but it shouldn't be
})
class Cat extends Animal {
    color: string;
}

Long Version

The JsonObject Decorator Factory

The JsonObject function is analogous to a function with a generic type parameter T, accepting a callback function returning a T as its argument, and returning a function accepting a newable type returning a T. The latter (the returned function) is the class decorator itself, obviously.

The compiler won't allow me to -- for example -- return a string from this initializer function (or any other mismatching type), which is as it should be.

Problem with Subtypes

However, the above type signature behaves in the exact opposite way when subtypes are used: from the initializer function I can return a base type, but not a derived type -- the following error occurs when used on the middle class of a 2-step inheritance pattern:

@JsonObject(function (json) {
    // Test case: return a base type.
    return new Animal(); // OK, but it shouldn't be: an 'Animal' is not a 'Cat'
})
@JsonObject(function (json) {
    // Test case: return an exact corresponding type.
    return new Cat(); // OK, as it should be
})
@JsonObject(function (json) {
    // Test case: return a derived type.
    return new Kitty(); // <-- Error, but it should be OK, a Kitty *is* a Cat
})
class Cat extends Animal {
    color: string;
}

class Kitty extends Cat {
    cutenessFactor: number;
}

Error: Type 'Cat' is not assignable to type 'Kitty'. Property 'cutenessFactor' is missing in type 'Cat'.

I believe I've pinpointed the origin of the error, it is caused by the compiler when inferring generics: the generic type parameter T is inferred from the "T" in initializer: (json: any) => T, which means the error is caused by the JsonObject function having the generic type Kitty, to which Cat is obviously not assignable as such, and therefore the class decorator cannot be used on Cat in this case.

I would like it so that T is instead inferred from the "return" type of target, which would solve my problem. How could I accomplish this?

Of course, when I explicitly specify the generic type parameter, it works flawlessly (but this carries redundant information):

@JsonObject<Cat>(function (json) {
    return new Kitty(); // OK, since type 'Kitty' is assignable to type 'Cat'
})
class Cat extends Animal { }

回答1:


Did you mean to chain the decorators or did you mean this:

function JsonObject<T>(initializer: (json: any) => T) {
    return function (target: { new (...args: any[]): T }) {
        return null;
    }
}


@JsonObject(function (json) {
    return new Foo();
})
class Foo {
    foo: string;
}

@JsonObject(function (json) {
    // Test case: return an exact corresponding type.
    return new Bar(); // OK, as it should be
})
class Bar extends Foo {
    bar: string;
}

@JsonObject(function (json) {
    // Test case: return a derived type.
    return new Baz(); // Ok
})
class Baz extends Bar {
    baz: string;
}

If you meant the above ^ it compiles fine



来源:https://stackoverflow.com/questions/36835363/generic-type-parameter-inference-priority-in-typescript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!