简体   繁体   中英

TypeScript: Enforce generic inferred type parameter in function

Is there a way to restrict the run call below to be strict about the type allowed by the type parameters specified to the RequestType<> ? The return type R seems to work, but RQ isn't strict.

class RequestType<RQ, R> {
    constructor(public readonly method: string) { }
 }

interface FooRequest {
    foo: string;
    bar: string;
}

interface FooResponse {
    foobar: string;
}

const FooRequestType = new RequestType<FooRequest, FooResponse>("foo");

function run<RQ, R>(type: RequestType<RQ, R>, request: RQ): R {
    // real code here
    return {} as R;
}

Here are the calls

const foo1 = run(FooRequestType, {}); // want an error here
const foo2 = run(FooRequestType, {
    foo: "foo" // want an error here
});
const foo3 = run(FooRequestType, {
    foo: "foo",
    bar: "bar",
    baz: "" // error here -- good
});

Here is a link to the TypeScript playgound . Any help is appreciated -- Thanks!

From docs : Because TypeScript is a structural type system, type parameters only affect the resulting type when consumed as part of the type of a member.

In your case, RequestType<{},FooResponse> and RequestType<FooRequest, FooResponse> have the exact same structure, so in line

run(FooRequestType, {});

the types are correctly inferred as

run<{},FooResponse>(FooRequestType: RequestType<{}, FooResponse>, {}: {})


One way to deal with that is to add some (bogus if needed) property to RequestType , such that RequestType<RQ1,R> is different from RequestType<RQ2,R> . That property could be

class RequestType<RQ, R> {
    private readonly acc: (req: RQ) => RQ | undefined;
    constructor(public readonly method: string) { }
}

Note that for this particular solution to succeed you need to enable strictFunctionTypes option. Otherwise, (req: FooRequest) => FooRequest will be assignable to (req: {}) => {} and so FooRequestType will still be assignable to RequestType<{}, FooResponse> . You can read more about it here .


A different approach would be to not allow TypeScript to infer the wrong type for request , and instead make it infer the type for requestType (changed from type for readability):

type RequestOf<RT> = RT extends RequestType<infer RQ, any> ? RQ : never;
type ResponseOf<RT> = RT extends RequestType<any, infer R> ? R : never;
function run<RT extends RequestType<any, any>>(
    requestType: RT,
    request: RequestOf<RT>
): ResponseOf<RT> {
    return {} as ResponseOf<RT>;
}

Now, TypeScript will "guess" correctly that RT is RequestType<FooRequest, FooResponse> . Then,

type RequestOf<RT> = RT extends RequestType<infer RQ, any> ? RQ : never;

basically says: if RT is RequestType<RQ, something> , make RequestOf<RT> equal to that RQ . For more on that infer magic, see Type inference in conditional types .

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