Skip to content

Why is syntax the preferred integration point? #248

Open

Description

@idleman

I understand the ecosystem argument for TypeScript compatibility. There is a huge amount of existing TypeScript code and maximizing compatibility is an explicit goal of this proposal. At the same time, full TypeScript compatibility is out of scope so we are already talking about a selected subset rather than TypeScript itself. What I'm trying to understand is why new syntax is the chosen integration point.

JavaScript often solves extensibility through API/protocols:

  • Symbol.iterator
  • Symbol.asyncIterator
  • .then() for Promise-like objects
  • Symbol.hasInstance

Different implementations can participate as long as they follow the protocol. Why wouldn't something similar make sense for type annotations?

I'm just wondering whether a JavaScript-level annotation protocol was considered, where TypeScript, Flow, Closure, or future tools could choose to recognize or emit it.

If that idea was explored, what were the main reasons for preferring new syntax instead? Especially given new syntax appears to be one of the primary sources of resistance, what benefits does the proposal gain from using syntax rather than a more JavaScript-native mechanism?

Activity

  1. shaedrich commented on Jun 6, 2026

    @shaedrich

    I understand the ecosystem argument for TypeScript compatibility. There is a huge amount of existing TypeScript code and maximizing compatibility is an explicit goal of this proposal. At the same time, full TypeScript compatibility is out of scope so we are already talking about a selected subset rather than TypeScript itself.

    Not even TypeScript specifically. Even though, (some of) the authors/champions might be biased towards TypeScript, this should not exclusively tied to one transpiler language. In some places, Flow is mentioned as the second considered language for this proposal, but there might be more others I'm not aware of

    What I'm trying to understand is why new syntax is the chosen integration point. […] If that idea was explored, what were the main reasons for preferring new syntax instead?

    Though not entirely part of your question, here are a few key facts:

    • In surveys, one of the most asked for missing feature in JavaScript is typing (the authors/champions misuse this to make their points for the proposal by indicating that the survey endorsed the proposal)
    • JavaScript's type system is weak, but due to its unique role in the client-side browser ecosystem, JavaScript has vowed to not have any breaking changes, so the maintainers have more or less deemed changes to the type system impossible. As much as "we" would like to see it, JavaScript is not the language for it—in another life maybe. That's also the reason why projects like TypeScript, Flow, and others were created independently from JavaScript in the first place
    • So, the solution is to have our cake and eat it, meaning, we get types, yet we don't. You can write them in JavaScript, but not use them. The usage part will be delegated to separate tooling (or not, JavaScript won't know)

    Disclaimer: I'm not a fan of this proposal, just explaining why and how we got into this corner.

  2. idleman commented on Jun 8, 2026

    @idleman
    Author

    Not even TypeScript specifically. Even though, (some of) the authors/champions might be biased towards TypeScript, this should not exclusively tied to one transpiler language. In some places, Flow is mentioned as the second considered language for this proposal, but there might be more others I'm not aware of

    I believe it is hard to argue that the authors are NOT biased towards TypeScript, given the focus of the proposal and the fact that the README.md explicitly states:

    This proposal is a balancing act: trying to be as TypeScript compatible as possible while still allowing other type systems, and also not impeding the evolution of JavaScript's syntax too much. We acknowledge that full compatibility is not within scope, but we will strive to maximize compatibility and minimize differences.

    A huge ecosystem is generally a good thing, so being aligned with TypeScript's design isn't automatically a bad thing.

    However, what I am trying to understand is why the authors chose to maximize TypeScript compatibility when it is openly admitted that full compatibility can never be achieved and the resulting syntax restricts the future evolution of JavaScript itself.

    The alternative solution would be to take a step back and design a mechanism or standardized comment convention that does not introduce new, rigid syntax. This would allow TypeScript, Flow, and any future type-checker to compile down to a unified format if they want annotations to be preserved at runtime. Removing the constraint of "must allow current TypeScript syntax" feels like a win-win for the long-term health of ECMAScript.

    Disclaimer: I'm not a fan of this proposal, just explaining why and how we got into this corner.

    I have actually met very few people who support this proposal once they realize it is essentially just an alternative, more rigid way to write comments that the engine completely ignores, rather than actual runtime type enforcement.

  3. spenserblack commented on Jun 8, 2026

    @spenserblack

    I believe that a benefit of new syntax is that you can be reasonably confident that it will not conflict with existing working code, because the new syntax makes a previously invalid sequence of tokens valid. Additionally, IDENT: TYPE is pretty familiar, and is used by a few languages besides TypeScript:

    • Python: name: str = "World"
    • Ruby
    • Rust: let name: &str = "World";

    However, there is one annoying issue with : TYPE for TypeScript, IMO, and that's object destructuring.

    function greet({ name: string }): void {
      console.log(`Hello, ${string}!`);
    }
    
    greet({ name: "World" });

    This code does not annotate that the property name is a string, but renames name to string. Type annotations for destructured objects can be unpleasantly verbose:

    function greet({ name }: { name: string }): void {
      console.log(`Hello, ${name}!`);
    }

    For this reason, it might be worth considering an alternative to :.

    I have actually met very few people who support this proposal once they realize it is essentially just an alternative, more rigid way to write comments that the engine completely ignores, rather than actual runtime type enforcement.

    I'm one of those who wants this, but does not want the types to be enforced (by default). I look at Python, which has implemented types. x: int = "one" is runnable Python, but development tools will report this error.

    However, what Python does that I find very interesting is making types known at runtime.

    def inc(n: int) -> int:
        return n + 1
    inc.__annotations__  # {'n': <class 'int'>, 'return': <class 'int'>}

    This empowers developers to write a runtime library that can be used to opt in to runtime type checking. I think it would be interesting if JavaScript did the same, perhaps like this:

    (function(n: Number) {})[Symbol.argTypes]  // [Number]
    

    Overall, I think that Python is a good example of adding types to a scripting language, and it would be worth researching for inspiration.

  4. shaedrich commented on Jun 8, 2026

    @shaedrich

    Not even TypeScript specifically. Even though, (some of) the authors/champions might be biased towards TypeScript, this should not exclusively tied to one transpiler language. In some places, Flow is mentioned as the second considered language for this proposal, but there might be more others I'm not aware of

    I believe it is hard to argue that the authors are NOT biased towards TypeScript, given the focus of the proposal

    I know. I was speaking euphemistically. Being too blunt already got me a strike in this repo.

    Additionally, IDENT: TYPE is pretty familiar, and is used by a few languages besides TypeScript

    With the noteworthy exception that it has meaning in these other languages. So, the benefit of familiarity comes with the tradeoff of wrong assumptions.

  5. idleman commented on Jun 10, 2026

    @idleman
    Author

    I believe that a benefit of new syntax is that you can be reasonably confident that it will not conflict with existing working code, because the new syntax makes a previously invalid sequence of tokens valid. Additionally, IDENT: TYPE is pretty familiar, and is used by a few languages besides TypeScript:

    It introduces additional language complexity and create expectations that do not match runtime behavior.

    Another concern is reflection. The following pattern exists in real-world code:

    function toNamedParameters(cb) {
      const params = cb.toString()
        .match(/\(([^)]*)\)/)![1]
        .split(",")
        .map(p => p.trim())
        .filter(Boolean);
    
      return args => cb(...params.map(p => args[p]));
    }
    //....
    const create = (email: string, admin: boolean) => ({ email, admin });
    const construct = toNamedParameters(create);
    console.log(construct({ admin: true, email: "a@b.com" }));

    Case 1: type annotations become observable
    If type annotations become observable through reflection APIs such as Function.prototype.toString(), code like the above would observe different runtime behavior depending on whether annotations were stripped beforehand or executed natively.

    Case 2: type annotations are not observable
    Function.prototype.toString() would no longer represent the original source text. This can cause some backward compatibility issues, consider how easily a function is converted to a string in various places, not just for reflection but also for error messages and debugging. Do also add developer confusion, because regular comments is preserved.

    Neither case is ideal, but one solution must ultimately be selected. This, in my opinion, highlight the importance of taking a step back and search for alternative solutions that do not have these issues above (and preferable less of all the other issues already raised in other threads).

    Overall, I think that Python is a good example of adding types to a scripting language, and it would be worth researching for inspiration.

    It is not common in Python to parse the source code of a function. Its fairly common in JavaScript, causing a fundamental difference.

  6. spenserblack commented on Jun 10, 2026

    @spenserblack

    Another concern is reflection. The following pattern exists in real-world code:

    // ...
    const create = (email: string, admin: boolean) => ({ email, admin });
    // ...

    That's real-world working JavaScript code? With the type annotations?

    If type annotations become observable through reflection APIs such as Function.prototype.toString(), code like the above would observe different runtime behavior depending on whether annotations were stripped beforehand or executed natively.

    Yes, I think it's likely that, if this proposal is accepted, the type annotation will become part of functions' string representation. All existing calls to toNamedParameters, before type annotations were introduced, should continue to work. Sure, any new call to toNamedParameters, if the caller passes a function with type annotations, may break. But that is new code.

    With a slightly different regular expression, I could write an example toNamedParameters that would break on arrow functions, object destructuring for parameters, or default values for parameters. If I used that toNamedParameters as an example to argue against those proposals, would you consider that a valid argument?

    In fact, almost any new syntax could conflict with code that parses a function's string representation, as long as that new syntax is valid anywhere in the parameters or the function body. Again, that would break new code, as old working code would obviously not have the new syntax.

    Am I misunderstanding your example?

  7. idleman commented on Jun 10, 2026

    @idleman
    Author

    That's real-world working JavaScript code? With the type annotations?

    The purpose of this proposal is to maximize the compatibility for TypeScript and since its initial release over a decade ago (2012), have the code I posted have one specific runtime behavior. If this proposal is accepted as-is, will stripped execution and native execution have different runtime behavior. Sure, not exactly that code (const was not supported 2012, either lambas and so on), but it misses the point.

    If a specific code 100% is supported by this proposal, how would various platform and runtimes tell it apart, when type stripping is needed or it can execute the code as-is?

    • It may be impossible from the source code to tell. The code will be valid ECMAScript after all then.
    • The developer may not know.
    • The runtime may need additional configuration to know.

    The result may be the user need to specify some kind of flag: "always type-strip". I do not say its is neccessary a bad thing, but it would cause more developer complexity and reduces one of the proposed benefits of this proposal.

    Yes, I think it's likely that, if this proposal is accepted, the type annotation will become part of functions' string representation. All existing calls to toNamedParameters, before type annotations were introduced, should continue to work. Sure, any new call to toNamedParameters, if the caller passes a function with type annotations, may break. But that is new code.

    Say you have run some TypeScript code in nodejs, deno or bun, Cloudflare workers or any other runtime for years, maybe even a decade. When the JavaScript engine is updated, to a version that supports type annotations natively either manually or indirect. Now may native execution and stripped execution diverge in observable ways.

    You may argue its "new code", but from a developer point of view, code that have one specific runtime behavior acrosss TypeScript implemenataions for more than a decade, and then behave differently depending on how it is executed, is not ideal.

    In a nutshell. This proposal, if accepted means native execution and stripped execution are not necessarily observationally equivalent anymore. That seems important for a proposal whose main value is compatibility with existing TypeScript-like code.

    With a slightly different regular expression, I could write an example toNamedParameters that would break on arrow functions, object destructuring for parameters, or default values for parameters. If I used that toNamedParameters as an example to argue against those proposals, would you consider that a valid argument?

    It would of course be rather irresponsible not to take it into consideration. However, given the state of the ecosystem 11 years ago when those proposals were introduced, I would say the risk was extremely low. Parameter lists are more sensitive because they are often the part people are interested in when parsing a function source-code.

    In fact, almost any new syntax could conflict with code that parses a function's string representation, as long as that new syntax is valid anywhere in the parameters or the function body.

    I agree. That is why I question the assumption that "new syntax is preferred".

    Keep in mind that it is possible to implement annotations in ECMAScript without introducing any new syntax. Such an approach could provide many of the same benefits while avoiding some of the compatibility and reflection concerns discussed above. Not being TypeScript-compatible would of course be one tradeoff.

  8. azder commented on Jul 3, 2026

    @azder

    You may want to check this #176 (comment) and of course, the entire issue thread, top to bottom.

    There is probably a syntax compatible way i.e. no further changes to EcmaScript syntax to introduce every kind of static type checking you can imagine, as long as there is a will for it. There isn't from the proposers because they want to make ES be TS, not ES be ES that enables static types.

    During the end of that issue comments, you can see something like (modified a little)

    import TS4 from "./ts4.checker.js" with { type: "checker", version: "ts@4" };
    import("ts5.checker.js", { with: { type: "checker", version: "ts@5.8.3" } });
    import('flow.checker.js', { with: { type: 'checker', version: 'Flow' } });
    
    //:  type FN = (a:number, b:number) => number;
    //: fn::FN
    
    const fn = (a,b) => a + b;

    As long as your JavaScript compiler sees the static types as comments, you can do anything in those comments. But that isn't TS, right? So, everyone will default to bickering about changing the ES syntax, even introducing new kinds of comments, ones starting with : - SMH

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions