Skip to content

Safe type assertion operatorΒ #56235

Open
Open

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?

  1. 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.

  1. Declare a variable
const f = async () => {
  const result: Foo = { a: 1 }

  return result
}

This is better than retyping generics but still pretty verbose.

  1. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions