tc39 / proposal-type-annotations

ECMAScript proposal for type syntax that is erased - Stage 1
https://tc39.es/proposal-type-annotations/
4.27k stars 47 forks source link

type syntax = expression syntax #206

Open phaux opened 11 months ago

phaux commented 11 months ago

My proposal for including a type syntax with minimal additions to the existing grammar rules.

Goals are:

The idea is: the types are just any expressions, but ignored. It basically resolves the following issue in the readme:

The challenge with this is denoting the end of a type [...] These rules have not yet been established, but will be explored in more detail upon advancement of this proposal

Some restrictions can be added to expressions in type position. For example a function expression in a type position must not have a block body.

Here are the new syntax additions grouped by use case, the most important ones first (IMHO).

Consuming a typed library

Note how these 3 features cover most of the TypeScript usage when only consuming a typed library.

Type imports

Allow optional type modifier on imports. Fairly obvious. The imports with the type modifier should be ignored by the runtime.

import type { Ident } from "..."
import { type Ident } from "..."

Type annotations

Add optional type annotations on variables, function arguments and function return. The type after the colon is just an expression and is ignored by the runtime.

let x: Type = 1
let f = (x: Type): Type -> x
function f(arg: Type): Type {}

Generic parameter application

In addition to regular function calls (() and ?.()), allow a new syntax .<> (can be replaced with some other syntax e.g. ::<>) for passing generic parameters but ignored during runtime. This would be valid to use anywhere in expression position, which includes the types, because they're just expressions. The "arguments" are also just any expression.

let a: Array.<Type> = [...]
a.reduce.<Type>(...)
await new Promise.<Type>(...) // also make sure this is not parsed as `(new Promise()).<Type>()`
class Foo extends Bar.<Type> {}

Defining own types

When defining own types, the following features are useful.

Generic parameters in declarations

Allow generic parameters in class and function declarations. The syntax could be either .<> to be consistent with the application syntax or just <> because it's not ambiguous. The type parameters could also have a type annotation and default value after them like a regular function argument. The type annotation of a type parameter could be used to constrain the type parameter (like TS extends).

function foo<Ident>() {}
function foo<Ident: Type>() {}
function foo<Ident = Type>() {}
class Foo<Ident: Type = Type> {}

Type declaration syntax

Add a new kind of declaration that declares a type and is ignored by the runtime.

type Ident = Type
type Ident<Ident> = Type
export type Ident = Type
// etc

Object optional property

For object literal syntax, allow an optional ? after the property name to indicate that the property is optional.

let o1: { foo: Type, bar?: Type } = { }
let o2 = { a?: 1 } // this has no meaning in TypeScript nor Flow but would be now allowed and ignored

+/- could be added too to indicate readonly/writeonly properties like in Flow.

Summary and examples

With just these syntax additions we can accomplish basically everything that typecheckers can do:

import { useState, type MouseEvent } from "react"

type FooProps = {
  className?: string
  onClick: (e: MouseEvent) => void
}

function Foo(props: FooProps) {
  const [state, setState] = useState.<number | null>(null)

  return ...
}

For incompatible TS/Flow syntax, more syntax additions could be proposed in the future or an alternative JS-expressions-only syntax could be invented:

azder commented 11 months ago

not favor any particular existing type checker

Just by defining the syntax, you favor at least one type checker. What if the checker doesn't parse TypeScript or Flow like syntaxes, but uses Hindley–Milner one (think of how Haskell does it).

Even now, it is cumbersome to provide TS definitions if your function automatically does partial application, so the syntax changes you propose will not account for code like that, thus make it harder to use (if possible), hence favor particulat existing one(s)