Is it possible to enforce a generic type ( T
here) to be a subset of an interface ? Example:
interface Type {
a: () => number
b: () => string
}
const func = <T /* Some condition */>(): T => { return ... }
func<Type>(); // OK
func<{a: () => number>(); // OK
func<{a: () => number, c: any}>(); // Error !
Why do I need this ?
The case below built an interface with the return types of Type
. ( TypeReturn
will mean something like {a: number, b: string}
).
type TypeReturn<T> = { [key in keyof T]: (ReturnType<Type[key]>)[] }
But typescript complains at Type[key]
because he can't ensure that T
will be a key of Type
.
You could use a recursive generic constraint to simulate what you're looking for, since TypeScript doesn't really have exact types which would prohibit extra properties. Here's how I might do it:
type Exactly<T, U> = T & Record<Exclude<keyof U, keyof T>, never>;
const func = <T extends Exactly<Partial<Type>, T>>(): T => { return null! }
func<Type>(); // OK
func<{ a: () => number }>(); // OK
func<{ a: () => number, c: any }>(); // Error !
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Types of property 'c' are incompatible.
This does what you've stated you want. Note that TypeReturn
will still fail to compile, though, because even though T extends Exactly<Partial<Type>, T>
represents the constraint, the compiler can only reason about concrete values of T
that way; it doesn't understand the constraint's implications when T
is still generic. You'll need to convince the compiler further, such as using the built-in utility type Extract
to ensure that K
will be assignable to keyof Type
:
type TypeReturn<T extends Exactly<Partial<Type>, T>> = {
[K in keyof T]: (ReturnType<Type[Extract<K, keyof Type>]>)[] // okay now
}
type A = TypeReturn<Pick<Type, "a">> // type A = { a: number[]; }
type B = TypeReturn<Pick<Type, "b">> // type B = { a: string[]; }
type AB = TypeReturn<Type> // type AB = { a: number[]; b: string [] }
type Oops = TypeReturn<{ a: () => number, c: any }>
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Types of property 'c' are incompatible.
Okay, hope that helps; good luck!
You really shouldn't do this... But you can partially do this by conditionally requiring a specific string argument. Unfortunately, this makes the errors really bad, forcing you to provide an argument to see the actual error you want to trigger. Playground
interface Type {
a: () => number
b: () => string
}
declare function func<T extends Partial<Type>>(
...check: [keyof T] extends [keyof Type] ? [] :
['T keys are not a subset of Type keys']
): T
func<Type>(); // OK
func<{ a: () => number }>(); // OK
func<{ a: number }>(); // Error, not assignable to Type
func<{ a: () => number, c: any }>(); // Error, extra key
// An argument for 'check' was not provided.
func<never>('') // Argument of type '""' is not assignable to parameter
// of type '"T keys are not a subset of Type keys"'.(2345)
// However, this won't prevent the following. This cannot be prevented.
const x = { a() { return 1 }, c: false }
const y: Partial<Type> = x
func<typeof y>()
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.