Description
openedon Oct 27, 2023
π Search Terms
as, satisfies, type assertion, type casting
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
I suggest adding a safe type assertion operator, a kind of middle ground between current as
and satisfies
.
- Like
as
, it should cast the expression to the asserted type - Like
satisfies
, it should only allow assertion if the asserted type is wider than the expression type
Here's what it might look like:
type Foo = { a: number }
const foo1 = { a: 1 } as! Foo // OK. foo1: Foo
const foo2 = { a: 1, b: 2 } as! Foo // OK, foo2: Foo
const foo3 = { b: 2 } as! Foo // Error, { b: number } is not assignable to Foo
const foo4 = { } as! Foo // Error, {} is not assignable to Foo
π Motivating Example
For example, such operator will allow to safely give named types to expressions without declaring extra variables or helper functions.
Consider an async function f
that returns an object of type Foo
:
type Foo = { a: number }
const f = async () => ({ a: 1 })
I want the returned object to strictly be of Foo
type, how do I enforce this?
- Hard code the return type
const f = async (): Promise<Foo> => ({ a: 1 })
The problem here is that I must write Promise<Foo>
instead of just Foo
, but the Promise
part can be inferred just fine. In real world these generics can be arbitrarily complex.
- Declare a variable
const f = async () => {
const result: Foo = { a: 1 }
return result
}
This is better than retyping generics but still pretty verbose.
- Use a helper function
const safeAssert = <T>(value: T): T => value
const f = async () => safeAssert<Foo>({ a: 1 })
This is ok but still requires writing/importing helper functions.
as
is not an option here because it's unsafe, satisfies
is neither because it doesn't name the type. Naming a type can be important for documentation purposes, this proposal allows to do this with less boilerplate.
const f = async () => ({ a: 1}) as! Foo // f: () => Promise<Foo>
π» Use Cases
As described above, this feature can be used to safely cast expressions to specified types. A practical example when it can be useful is naming a type in an expression. Currently this is only covered by custom helper functions such as <T>(value: T): T => value
.