tree-sitter / tree-sitter-typescript

TypeScript grammar for tree-sitter
MIT License
332 stars 103 forks source link

Support instantiation expressions #256

Closed guillaumebrunerie closed 8 months ago

guillaumebrunerie commented 11 months ago

The following piece of code is valid but it is parsed incorrectly:

const makeBoxString = makeBox<string>;

Here's a link to the TypeScript Playground showing that the snippet above is valid JavaScript or TypeScript

The output of tree-sitter parse is the following:

(program [0, 0] - [1, 0]
  (lexical_declaration [0, 0] - [0, 38]
    (variable_declarator [0, 6] - [0, 29]
      name: (identifier [0, 6] - [0, 19])
      value: (identifier [0, 22] - [0, 29]))
    (ERROR [0, 29] - [0, 37]
      (type_arguments [0, 29] - [0, 37]
        (predefined_type [0, 30] - [0, 36])))))

This is a relatively new feature, called "instantiation expressions", since Typescript 4.7: see here.

Probably related: #243.

I tried to implement it myself as follows:

diff --git a/common/define-grammar.js b/common/define-grammar.js
index f9d1b58..7ec3207 100644
--- a/common/define-grammar.js
+++ b/common/define-grammar.js
@@ -18,6 +18,7 @@ module.exports = function defineGrammar(dialect) {

     precedences: ($, previous) => previous.concat([
       [
+        'instantiation',
         'call',
         'unary',
         'binary',
@@ -208,6 +209,7 @@ module.exports = function defineGrammar(dialect) {
         const choices = [
           $.as_expression,
           $.satisfies_expression,
+          $.instantiation_expression,
           $.internal_module,
         ];

@@ -447,6 +449,11 @@ module.exports = function defineGrammar(dialect) {
         $._type
       )),

+      instantiation_expression: $ => prec.left('instantiation', seq(
+        $.expression,
+        field('type_arguments', $.type_arguments),
+      )),
+
       class_heritage: $ => choice(
         seq($.extends_clause, optional($.implements_clause)),
         $.implements_clause

but tree-sitter generate complains about a conflict:

Unresolved conflict for symbol sequence:

  'typeof'  expression  •  '<'  …

Possible interpretations:

  1:  'typeof'  (binary_expression  expression  •  '<'  expression)                (precedence: 'binary_relation', associativity: Left)
  2:  'typeof'  (call_expression  expression  •  type_arguments  arguments)        (precedence: 'call')
  3:  'typeof'  (call_expression  expression  •  type_arguments  template_string)  (precedence: 'call')
  4:  'typeof'  (instantiation_expression  expression  •  type_arguments)          (precedence: 'instantiation', associativity: Left)
  5:  (unary_expression  'typeof'  expression)  •  '<'  …                          (precedence: 'unary_void', associativity: Left)

Possible resolutions:

  1:  Specify a higher precedence in `call_expression` and `binary_expression` and `instantiation_expression` than in the other rules.
  2:  Specify a higher precedence in `unary_expression` than in the other rules.
  3:  Add a conflict for these rules: `call_expression`, `binary_expression`, `unary_expression`, `instantiation_expression`

Note that typeof stuff<T> should be parsed with an instantiation expression (possible interpretation 4). I tried to change the precedences field in many different ways to fix the conflict, but couldn’t figure out how to make it work.

Is there someone who knows how to make it work?