Reputation: 723
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!
Upvotes: 1
Views: 1794
Reputation: 1008
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.
Upvotes: 2