简体   繁体   中英

Generic type parameter inference priority in TypeScript

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 { }

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM