Hey there! Your attempt at implementing the "satisfies" operator, as mentioned in microsoft/TypeScript#7481, is quite interesting. The concept revolves around writing val satisfies Type
to trigger a compiler error if val
does not match type Type
, without widening the type of val
to Type
. Unfortunately, TypeScript doesn't have this operator yet, so you're resorting to a workaround by renaming extendsA
to satisfies
.
Implementing satisfies
as a single generic function with two type parameters isn't straightforward due to TypeScript's lack of partial type parameter inference, as discussed in microsoft/TypeScript#26242. You'll either need to manually specify all type parameters or let the compiler infer them all. This limitation leads us to consider refactoring to a curried function approach.
// declare function satisfies<I>(): <X extends I>(argument: X) => X;
function satisfies<I>() {
return <X extends I>(argument: X) => argument
}
This refactor requires an additional step for calling:
satisfies<{ [K: string]: readonly number[] }>()({
"hello": [10, 200]
} as const); // good
While this method may seem cumbersome, reusing the partially applied function can alleviate some of the complexity when working with the same I
type repeatedly:
const satisfiesRecordOfNumberArray =
satisfies<{ [K: string]: readonly number[] }>();
const recordOfNumberArray = satisfiesRecordOfNumberArray({
"hello": [10, 200]
} as const); // good
recordOfNumberArray.hello // okay
const recordOfNumberArray2 = satisfiesRecordOfNumberArray({
"hello": [10, 200, "hi"]
} as const); // error!
// Type 'readonly [10, 200, "hi"]' is not assignable to type 'readonly number[]'
An alternative approach entails having both I
and X
type parameters in one function, including a dummy parameter dummyI
of type I
. This method requires providing a bogus value for I
:
// declare function satisfies<I, X extends I>(dummyI: I, argument: X): X;
function satisfies<I, X extends I>(dummyI: I, argument: X) { return argument }
const recordOfNumberArray = satisfies(null! as { [K: string]: readonly number[] }, {
"hello": [10, 200]
} as const); // good
const recordOfNumberArray2 = satisfies(null! as { [K: string]: readonly number[] }, {
"hello": [10, 200, "hi"]
} as const); // error!
// Type 'readonly [10, 200, "hi"]' is not assignable to type 'readonly number[]'
Although functional, this method trades off a cleaner syntax for explicitness. Ideally, a built-in satisfies
operator would be preferred over these workarounds. Both approaches involve trade-offs between elegance and verbosity.
Check out the code in the TypeScript Playground