In given function, last function should give a compilation error as it is not allowed.
interface IQuery<T> {
first<U = T & object>(): Promise<U>
sum(): Promise<T>;
map<TR>(fx: (x: T) => TR): IQuery<TR>;
}
async function fx(q: IQuery<Product>) {
// type is correct
const total = await q.map((x) => x.price).sum();
// type is correct
const firstProduct = await q.map((x) => x).first();
// type is never, this doesn't give compilation error
const firstID = await q.map((x) => x.id).first();
}
class Product {
id?: number;
price?: number;
}
Last line in function fx
compiles successfully however the return type is never but it doesn't give me compilation error.
Basically, method first
cannot be called unless map
returns an object, not a literal.
How should I restrict first<U = T & object>(): Promise<U>
?
I have tried
first<U = T extends object ? T : never>(): Promise<U>
first<U = T>(): U extends object ? Promise<U> : never;
Here is playground link, TypeScript Playground Code
Updated code by Alexander works but doesn't allow inheriting from IQuery again to add few more methods. TypeScript Playground Code
Basically you should make first()
not valid if T
doesn't extend object, the error message is though kind of cryptic:
interface IQuery<T> {
first(this: T extends object ? IQuery<T> : never): Promise<T>
sum(): Promise<T>;
map<TR>(fx: (x: T) => TR): IQuery<TR>;
}
async function fx(q: IQuery<Product>) {
// type is correct
const total = await q.map((x) => x.price).sum();
// type is correct
const firstProduct = await q.map((x) => x).first();
// The 'this' context of type 'IQuery<number | undefined>' is not assignable to method's 'this' of type 'never'.(2684)
const firstID = await q.map((x) => x.id).first();
}
class Product {
id?: number;
price?: number;
}
With a type it looks better:
type IQuery<T> = {
sum(): Promise<T>;
map<TR>(fx: (x: T) => TR): IQuery<TR>;
} & (T extends object ? {first(): Promise<T>} : {})
async function fx(q: IQuery<Product>) {
// type is correct
const total = await q.map((x) => x.price).sum();
// type is correct
const firstProduct = await q.map((x) => x).first();
// Property 'first' does not exist on type '{ sum(): Promise<number | undefined>; map<TR>(fx: (x: number | undefined) => TR): IQuery<TR>; }'
const firstID = await q.map((x) => x.id).first();
}
class Product {
id?: number;
price?: number;
}