简体   繁体   中英

Regressive generic type inference

Demo code in the TypeScript playground

I'm trying to create a more dynamic implementation of the decorator pattern using the ES6's Proxy object .

The general idea is that I have an IService interface and a BaseService abstract class. The abstract class has an origin property that's an instance of IService and all the unimplemented calls are forwarded to this object. BaseService exposes a static method wrap that creates a new instance of the decorator and sets it's origin to the service instance provided as a parameter.

Functionality of this approach is not a problem, the problem comes when I try to specify the types. For some reason, when I try to decorate the MongoService with a VerificationService , the origin type is reduced to IService and the return value is of type VerificationService & IService instead of the desired VerificationService & MongoService .

My question is whether this is intended behavior because of some covariance/contra-variance issues the code could lead to or if it's a bug and the compiler just doesn't know that the type of the parameter is MongoService. Please notice that when I try to decorate an instance of a class that doesn't add any properties, the type is correctly inferred (line 37)

I am not 100% sure why this happens, but if you look at the type of wrapper and the constructor for VerificationService (inherited from BaseService ) we get an idea of why this might be. The parameter to the constructor is ISerivce not a generic type, so the simplest assumption would be to infer U to IService in wrap , and the second parameter to wrap satisfies IService so it all works fine, no reason to look further.

The simplest solution would be to change the type of wrapper , since the argument can be any service anyway:

static wrap<U extends IService, T extends BaseService>(this: { new(origin: IService): T }, origin: U): T & U {
    return new Proxy(new this(origin) as T & U, {
        get(target: T & U, prop: keyof T | keyof U) {
            if (isOverriden(target, prop)) return target[prop];
            return target.origin[prop];
        }
    })
}
// All work as expected
const serviceInferedIncorrectly = VerificationService.wrap(new MongoService()); // VerificationService & MongoService
const serviceInferedCorrectly = VerificationService.wrap(new SimpleService()); // VerificationService & SimpleService
const serviceSpecified = VerificationService.wrap<MongoService, VerificationService>(new MongoService()); // VerificationService & MongoService

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