tc39 / proposal-type-annotations

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

This proposal will not necessarily allow people to stop using tsc, causing confusion on when to use it #120

Open corevo opened 2 years ago

corevo commented 2 years ago

First off I think there is some preface I have to explain, typescript has two parts to it

tsc the process of transpiling typescript down into javascript and declaration files.
tsserver the static analyzer that gives the developers the hints and refactoring tools in their editors.

The gist of the proposal is to allow use-cases where tsc is not needed for certain files, which means that they will not need to depend on typescript at all, so long as the user has tsserver or another static analyzer installed on his machine that is able to consume these annotations.

This approach creates two issues, both of which can be resolved, but I was wondering what is the proposal plans on solving them.

Issue 1: TypeScript generational improvements (expectation of future compatibility)

Just like with ecmascript, typescript gets generational improvements as well, except this time it is unclear wether it'll make it into ecmascript or not.

For example, this proposal excludes enums and namespaces, to the typescript user it'll be unclear wether a certain feature will exist only in typescript or wether it is worth the wait to see if that feature gets standardized as a "comment".

An obvious answer to this concern is to say, these features (type annotations, interfaces, type aliases, etc...), are part of ecmascript now, and the rest aren't (like pending proposals), and that users should refrain or use their own judgement. But since this proposal draws so much from typescript, my concern is that it'll develop the expectation of compatibility.

Issue 2: Use of new reserved words

In order for this proposal to work in a meaningful way (as in to allow users to not use tsc), it'll have to declare new reserved words in the language, at minimum being type.

Given that TC39 is unwilling to create new modes for ecmascript (await is reserved only in modules), how does the proposal handle this backwards incompatibility?

Without a new mode the interpreter won't be able to tell wether type should be reserved or not.

One possible workaround is to break typescript compatibility, and to use existing language features to support it, tsc can then compile into that standard to leverage it's existence while staying backwards compatible syntax-wise.

Summary

I think that my main concern is that this proposal draws so much from typescript, that it'll create the expectation of ecmascript being fully compatible with typescript.

But all of these are concerns of human behavior, so I digress.

orta commented 2 years ago

It might be worth re-reading the spec/minisite: https://giltayar.github.io/proposal-types-as-comments/ as this proposal is about declaring areas of syntax which JavaScript does not do anything with (aka 'treat it as comments') and not about standardizing a type system into JS.

Issue 1: TypeScript generational improvements (expectation of future compatibility)

This would be handled by TypeScript knowing that you are in a JS file and raising an error for users (when using something like enums for example) - it's likely that enums or something like it may find its way into the language (which has a TypeScript team member on the champions) - at which point it would be native to JS.

Issue 2: Use of new reserved words

Declaring new keywords or syntax is always a breaking change, and backwards compatibility is usually handled via tools like babel / TypeScript to declare which version of JS you support (e.g. es6/es2015)

TypeScript's syntax would also shift somewhat to support this proposal, as it's not 'take TypeScript and make it JavaScript' but that work should be worth it for long-term alignment between JS and JS + types.

acutmore commented 2 years ago

Hi @corevo

For example, this proposal excludes enums and namespaces, to the typescript user it'll be unclear wether a certain feature will exist only in typescript or wether it is worth the wait to see if that feature gets standardized as a "comment".

The reason enums and namespaces are not included in this proposal is because they can’t be treated as a comment, as they have runtime semantics. Some TypeScript developers already avoid these parts of TS because of this and use alternative patterns. That said there are separate proposals for adding enums to EcmaScript which could progress separately.

corevo commented 2 years ago

@orta I understand the part where the type annotations are ignored by the interpreter and compiler. I might have to re-iterate my point since I see others misunderstanding what I was trying to convey.

The point of this proposal is for me to be able to use tsserver without needing to write typescript and subsequently compile it using tsc.

The issue I laid out is for the end user, he still needs to install the typescript LSP in order to get the value out of these "comments", thus causing a confusion for the end user, on the one hand he's writing javascript, on the other he's using tsserver to get static analysis.

This in turn can cause the expectation of javascript to become typescript.

@acutmore you allude to my point, typescript could change it's syntax from type to something that will be backwards compatible with javascript, but regardless of what typescript chooses to do, this proposal has to be updated first to be backwards compatible.

acutmore commented 2 years ago

Hi @corevo

@acutmore you allude to my point, typescript could change it's syntax from type to something that will be backwards compatible with javascript, but regardless of what typescript chooses to do, this proposal has to be updated first to be backwards compatible.

Would you be able to expand on what you mean by saying that this proposal should be backwards compatible? Most TC39 proposals are only forwards compatible (existing programs will continue to function unchanged when executed by newer engines) but are not backwards compatible (the new syntax will result in an error if used in an older engine).

corevo commented 2 years ago

Adding type as a reserve word will break existing javascript programs, in ES2022 this is valid JavaScript

const type = 5

The proposal seeks to make type a reserved word, rendering this program illegal.

A workaround would be to find alternative syntax to typescript's type aliases using existing reserved words, another solution would be adding a new mode, but TC39 were pretty clear on not doing that.

johnnyreilly commented 2 years ago

I'll expose my ignorance here, but is the way in which a reserved word is used a sufficient distinction to make this less of an issue?

Consider:

const type = 5 // valid JavaScript
type NameOfType = { some type: string }

In the second case, type is always used in the context of being at the start of a statement and always followed by an identifier. As opposed to the first where type is a variable identifier (words may be wrong here)

Does that distinction allow this to be "less breaking" from a parsing perspective? Appreciate that the whole proposal is about introducing new syntax anyway, just kind of interested in the nuance here

corevo commented 2 years ago

@johnnyreilly the compiler won't know in line 2, wether you are referring to the identifier type or to the reserved word type.

Also usually reserved words are checked first in the tokenizer, we'll have to consult someone from V8 to get input on this issue, but in any case consider this:

'use strict'
const interface = 5

Will fail parsing because interface is a reserved word, so even if it was possible lexically (which I'm not an expert on), it would change the current behavior of the language, currently the language restricts reserved words from being used as identifiers.

corevo commented 2 years ago

Another issue that I didn't get into in this thread is the reserved word interface, it is already reserved in strict mode, and it is unclear wether TC39 will use it as a comment, they might reject the proposal on grounds that this word is reserved for logical use.

spenserblack commented 2 years ago

Just want to point out that

const type = 5

is valid TypeScript (4.6.2), so a parser should be able to handle this even if type becomes a keyword. In fact,

type type = 5;
const type: type = 5;

seems valid. https://www.typescriptlang.org/play?#code/C4TwDgpgBKlQvFArAbgFAGMD2A7AzsDOBAFxFyKpA

Other reserved keywords (interface, as pointed out) seem invalid, though.

corevo commented 2 years ago

@jasikpark The fact that this is valid typescript does not make it necessarily valid javascript, looking at my first point, I was afraid of the expectation of this proposal becoming "standardize typescript into ecmascript".

According to the language rules, a reserved word can't be used as identifier, this is not true for typescript (since it has to be a superset and type is a valid identifier), but the fact that it is valid typescript does not mean it should also be valid in javascript.

spenserblack commented 2 years ago

@corevo I'm going to guess that you meant to tag me :laughing:

According to the language rules, a reserved word can't be used as identifier, this is not true for typescript

Thanks for explaining! I wasn't aware that that was a language rule of JavaScript (I should have read your comment more thoroughly). Now I understand the concern much better. In that case, assuming that language rule doesn't change, I share your concern. In fact, I think that const type could actually be very common.

ptomato commented 2 years ago

It's generally true that reserved words can't be used as identifiers, but for reserved words that haven't been reserved since the beginning of the language, the language grammar makes them "conditionally reserved". This is the case for await and yield which are sometimes allowed as identifiers. There are also keywords that are not reserved, such as async, which is always allowed as an identifier despite having a syntactical meaning in other contexts where identifiers aren't legal. So I'm not sure that concern about language rules is entirely accurate. The section Keywords and Reserved Words explains more.

simonbuchan commented 2 years ago

Basically, it's entirely up to the grammar what the rules are for when you can use an identifier. If you want to allow only names that are not keywords, use Identifier, if you want to allow any name, BindingName (iirc), if you want it to be permitted in some contexts, and not others, use production parameters, e.g. await is permitted as an identifier (grammatically) if and only if the [Async] parameter is false, which is true in async functions and generators, and false otherwise. (Although it's actually made illegal to use await and yield as identifiers in strict mode with a semantic rule)

There is also the case of async function ..., where async is not a reserved word (and thus is a valid Identifier), but is treated as a keyword in that specific production.

An interesting case is in object literals, where you can use async, get and set as property names, just like any other keyword, but you can also use them as keywords, like in { async foo() { ... }, ... }.

So yeah, the ship sailed a long time ago on being particularly dogmatic on keywords. Good riddance, they are pretty useless, really.

giltayar commented 2 years ago

The issue I laid out is for the end user, he still needs to install the typescript LSP in order to get the value out of these "comments", thus causing a confusion for the end user, on the one hand he's writing javascript, on the other he's using tsserver to get static analysis.

I get the point, and it has some merit to it. But this is true for JSDoc typings too, and yet the people that use them don't get confused. Another data point is Python types, which are very similar to the proposal here (and were an inspiration for it).

zsakowitz commented 2 years ago

JavaScript still has some cases of ambiguity with keywords. As @simonbuchan mentioned above, async isn't a reserved word in strict mode or in "sloppy mode". If anyone needs another example, let isn't a keyword in sloppy mode environments, so this code is valid JavaScript (as long as it's not in strict mode):

var let = 23;
let result = let + 45; // 68
let; // 23

let let = 45; // this is not allowed

Disclaimer: let isn't allowed to be declared using let or const statements to prevent ambiguity there.