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

Suggestion: Put everything after `:` (Plan B) #180

Closed msadeqhe closed 1 year ago

msadeqhe commented 1 year ago

This is generalized from this issue. Everything related to types could be after : even for type declarations and interfaces. These are modified examples from README:

Type Annotations

let x: string;

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

Type Declarations

Javascript would ignore statements which start with ::

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

:type CoolBool = boolean;

Classes as Type Declarations

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

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

Parameter Optionality

Optional parameters can have their own notation after ::

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

BTW, Javascript already have ?? and ?. operators, thus allowing ?: within optional parameters seem natural and acceptable.

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

Importing and Exporting Types

Javascript would ignore statements which start with ::

:export type SpecialBool = boolean;

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

:import type { Person } from "schema";

Type Assertions

Javascript would ignore :as (...) after expressions:

const point = JSON.parse(serializedPoint) :as ({ x: number, y: number });

Generic Declarations

Javascript would ignore <>, <,>, <,,> and etc after removing :TYPE within them:

function foo<:T>(x: T) {
// function foo<>(x) {
// function foo(x) {
    return x;
}

class Box<:T, :U> {
// class Box<,> {
// class Box {
    value: T;
    constructor(value: T) {
        this.value = value;
    }
}

:type Foo<:T> = T[]

:interface Bar<:T> {
    x: T;
}

Generic Invocations

Javascript would ignore <>, <,>, <,,> and etc after removing :TYPE within them:

add<:number>(4, 5)
// add<>(4, 5)
// add(4, 5)

another<:number, :number>(4, 5)
// add<,>(4, 5)
// add(4, 5)

new Point<:bigint>(4n, 5n)
// new Point<>(4n, 5n)
// new Point(4n, 5n)

Simply this would mean everything after : will be ignored. Also empty <>, <,>, <,,> and etc will be ignored after removing :TYPE within them.

IMO, the original proposal from README is OK. My suggestion is just an option to consider, if the original proposal doesn't seem to be accepted in Javascript.

spenserblack commented 1 year ago

Simply this would mean everything after : will be ignored.

I think the real hard question that needs to be answered is when to stop ignoring. "Ignore after token X," regardless of what X is, can be too broad a statement.

For example:

function foo(): void {}

declare const WIDTH: number;
declare const HEIGHT: number;
function randomPoint(): { x: number, y: number } {
  return { x: Math.random() * WIDTH, y: Math.random() * HEIGHT };
}

For foo, it seems obvious, right? Ignore everything between : and {? But what should happen with randomPoint?

You could make that unacceptable syntax and require only identifiers as the return type. E.g. randomPoint(): {} is invalid, but randomPoint(): Point would be valid.

But if that is valid, then you'd need to define syntax for types to recognize where the type ends and the function body begins. In this case, : is expected to be followed by a type, { is an acceptable token to start a type, etc. etc., } ends the type, then { starts the function body.

So I think it's going to be really hard to "just ignore after TOKEN," and you're going to have to define a more strict set of acceptable and unacceptable tokens that can make up a type annotation.

msadeqhe commented 1 year ago

You're right.

The rule of when to stop ignoring should be as simple as possible. For example, "stop after the next brackets (e.g. <...>, [...], (...) or {...}) or expressions (a combination of operators and operands e.g. A & B) or identifier<...>".

If a statement or declaration starts with :, JavaScript should ignore the whole line, if it has an opening bracket (e.g. <, [, ( or {) within itself, Javascript should ignore until it finds the corresponding closing bracket (e.g. >, ], ) or }) even if it is within multiple lines.

On the other hand, a complicated rule will affect Javascript interpreter performance.

msadeqhe commented 1 year ago

For example:

function foo(): /*next expression*/ void /*stop*/ {}

//...
function randomPoint(): /*next brackets*/ { x: number, y: number } /*stop*/ {
  return { x: Math.random() * Width, y: Math.random() * Height };
}

Within return the : is a part of object creation and will be not ignored. But : within declarations are types as comments.

msadeqhe commented 1 year ago

Another possible syntax for generics would be:

Generic Declarations

function foo:<T>(x: T) {
    return x;
}

class Box:<T, U> {
    value: T;
    constructor(value: T) {
        this.value = value;
    }
}

:type Foo:<T> = T[]

:interface Bar:<T> {
    x: T;
}

Generic Invocations

add:<number>(4, 5)

another:<number, number>(4, 5)

new Point:<bigint>(4n, 5n)

To differentiate between declarations and invocations, if it starts with function, class, :type or :interface, it would be a declaration, otherwise it's an invocation.

piranna commented 1 year ago

I like this idea, but not the :import and :export, normal ones can be used since types and interfaces is something you would like to export too.

msadeqhe commented 1 year ago

Do you mean if we import and export the whole type as comments instead of :import and :export?

export :type SpecialBool = boolean;

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

import :type { Person } from "schema";
piranna commented 1 year ago

Do you mean if we import and export the whole type as comments instead of :import and :export?

Yes, I find it nicer.

By the way, I think you are missing an equal sign, I think it should be export :interface Person = {.

theScottyJam commented 1 year ago

How do you handle some other odd-ball scenarios, like these?

// Do you really want to stop parsing the type after the first closing bracket?
// How would you handle these?
function fn(): { x: number } | { y: number} {  ... }
function fn(): number[] | null {  ... }

// And what about this? Is this a function who's return type is a readonly object of some sort,
// and it's missing a function body, so this should be a syntax error?
// Or is this a function who's return type is just "readonly", and we don't ignore those curly brackets?
function fn(): readonly { ... }

What about inline-operators, like as, how would those get handled? If they are included, what rules should be used to know when the types stop.

return x as y | z // Is this a bitwise-or, or a union type?
msadeqhe commented 1 year ago

You're right. It's not possible to make things simple with the current syntax. I hope TypeScript, Flow, and etc, could change their syntax to make Type as Comments as simple as possible. For example, if the syntax would be :{type}:

function fn():{{ x: number } | { y: number}} {  ... }
function fn():{number[] | null} {  ... }
function fn():{readonly} { ... }
return x :{as y} | z
fierflame commented 1 year ago

Can I think that this plan is like:

  1. Use : and later type-units ({...}, [...], (...), <...> and identifiers) as type syntax.
  2. If : is at the beginning of the line, the current line (type-units across multiple lines as a line) is taken as the type syntax

The above content is the translated content, and the original text is as follows:

我是否可以认为这个方案是:

1. 将 `:` 及其后的类型单元(`{...}`,`[...]`,`(...)`,`<...>`, 标识符) 作为类型语法
2. 如果 `:` 在行首,将会把当前行整行(跨越多行的类型单元视作一行)作为类型语法
msadeqhe commented 1 year ago

I close this issue, because issue #186 is a good alternative.