Skip to content

Commit 3aec6ae

Browse files
fix: prefer generic call interpretation for f<T>(x) via prec.dynamic
Add _generic_call_expression rule with required type_arguments and prec.dynamic(1) to resolve the <> generic vs comparison ambiguity. When the GLR parser forks on `<` (type_arguments vs comparison operator), the generic call path now has higher dynamic precedence. The rule is aliased to call_expression so the output tree is unchanged. A separate rule is needed because prec.dynamic on the full call_expression (with optional type_arguments) would also boost trailing lambda calls, breaking object literal class_body parsing and getter/setter accessor recognition. Follows the pattern used by tree-sitter-java (prec.dynamic on generic_type) and tree-sitter-go (prec.dynamic on type_arguments).
1 parent 1e5884e commit 3aec6ae

File tree

3 files changed

+592985
-590348
lines changed

3 files changed

+592985
-590348
lines changed

grammar.js

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,21 @@ module.exports = grammar({
9191
[$._postfix_unary_expression, $._expression],
9292

9393
// ambiguity between generics and comparison operations (foo < b > c)
94-
[$.call_expression, $.navigation_expression, $.range_expression, $.comparison_expression],
95-
[$.call_expression, $.navigation_expression, $.elvis_expression, $.comparison_expression],
96-
[$.call_expression, $.navigation_expression, $.check_expression, $.comparison_expression],
97-
[$.call_expression, $.navigation_expression, $.additive_expression, $.comparison_expression],
98-
[$.call_expression, $.navigation_expression, $.infix_expression, $.comparison_expression],
99-
[$.call_expression, $.navigation_expression, $.multiplicative_expression, $.comparison_expression],
94+
// _generic_call_expression is the prec.dynamic(1) variant with required type_arguments
95+
[$.call_expression, $._generic_call_expression, $.navigation_expression, $.range_expression, $.comparison_expression],
96+
[$.call_expression, $._generic_call_expression, $.navigation_expression, $.elvis_expression, $.comparison_expression],
97+
[$.call_expression, $._generic_call_expression, $.navigation_expression, $.check_expression, $.comparison_expression],
98+
[$.call_expression, $._generic_call_expression, $.navigation_expression, $.additive_expression, $.comparison_expression],
99+
[$.call_expression, $._generic_call_expression, $.navigation_expression, $.infix_expression, $.comparison_expression],
100+
[$.call_expression, $._generic_call_expression, $.navigation_expression, $.multiplicative_expression, $.comparison_expression],
100101
[$.type_arguments, $._comparison_operator],
101102

102103
// ambiguity between prefix expressions and annotations before functions
103104
[$._statement, $.prefix_expression],
104105
[$.prefix_expression, $.when_subject],
105106
[$.prefix_expression, $.value_argument],
106107
// ambiguity between prefix unary operators and other expression types
107-
[$.call_expression, $.navigation_expression, $.prefix_expression, $.comparison_expression],
108+
[$.call_expression, $._generic_call_expression, $.navigation_expression, $.prefix_expression, $.comparison_expression],
108109

109110
// ambiguity between multiple user types and class property/function declarations
110111
[$.user_type],
@@ -156,6 +157,10 @@ module.exports = grammar({
156157
// Three-way conflict: callable_reference (Foo::bar) starts with simple_identifier
157158
// which both primary_expression variants can match.
158159
[$._primary_expression, $._primary_expression_no_trailing_lambda, $.callable_reference],
160+
161+
// ambiguity between call_suffix and _generic_call_suffix (both can match type_arguments + args)
162+
[$.call_suffix, $._generic_call_suffix],
163+
[$._call_suffix_no_trailing_lambda, $._generic_call_suffix_no_trailing_lambda],
159164
],
160165

161166
externals: $ => [
@@ -355,6 +360,7 @@ module.exports = grammar({
355360
$.string_literal,
356361
$.callable_reference,
357362
alias($.call_expression_no_trailing_lambda, $.call_expression),
363+
alias($._generic_call_expression_no_trailing_lambda, $.call_expression),
358364
$._function_literal,
359365
$.object_literal,
360366
$.collection_literal,
@@ -372,11 +378,22 @@ module.exports = grammar({
372378
alias($._call_suffix_no_trailing_lambda, $.call_suffix)
373379
)),
374380

381+
// Generic call (required type_arguments) without trailing lambda
382+
_generic_call_expression_no_trailing_lambda: $ => prec.left(PREC.POSTFIX, prec.dynamic(1, seq(
383+
$._expression_no_trailing_lambda,
384+
alias($._generic_call_suffix_no_trailing_lambda, $.call_suffix)
385+
))),
386+
375387
_call_suffix_no_trailing_lambda: $ => seq(
376388
optional($.type_arguments),
377389
$.value_arguments
378390
),
379391

392+
_generic_call_suffix_no_trailing_lambda: $ => seq(
393+
$.type_arguments,
394+
$.value_arguments
395+
),
396+
380397
type_parameters: $ => seq("<", sep1($.type_parameter, ","), optional(","), ">"),
381398

382399
type_parameter: $ => seq(
@@ -727,6 +744,16 @@ module.exports = grammar({
727744

728745
call_expression: $ => prec.left(PREC.POSTFIX, seq($._expression, $.call_suffix)),
729746

747+
// Generic call expression: call with REQUIRED type_arguments.
748+
// prec.dynamic(1) tells GLR to prefer the generic-call interpretation
749+
// (f<T>(x)) over the comparison interpretation (f < T > (x)).
750+
// Separated from call_expression so the dynamic boost only applies
751+
// when type_arguments are actually parsed, not for all calls.
752+
_generic_call_expression: $ => prec.left(PREC.POSTFIX, prec.dynamic(1, seq(
753+
$._expression,
754+
alias($._generic_call_suffix, $.call_suffix)
755+
))),
756+
730757
indexing_expression: $ => prec.left(PREC.POSTFIX, seq($._expression, $.indexing_suffix)),
731758

732759
navigation_expression: $ => prec.left(PREC.POSTFIX, seq($._expression, $.navigation_suffix)),
@@ -801,6 +828,15 @@ module.exports = grammar({
801828
)
802829
)),
803830

831+
// Call suffix with REQUIRED type_arguments (used by _generic_call_expression)
832+
_generic_call_suffix: $ => prec.left(seq(
833+
$.type_arguments,
834+
choice(
835+
prec(PREC.ARGUMENTS, seq(optional($.value_arguments), $.annotated_lambda)),
836+
$.value_arguments
837+
)
838+
)),
839+
804840
annotated_lambda: $ => seq(
805841
repeat($.annotation),
806842
optional($.label),
@@ -834,6 +870,7 @@ module.exports = grammar({
834870
$.string_literal,
835871
$.callable_reference,
836872
$.call_expression,
873+
alias($._generic_call_expression, $.call_expression),
837874
$._function_literal,
838875
$.object_literal,
839876
$.collection_literal,

0 commit comments

Comments
 (0)