简体   繁体   中英

Return inferred generic type

I want to return the inferred type from a function on a class, but the return type is of the extended generic type, not the actual result of the function. I want to instead return the inferred type of the function.

I tried a few things surrounding wrapping and unwrapping, but was unable to get those to work.

Problem : newDocumentSet is of type IDocumentSet<DocumentTypes, IContact, never> , not the type returned in the create functions extend parameter

Notes : I would like the create function to return the extended instance, not the interface of IDocumentSet . It seems like this bit (create<TExtension extends IDocumentSet<TDocumentType, TEntity, TExtraExclusions>>) is screwing it up, I am just not sure how to enforce a return type that extends IDocumentSet<TDocumentType, TEntity, TExtraExclusions> yet returns the extended type not IDocumentSet<TDocumentType, TEntity, TExtraExclusions> .

Goal : I want to create the ability to return the extended class from the create function, not the interface.

Example is below and here in this fiddle

Shorter Example

I also found this post about wrapping and unwrapping, but was unable to get it to work for my needs

This example is from a larger code base, code surrounding TExtraExclusions has been omitted to keep this example smaller, yet the generics remain.


export interface IDocumentSetBase<TDocumentType extends string> {

    get DocumentType(): TDocumentType;

    empty(): Promise<void>;
}

interface IDocumentSet<TDocumentType extends string, TEntity extends IDbRecord<TDocumentType>, TExtraExclusions extends (keyof TEntity) = never> extends IDocumentSetBase<TDocumentType> {
    info(): boolean
}

interface IDbRecord<TDocumentType> extends IDbAdditionRecord<TDocumentType> {
    readonly _id: string;
    readonly _rev: string;
}

interface IDbAdditionRecord<T> {
    readonly DocumentType: T;
}

export class DocumentSet<TDocumentType extends string, TEntity extends IDbRecord<TDocumentType>, TExtraExclusions extends (keyof TEntity) = never> implements IDocumentSet<TDocumentType, TEntity, TExtraExclusions> {

    private _documentType: TDocumentType;

    get DocumentType() { return this._documentType; }

    constructor(documentType: TDocumentType) {
        this._documentType = documentType;
    }

    info() {
        return true;
    }

    async empty() {

    }
}

class Builder<TDocumentType extends string, TEntity extends IDbRecord<TDocumentType>, TExtraExclusions extends (keyof TEntity) = never> {

    private _documentType: TDocumentType;

    constructor(documentType: TDocumentType) {
        this._documentType = documentType;
    }

    create<TExtension extends IDocumentSet<TDocumentType, TEntity, TExtraExclusions>>(extend: (Instance: typeof DocumentSet<TDocumentType, TEntity, TExtraExclusions>, documentType: TDocumentType) => TExtension = w => (w as any) as TExtension) {

        const result = extend(DocumentSet<TDocumentType, TEntity, TExtraExclusions>, this._documentType);

        return result;
    }
}

enum DocumentTypes {
    Contacts = "Contacts"
}

interface IContact extends IDbRecord<DocumentTypes> {
    firstName: string;
    lastName: string;
    address: string;
    phone: string;
}

const builder = new Builder<DocumentTypes, IContact>(DocumentTypes.Contacts);

const newDocumentSet = builder.create((Instance, documentType) => {
    return new class extends Instance {
        constructor() {
            super(documentType)
        }

        test() {
            return "some value";
        }
    }
});

In your shorter example, you are using the instantiation expression typeof DocumentSet<TDocumentType, TEntity> to refer to the type of a generic class constructor whose generic type parameters have been specified with the arguments TDocumentType, TEntity . That is, a type essentially the same as new() => DocumentSet<TDocumentType, TEntity> (using a construct signature ).

If you replace the instantiation expression with the equivalent type:

create<TExtension extends IDocumentSet<TDocumentType, TEntity>>(
    extend: (Instance: new () => DocumentSet<TDocumentType, TEntity>
    ) => TExtension = w => (w as any) as TExtension) {
    return extend(DocumentSet);
}

Then you get the expected inference when you call extend() :

myNewSet.someNewFunction(); // okay

I'm not sure why it doesn't work the way you wrote it. Instantiation expressions are new (they were introduced in TypeScript 4.7 which is the latest release version), so I can't tell if this inference failure is a language bug, or a design limitation, or the intentional behavior. If it matters, someone might want to open an issue in GitHub asking about it. If that happens and we get an answer, we can update this post.

Playground link to code

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