尽管 IDE 正确推断类型,但为什么 Typescript 不推断泛型类型保护的类型?

Why does Typescript not infer the type for a generic type guard although the IDE infers the type correctly?

Hello, world!

我正在使用 Typescript 和 Webstorm,我正在尝试使用类型保护,这似乎并不完全正确。

这是一个展示我想要实现的目标的例子。也是linked in a playground.

// This is a helper to find the matching class Type
interface correspondsTo<Klass> {}

// An interface for a plain javascript object
interface WizardPojo extends correspondsTo<RealWizard>{
    name?: string;
}

// A derivable abstract class 
abstract class Action {

    performAction() {
        console.log('Go!');
    }

}

// A class which implements the plain interface and also inherits a method
class RealWizard extends Action implements WizardPojo {
    name: string;
    
    performMagic() {
        console.log('✨');
    }
}

// This is handled by typescript as expected
function lameMagic () {

    const harry: RealWizard = new RealWizard();
    harry.performMagic();  // correct
    harry.performAction();  // correct


    const harryPojo: WizardPojo = {};
    harryPojo.performMagic() // error: 'performMagic' does not exist -> correct
    harryPojo.performAction() // error: 'performAction' does not exist -> correct

}



// Now here is something not working as expected
function realMagic(maybe: WizardPojo | RealWizard ) {

    if(isKlassObject(maybe)) {

        // Why is that an error?
        maybe.performMagic();
        maybe.performAction();
        const wizard: RealWizard = maybe;

        // Why does not the type guard handle the cast?
        const asWizard = maybe as RealWizard; 
        asWizard.performMagic();
        asWizard.performAction()
    }

}


/*
 I guess this type guard needs some more magic to work correctly
 The weird thing: Webstorm (without Typescript language service setting set)
 recognizes the type correctly
*/
function isKlassObject<Pojo extends correspondsTo<any>> (obj: Pojo)
    : obj is Pojo extends correspondsTo<infer Klass> ? Klass : unknown {
        return typeof (obj as any).save === 'function';
}

问题是:当我在没有激活 'Typescript language service' 的情况下使用 Webstorm 时,类型保护会生成正确的类型。但是,当我尝试转换代码时,tsc 抛出一个错误,我不知道为什么。

在我的项目中,我有很多 Pojo 接口和 类,它们在 d.ts 文件中自动生成,我只想使用一个通用类型保护函数来确定 Pojo 是否是类对象。我想避免使用 as.

进行显式类型转换

同时使用 Pojo 接口和 Klass 类型对我来说很重要。

你知道如何解决这个类型保护问题吗?

我找到了解决方案 !

我可以使用与 sequelize 相同的技术,在 model attribute types 的类型中使用它。

诀窍是将通用 Klass 类型分配给 correspondsTo 接口中的假 属性。

Playground

// This is a helper to find the matching class Type
interface correspondsTo<Klass> {
    
    /*
     * A dummy variable that doesn't exist on the real object.
     * This is needed so Typescript can infer the type correctly in the type guard. 
     */
    readonly _realKlass?: Klass;
}

// Use the dummy `_realKlass` attribute to infer the correct type 
function isKlassObject<Pojo> (obj: Pojo)
    : obj is Pojo extends correspondsTo<any> ? Pojo['_realKlass'] : Pojo {
        return typeof (obj as any).performAction === 'function';
}

// An interface for a plain javascript object
interface WizardPojo extends correspondsTo<RealWizard>{
    name?: string;
}

// A derivable abstract class 
abstract class Action {

    performAction() {
        console.log('Go!');
    }

}

// A class which implements the plain interface and also inherits a method
class RealWizard extends Action implements WizardPojo {
    name: string;
    
    performMagic() {
        console.log('✨');
    }
}

// both type signatures are ok: either only `WizardPojo`
function pojoMagic(maybe: WizardPojo ) {

    if(isKlassObject(maybe)) {

        // The type is inferred correctly! 
        maybe.performMagic();
        maybe.performAction();
    }

}

// ... or the intersection type `WizardPojo | RealWizard`
function realMagic(maybe: WizardPojo | RealWizard ) {

    if(isKlassObject(maybe)) {

        // The type is inferred correctly! 
        maybe.performMagic();
        maybe.performAction();
    }

}