Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Functional cast with `unsigned` / `signed` / `long` / `short` (e.g. `unsigned(x)`) parsed as a declaration in conditions, producing ERROR

Open Beginner friendly
#374 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
82/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript
Domain
compilers

Research direction

Start in grammar.js at the call_expression rule (the issue links the relevant lines), then review how its function field handles primitive_type. Run tree-sitter generate and tree-sitter test, and parse the minimal if (unsigned(id) < 8) example. Done means the example and while (long(n) > 0) parse without errors while the listed declaration examples keep their existing trees.

Written by the indexing model from the issue text.

Description

Description

A functional-notation cast whose type is a sized_type_specifier (unsigned(x), signed(x), long(x), short(x)) is not
parsed as an expression wherever a declaration could also start: at the beginning of a statement or in the condition of
if / while / switch. The parser commits to a declaration (unsigned as the type, (x) as a parenthesized declarator),
and the following operator (<, >=, ==, ...) produces an ERROR node.

The same code with a primitive_type (int(x)) parses fine, because call_expression accepts primitive_type as the
function field, but not sized_type_specifier. In positions where a declaration is impossible (e.g. return unsigned(id) < 8;)
there is no error only because unsigned is lexed as an identifier and the cast becomes an ordinary call.

Real-world example: fmt, include/fmt/core.h (basic_format_args::get):

if (unsigned(id) < unsigned(max_size())) arg = args_[id];

In larger files the error recovery after this ERROR can swallow much more than the condition itself (in our runs on fmt
headers it absorbed the enclosing class body).

Checked with tree-sitter CLI 0.26.3 on:

  • 12bd6f7 (v0.23.4-37, 2025-09-27);
  • current master c009222 (v0.23.4-41, 2026-09-17).

Both produce the same tree.

Minimal example

bool f(int id) {
  if (unsigned(id) < 8) return true;
  return false;
}

Actual (tree-sitter parse, identical on 12bd6f7 and c009222)

(translation_unit [0, 0] - [4, 0]
  (function_definition [0, 0] - [3, 1]
    type: (primitive_type [0, 0] - [0, 4])
    declarator: (function_declarator [0, 5] - [0, 14]
      declarator: (identifier [0, 5] - [0, 6])
      parameters: (parameter_list [0, 6] - [0, 14]
        (parameter_declaration [0, 7] - [0, 13]
          type: (primitive_type [0, 7] - [0, 10])
          declarator: (identifier [0, 11] - [0, 13]))))
    body: (compound_statement [0, 15] - [3, 1]
      (if_statement [1, 2] - [1, 36]
        condition: (condition_clause [1, 5] - [1, 23]
          (ERROR [1, 6] - [1, 20]
            type: (sized_type_specifier [1, 6] - [1, 14])
            (parenthesized_declarator [1, 14] - [1, 18]
              (identifier [1, 15] - [1, 17])))
          value: (number_literal [1, 21] - [1, 22]))
        consequence: (return_statement [1, 24] - [1, 36]
          (true [1, 31] - [1, 35])))
      (return_statement [2, 2] - [2, 15]
        (false [2, 9] - [2, 14])))))
ex.cc	Parse:    0.77 ms	    93 bytes/ms	(ERROR [1, 6] - [1, 20])

Expected

The same shape as for if (int(id) < 8), with sized_type_specifier as the function of the call_expression
(output of a local build with the change proposed below):

(translation_unit [0, 0] - [4, 0]
  (function_definition [0, 0] - [3, 1]
    type: (primitive_type [0, 0] - [0, 4])
    declarator: (function_declarator [0, 5] - [0, 14]
      declarator: (identifier [0, 5] - [0, 6])
      parameters: (parameter_list [0, 6] - [0, 14]
        (parameter_declaration [0, 7] - [0, 13]
          type: (primitive_type [0, 7] - [0, 10])
          declarator: (identifier [0, 11] - [0, 13]))))
    body: (compound_statement [0, 15] - [3, 1]
      (if_statement [1, 2] - [1, 36]
        condition: (condition_clause [1, 5] - [1, 23]
          value: (binary_expression [1, 6] - [1, 22]
            left: (call_expression [1, 6] - [1, 18]
              function: (sized_type_specifier [1, 6] - [1, 14])
              arguments: (argument_list [1, 14] - [1, 18]
                (identifier [1, 15] - [1, 17])))
            right: (number_literal [1, 21] - [1, 22])))
        consequence: (return_statement [1, 24] - [1, 36]
          (true [1, 31] - [1, 35])))
      (return_statement [2, 2] - [2, 15]
        (false [2, 9] - [2, 14])))))

Cause

call_expression only allows primitive_type (or a splice type) as the function of a functional cast:
https://github.com/tree-sitter/tree-sitter-cpp/blob/c009222808634c1014f82438d4883753516a2c24/grammar.js#L1080-L1086

call_expression: ($, original) => prec.dynamic(1, choice(original, seq(
  field('function', choice($.primitive_type, seq(
    optional('typename'),
    $.splice_type_specifier,
  ))),
  field('arguments', $.argument_list),
))),

Proposed fix (one line)

     call_expression: ($, original) => prec.dynamic(1, choice(original, seq(
-      field('function', choice($.primitive_type, seq(
+      field('function', choice($.primitive_type, $.sized_type_specifier, seq(
         optional('typename'),
         $.splice_type_specifier,
       ))),

With this change on master c009222:

  • tree-sitter generate (CLI 0.26.3) succeeds without new conflicts;
  • tree-sitter test passes (200 of 200, same as without the change);
  • the example above parses without errors (tree above), and so does while (long(n) > 0) --n;
    (on master it gives the same kind of ERROR with long as sized_type_specifier);
  • declarations stay declarations: unsigned (*fp)(int);, std::function<unsigned(int)> h;,
    unsigned long y = (unsigned long)(x); give the same trees as before.

One visible side effect: the statement unsigned (x); becomes an expression_statement with a call_expression instead of a
declaration. This matches how master already parses int (x); (also a call_expression), so the two are now consistent;
if the declaration reading is preferred for both, that is a separate question about prec.dynamic(1, ...) on this rule.

Dominant language
JavaScript
Stars
459
Forks
190
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from tree-sitter/tree-sitter-cpp

All issues in tree-sitter/tree-sitter-cpp

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.