microsoft / microsoft/TypeScript
Safe type assertion (upcast / widening) operator
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 117
Description
🔍 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue proposes a new as! syntax and gives examples for widening assertions, including the async Foo case. No source files, tests, or compiler entry points are named, so start by locating the existing as and satisfies handling in the TypeScript codebase. Done means the syntax enforces the proposed assignability rule, produces the stated inferred types, and does not alter emitted JavaScript.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100