MCPcopy Index your code
hub / github.com/tc39/proposal-type-annotations

github.com/tc39/proposal-type-annotations @main sqlite

repository ↗ · DeepWiki ↗
15 symbols 35 edges 3 files 1 documented · 7%
README

⚠️ This document has not been updated regularly. See TC39 meeting notes from 2022 (March 29, March 31) and 2023 (March 22, September 27) for the most up-to-date information.

ECMAScript proposal: Type Annotations

This proposal aims to enable developers to add type annotations to their JavaScript code, allowing those annotations to be checked by a type checker that is external to JavaScript. At runtime, a JavaScript engine ignores them, treating the types as comments.

The aim of this proposal is to enable developers to run programs written in TypeScript, Flow, and other static typing supersets of JavaScript without any need for transpilation, if they stick within a certain reasonably large subset of the language.

Status

Stage: 1

Authors:

  • Gil Tayar
  • Daniel Rosenwasser (Microsoft)
  • Romulo Cintra (Igalia)
  • Rob Palmer (Bloomberg)
  • ...and a number of contributors, see history.

Champions:

  • Daniel Rosenwasser (Microsoft)
  • Romulo Cintra (Igalia)
  • Rob Palmer (Bloomberg)

Please leave any feedback you have in the issues!

Motivation: Unfork JavaScript

Over the past decade, the case for static type-checking has been proven out fairly successfully. Microsoft, Google, and Facebook released TypeScript, Closure Compiler, and Flow, respectively. These efforts have been large investments in JavaScript to reap the productivity gains they saw in other statically-typed languages including finding errors earlier on, and leveraging powerful editor tooling.

In the case of TypeScript, Flow, and others, these variants of JavaScript brought convenient syntax for declaring and using types in JavaScript. This syntax mostly does not affect runtime semantics, and in practice, most of the work of converting these variants to plain JavaScript amounts to erasing types.

The strong demand for ergonomic type annotation syntax has led to forks of JavaScript with custom syntax. This has introduced developer friction and means widely-used JavaScript forks have trouble coordinating with TC39 and must risk syntax conflicts. This proposal formalizes an ergonomic syntax space for comments, to integrate the needs of type-checked forks of JavaScript.

Community Usage and Demand

Static Typing was the most missed language feature in the State of JS survey each year on record: 2020, 2021, 2022, 2023 and 2024.

missing-features-js

Trends in JavaScript Compilation

The rise of type syntax in JavaScript coincided with the rise of downlevel compilation (sometimes called "transpilation"). As ES2015 was standardized, JavaScript developers saw wonderful new features they could not immediately use because of constraints around supporting older browsers. For example, an arrow function could provide developer ergonomics, but wouldn't run on every end-user's machine. As a result, projects like Traceur, TypeScript, and Babel filled the gap by rewriting ES2015 code into equivalent code that would work on older runtimes.

Because type syntax is not natively supported in JavaScript, some tool had to exist to remove those types before running any code. For type systems like TypeScript and Flow, it made sense to integrate a type erasure step with a syntax-downleveling step, so that users wouldn't need to run separate tools. More recently, some bundlers have even started doing both.

But over time, we anticipate there will be less need for developers to downlevel-compile. Evergreen browsers have become more of the norm, and on the back-end, Node.js and Deno use very recent versions of V8. Over time, for many static type system users, the only necessary step between writing code and running it will be to erase away type annotations.

Build steps add another layer of concerns to writing code. For example, ensuring freshness of the build output, optimizing the speed of the build, and managing sourcemaps for debugging, are all concerns that JavaScript initially side-stepped. This simplicity made JavaScript much more approachable.

This proposal will reduce the need to have a build step which can make some development set-ups much simpler. Users can simply run the code they wrote.

Limits of JSDoc Type Annotations

While build tools are not terribly difficult to use, they are yet another barrier to entry for many developers. This is in part why the TypeScript team invested in support for expressing types in JSDoc comments. JSDoc comments had some existing precedence in the JavaScript community for documenting types, and these types were leveraged by the Closure compiler.

This comment convention is often found in build scripts, small web apps, server-side apps, and elsewhere where the cost/benefit tradeoff of adding a build-tool is too high. Even when no type-checking diagnostics put in place, the comment convention is still leveraged in editors supporting types in their underlying JavaScript editing experience.

Here's an example of the JSDoc-based type syntax from TypeScript's JSDoc Reference.

/**
 * @param {string}  p1 - A string param.
 * @param {string=} p2 - An optional param (Closure syntax)
 * @param {string} [p3] - Another optional param (JSDoc syntax).
 * @param {string} [p4="test"] - An optional param with a default value
 * @return {string} This is the result
 */
function stringsStringStrings(p1, p2, p3, p4="test") {
    // TODO
}

And here's the proposed syntax, that's compatible with the majority of type-checkers.

function stringsStringStrings(p1: string, p2?: string, p3?: string, p4 = "test"): string {
    // TODO
}

JSDoc comments are typically more verbose. On top of this, JSDoc comments only provide a subset of the feature set supported in TypeScript, in part because it's difficult to provide expressive syntax within JSDoc comments.

Nevertheless, the JSDoc-based syntax remains useful, and the need for some form of type annotations in JavaScript was significant enough for the TypeScript team to invest in it and the community to create tooling to support type-checking using JSDoc[1], [2].

For these reasons, this proposal explores and expects to a larger syntax to appear as-is in JavaScript source files, interpreted as comments.

Proposal

The following is a Stage 1 proposal. Please treat it as such, updates are expected and feedback is welcome. TC39 stage process document

Type Annotations

Type annotations allow a developer to explicitly state what type a variable or expression is intended to be. Annotations follow the name or binding pattern of a declaration. They start with a : and are followed by the actual type.

let x: string;

x = "hello";

x = 100;

In the example above, x is annotated with the type string. Tools doing static type analysis can utilize that type, and might choose to error on the statement x = 100; however, a JavaScript engine that follows this proposal would execute every line here without error. This is because annotations do not change the semantics of a program, and are equivalent to comments.

Annotations can also be placed on parameters to specify the types that they accept, and following the end of a parameter list to specify the return type of a function.

function equals(x: number, y: number): boolean {
    return x === y;
}

Here we have specified number for each parameter type, and boolean for the return type of equals.

Type Declarations

Much of the time, developers need to create new names for types so that they can be easily referenced without repetition and so that they can be declared recursively.

One way to declare a type - specifically an object type - is with an interface.

interface Person {
    name: string;
    age: number;
}

Anything declared between the { and } of an interface is entirely ignored.

A type alias is another kind of declaration. It can declare a name for a broader set of types.

type CoolBool = boolean;

Classes as Type Declarations

This proposal would allow class members, like property declarations and private field declarations, to specify type annotations.

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    getGreeting(): string {
        return `Hello, my name is ${this.name}`;
    }
}

For most type-checkers, annotated class members would contribute to the type produced by constructing a given class. In the above example, a type-checker could assume a new type named Person, with a property name of type string and a method getGreeting that returns a string; but like any other syntax in this proposal, these annotations do not weigh into the runtime behavior of the program.

Kinds of Types

The above examples use type names like string, number, and boolean, the majority of static type-checkers support types with more involved syntax than just a single identifier. Some examples are given in the following table.

Name Example
Types References with Type Arguments Set<string>
Object Types { name: string, age: number }
Array Type Shorthand number[]
Callable Type Shorthand (x: string) => string
Constructable Type Shorthand new (b: Bread) => Duck
Tuple Types [number, number]
Union Types string \| number
Intersection Types Named & Dog
this Types this
Indexed Access Types T[K]

This table is not comprehensive.

The goal of this proposal is to find a reasonable set of syntactic rules to accommodate these constructs (and more) without prohibiting existing type systems from innovating in this space.

The challenge with this is denoting the end of a type - this involves stating explicitly what tokens may and may not be part of a comment. An easy first step is to ensure that anything within matching parentheses and brackets ((...), [...], {...}, or <...>) can be immediately skipped. Going further is where things get harder.

These rules have not yet been established, but will be explored in more detail upon advancement of this proposal.

See also

Parameter Optionality

In JavaScript, parameters are technically "optional" - when arguments are omitted, the parameters of a function will be assigned the value undefined upon invocation. This can be a source of errors, and it is a useful signal whether or not a parameter is actually optional.

To specify that a parameter is optional, the name of that parameter can be followed with a ?.

function split(str: string, separator?: string) {
    // ...
}

Importing and Exporting Types

As projects get bigger, code is split into modules, and sometimes a developer will need to refer to a type declared in another file.

Types declarations can be exported by prefixing them with the export keyword.

export type SpecialBool = boolean;

export interface Person {
    name: string;
    age: number;
}

Types can also be exported using an export type statement.

export type { SomeLocalType };

export type { SomeType as AnotherType } from "some-module";

Correspondingly, another module can use an import type statement to reference these types.

import type { Person } from "schema";

let person: Person;

// or...

import type * as schema from "schema";

let person: schema.Person;

These type-specified declarations act as comments as well. They would not trigger any resolution or inclusion of modules. Similarly, their named bindings are not validated, and thus there is no runtime error if a named binding references an entity that was never declared. Instead, design-time tools would be free to statically analyze these declarations and issue an error under such conditions.

Type-Only Named Bindings

Maintaining separate import statements for values and types can be cumbersome - especially when many modules export both types and values.

```ts import { parseSourceFile } from "./parser"; import type { SourceFile } from "./parser";

// ...

let file: SourceFile; try { file = parseS

Core symbols most depended-on inside this repo

build
called by 2
site/src/server.js
go
called by 1
site/src/site.jsx
setupShikiTwoslash
called by 1
site/src/components.jsx
dedentString
called by 1
site/src/components.jsx
Page
called by 0
site/src/site.jsx
Header
called by 0
site/src/components.jsx
TC39Logo
called by 0
site/src/components.jsx
Split
called by 0
site/src/components.jsx

Shape

Function 15

Languages

TypeScript100%

Modules by API surface

site/src/components.jsx12 symbols
site/src/site.jsx2 symbols
site/src/server.js1 symbols

Dependencies from manifests, versioned

@types/node17.0.21 · 1×
@types/react17.0.39 · 1×
@types/react-dom17.0.13 · 1×
@types/sass1.43.1 · 1×
@types/ws8.5.2 · 1×
ecmarkup10.0.2 · 1×
esbuild0.12.17 · 1×
react17.0.2 · 1×
react-dom17.0.2 · 1×
remark-shiki-twoslash3.0.5 · 1×
sass1.37.0 · 1×
typescript4.6.2 · 1×

For agents

$ claude mcp add proposal-type-annotations \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact