Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[grammar] Syntax considerations #197

Open
kaleidawave opened this issue Sep 25, 2023 · 4 comments
Open

[grammar] Syntax considerations #197

kaleidawave opened this issue Sep 25, 2023 · 4 comments

Comments

@kaleidawave
Copy link

kaleidawave commented Sep 25, 2023

As I am building a checker for JS, I thought it would be a good to chime in with my point of view on some syntax things.

Firstly I think it would be great to standardise type syntax as a standard. Parsers and other tools are in the wild-west about how to handle type syntax. The most used type syntax extension don't have a good specification and process for adding syntax, leading some tools to be in the dark with trying to supporting codebases using it.

Things that I think would benefit from type annotations being in the source

  • Pushing code that contains type annotations to package managers (such as NPM). It would make it a lot easier for checkers to parse the source code rather than having to pair up declaration files
  • Similarly for package managers, there would not be the complexity of shipping two files that contain similar things. Aka no need for "types" field in package.json etc
  • Being able to see original type annotations in debugging tracing (without the complexities of source maps)

Things that I think are important to upheld/accept in this proposal

  • That it is not going to be one-to-one with the whole of TypeScript's syntax
  • The syntax provides a usable and minimal but useful set of syntax and accepts there are still cases where checkers might benefits from extending this standard
  • That checkers should attempt to support the defined annotations. (aka x as Type should be handled by Flow) and hopefully in the future converge towards standards rather than diverging.

So while I would like to see an improvement on this front I have some concerns and questions with how this implemented

Reading the current document (as of 25/09/23), I think the concept of defined type annotations is a better that the original idea of tokens/comments. I would back that decision up with:

  • Token soup (love the phrase BTW) is a good concern. I don't think let x: &4a?asdd££ = 2 should be valid 😂
  • Not parsing parts of the source would be another concept that parsers would have to be aware of.
  • Some consequences storing tokens as sequences rather than structured would have for parsing tooling:
    • Lets say my-parser written in language x . And I want to treat the tokens in the way of type checker y . That would require me to find a library compatible with the way my-parser represents tokens for y. If my-parser had parsed it ahead of time, I could do things right away, wouldn't have to worry about eight tools parsing the token stream each and requiring caching for that

IMO 90% of the type annotations from TypeScript are perfect. Those being

  • primitive literals (e.g. 4, "my string")
  • unions and intersections
  • objects and tuples (including named tuples!)
  • references (e.g. Console)
  • generics structures with arguments
  • conditionals, property accesses
    By perfect I mean they cover a lot of type scenarios, aren't unintuitive and are at their simplest form (not overly complex)

The ones that are of slight concern are the modifiers under TypeOperatorType. I wonder whether these could be just done as generics e.g. readonly T could be ReadOnly<T>?

Interface declarations and type aliases are pretty much fine. Not sure about IndexSignature though (if that is what I think it is).

I don't think there is any way an experimental checker could benefit from having different syntax here. I don't think a more minimal and fixed set holds checkers back

So 👍 for type annotations (in function parameters, return and variables) and the two types of declarations.


The slight concern is for other places where type syntax can crop up (in TypeScript and others). Removing these would make this proposal simpler and adhere to non-bias. Some of these have too high of precedence and leak types outside of where they are usually found in declarations and variables. If they were included I think it would be difficult to document on things like MDN without favouring a implementation of checking or being difficult to explain.

Here is an inconclusive list of things I don't think should be included:

  • import type is a hint for bundlers (right?). If this was an engine, would it meant that the import doesn't import anything? Bit confusing so might be better to not include?
  • ! (non null assertion). IMO this signifies a failure in the checker knowledge. Should optional chaining and handing it at runtime be preferred? This also is quite a niche piece of syntax which instead could be used in the future for a lot of bigger features. For example Rust uses them to denote macros.
  • as type. If this were to be syntax, would it condone unsafe type casting? Are there are uses for as syntax? Could cast<T> be used instead by checkers?
  • as const. Does it look like const is type? Does this double up with Object.frozen? Maybe in the future a Object.deepFreeze could be considered instead of this syntax?
  • satisfies (in expressions). Do all checkers benefit from this? What should it be documented as?
  • asserts *condition* in the place of a function return
  • publicity modifiers. Aren't these solved by #?
  • abstract unclear of runtime implications

Might have missed some others here. But those I think are cases that shouldn't be included in this specification. I think simplifying would the make this more acceptable to pass.

That is recommendations for syntax. I may open another issue later about reflection, runtime checking and direction later.

@egasimus
Copy link

egasimus commented Sep 26, 2023

Yay, Ezno - an actual alternative implementation of TS checking!

Glad to have you on here.

FWIW, a piece of feedback on part your list:

import type is a hint for bundlers (right?). If this was an engine, would it meant that the import doesn't import anything? Bit confusing so might be better to not include?

My experience with this: I'm building on top of some TypeScript libraries where the authors have used import indiscriminately, for types and values.

Apparently, everyone else ever who has ever used those libraries was doing it through a bundler, which papers over the issue (and then some). But as I tried to compile them with tsc to ESM (to use them with import maps - lovely instant feedback!), the type identifiers still ended up in the imports alongside the value identifiers, while the exported types were obviously stripped. As a result, the libraries promptly broke at runtime, due to trying to import things that weren't there.

Since those libs were not tiny things, but hundreds of files, my only option was to write an AST codemod utility (using recast), which parsed all the source files, took account of all exports (and reexports), determined what's a type and what's a value, and split each import line into separate import and import type lines.

So that's one failure mode to take into account. Not importing anything would've been fine, but not discriminating between value and type imports ended up trying to import types at runtime.

Import assertions might make good "canonical" alternative to import type.

The ones that are of slight concern are the modifiers under TypeOperatorType. I wonder whether these could be just done as generics e.g. readonly T could be ReadOnly?

Agree about doing these as generics by default. Still, it's nice how TS allows these as prefixes to constructor arguments, and declares/assigns them automatically. Maybe out of scope for type checking, though.

@azder
Copy link

azder commented Sep 27, 2023

import type is a hint for bundlers (right?). If this was an engine, would it meant that the import doesn't import anything? Bit confusing so might be better to not include?

I was thinking about it as I wrote #176 (comment)

Let's say the first step is not adding new syntax, similar to how EcmaScript 5.1 just added tools before EcmaScript 6 came along and built on top of it.

In that other comment, I tried to see if we can leverage an existing syntax like

import ts4 from './local/interface/definitions/file.v4.d.ts' assert { checker:  ['TypeScript','4','d.ts']  };
import flw from './local/interface/definitions/file.fjs' assert { checker:  ['Flow','0.217.0']  };

but then got into thinking... what would those ts4 and flw be?

  • Similar/same to the module namespace objects?
  • Objects of some other TypeChecker type?
  • Maybe TypeChecker can be a global object/namespace for utilities, and like Object.create it can have TypeChecker.assert( s, ts4.SomeType ) or TypeChecker.as( s, 'flw.SomeType' ) or similar uses?
  • Maybe just flw.check( someIdentifier, 'flw.SomeType') ?

In that other comment I mentioned someone might use out-of-band workers to provide load and run time checks, but an object returned via the imports with assertions or a global object that would provide a plug-in mechanism for any type checker for you to bring is also feasable I suppose, or hope so, through something like TypeChecker.register() utility.

I mean that as much in favor of

not going to be one-to-one with the whole of TypeScript's syntax

as I'm trying to envision extensibility beyond TypeScript and Flow as to not have

in the future converge towards standards rather than diverging.

at least not standardize type syntax into EcmaScript, but standardize extensibility.

And it could be extensible enough that someone might

  • use a SQL syntax for their domain objects,
  • check/define their protocol buffers,
  • add hints for code that interoperates with other languages, maybe WASM types or others still,
  • or even beyond what we currently know and what might become obsolete some time in the future.

Yes, the above sounds like annotations. Overlap? Complement?

@niyarlatotep
Copy link

Sorry to interrupt, I'm not an expert but maybe we can simply add way to rigister any typechecker or transpiler like it implemented in node js (or simular) i mean --loader (transpiler loader). And just write js code with types, then in the beginning of "main" module import this checker or transpiler and it will typecheck and transpile code on the fly. It's not resolve typing "forks" problem but it may be can solve transpile stage problem may be it's enough for the first step.
Sorry if this isn't the topic.

@theScottyJam
Copy link

@niyarlatotep - you're welcome to open a new issue on that topic. I know some TC39 delegates shared some similar concerns, with the idea of "do we really need this, or would it be better to just provide options for different environments to be able to run TypeScript and other type checkers directly?". I saw this concern brought up in their meeting notes, but haven't really seen it get discussed in the github issues yet.

@niyarlatotep niyarlatotep mentioned this issue Oct 18, 2023
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants