TypeScript
TypeScript copied to clipboard
satisfies operator for generic type constraints
Suggestion
I would love to use the new satisfies
in generic type constrains instead of extend
because of its ability to detect the conformance of a type with a wider one.
🔍 Search Terms
satisfies, generic, constrains, extend,
✅ Viability Checklist
My suggestion meets these guidelines:
- [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [x] This wouldn't change the runtime behavior of existing JavaScript code
- [x] This could be implemented without emitting different JS based on the types of the expressions
- [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [x] This feature would agree with the rest of TypeScript's Design Goals.
⭐ Suggestion
📃 Motivating Example
We need to make sure one type is JSON serializable, thanks to recursive types this is now possible, however we also want developers to specify an exact interface for strong types. Today, there is not easy way to check if the provided interface is JSON serializable!
// Let's assume that we expect that only data which is JSON serializable is a valid response.
type JSONValue =
| string
| number
| boolean
| undefined
| null
| { [key: string]: JSONValue }
| Array<JSONValue>;
export interface MyData {
foo: string,
thisShouldBeAnError?: Date;
}
type FactoryFunction<RET satisfies JSONValue> = () => RET;
export const problematicFactory: FactoryFunction<MyData> = () => {
return {
foo: 'ok',
thisShouldBeAnError: new Date(),
};
}
💻 Use Cases
- Provide even better types when right now any is the only option
- Values that can be serializable, JSON, SQL, any complex recursive type
- Simplified types and less typing since the satisfied operator is part of the generic type
It sounds like a duplicate of #14520 to me.
Maybe! but with the introduction of the satisfies
operator, we already have a semantic of how this could be implemented
This doesn't look like a lower bound. Actually, I'm not sure what it is.
@manucorporat could you clarify what you're trying to do? Even without thisShouldBeAnError
, a value of type MyData
does not satisfy JSONValue
, due to #15300:
interface MyData {
foo: string,
}
declare const myData: MyData
const oops = myData satisfies JSONValue; // error!
What, specifically, would T satisfies U
accomplish that T extends U
does not?
sorry for the confusing example, this example means to detect the error! extends does not work because JSONType is much wider.
extends
respond to the questions: is type X a superset of Y?
satisfies
respond to does X conform to Y
?
https://twitter.com/nomsternom/status/1597637566714417152?s=20&t=xPIUEvb_0-SsGqRRqO89MA
X extends Y
means that X
is a subset, not a superset.
extends
and satisfies
mean the same thing: “X is assignable to Y”. The only difference is the LHS of extends
is a type while for satisfies
it’s a value.
This isn't well-specified enough to be actionable. It seems like you're asking for either negated types, type parameter upper bounds, or possibly exact types, all of which have open issues already.
…or possibly even oneof
-style constraints.
Some thing likes Opaque Result Types in Swift
I'm running into this same problem - I'm not sure if there's even a way to check what I want right now. Here's a TS playground example
type AllowedValueType = string | number
type AllowedValueRecord = Record<string, AllowedValueType>
type AllowedValuesFunction<T extends AllowedValueRecord> = (values: T) => void
// ----- //
interface MyProps {
message: string
value: number
isAllowed?: boolean
}
interface OtherProps {
yes: "I am a string"
yup: boolean
}
const thisShouldBeFine: MyProps = {
message: "hello",
value: 1
} satisfies AllowedValueRecord
const thisShouldNotBeFine: MyProps = {
message: "hello",
value: 1,
// @ts-expect-error
isAllowed: false,
} satisfies AllowedValueRecord
// @ts-expect-error Should fail because `isAllowed` is a boolean
const notAllowed: AllowedValuesFunction<MyProps> = () => {}
// This should be allowed
const allowed: AllowedValuesFunction<OtherProps> = () => {}
// ^ I basically just want this to check satisfies
X extends Y
means thatX
is a subset, not a superset.
@fatcerberus Is this true? If X
were a superset wouldn't that entail { a: number } extends { a: number, b: number }
is true
?
Or am I somehow confusing my definitions here?
Yes, it’s true. Subset vs. superset are in terms of which values are assignable to it, not in terms of their properties. { a: number, b: number }
is a subset of { a: number }
because the latter consists of only those members of the former that also have a numeric b
property.
(I suspect you may have misread that @sinclairnick - I’m specifically saying there that X
is not a superset.)
I see.So superset and subset in this context have a different definition to the strictly mathematical one - or at least how I interpret it.So in math we might say {2} is a subset of {2,3}, in TS I was thinking in terms of properties as members of the set. Instead you’re saying the correct interpretation is that a subset is “that which can be assigned to a type”?So since {2,3} is assignable to {2}, it would be a subset, in typescript parlance?Have I got this right?On 22 May 2024, at 2:16 AM, Bruce Pascoe @.***> wrote: Yes, it’s true. Subset vs. superset are in terms of which values are assignable to it, not in terms of their properties. { a: number, b: number } is a subset of { a: number } because the latter consists of only those members of the former that also have a numeric b property. (I suspect you may have misread that @sinclairnick - I’m specifically saying there that X is not a superset.)
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>
See this q/a. Think of a type as the set of values that have that type. Then a subtype corresponds to a subset, and a supertype corresponds to a superset. If, instead, you think of object types as their sets of declared properties, then you'll be thinking of things "backwards" because keyof
is contravariant.