1

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!

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.