Skip to content

bug: anonymous pointer-to-member declarator (Class::*) is not parsed in abstract-declarator contexts (using, sizeof, cast) #355

@Odiob

Description

@Odiob

Did you check existing issues?

  • I have read all the tree-sitter docs if it relates to using the parser
  • I have searched the existing issues of tree-sitter-cpp

Tree-Sitter CLI Version, if relevant

tree-sitter-cpp 0.23.4, tree-sitter 0.23.2 (Python bindings)

Describe the bug

The grammar handles the named pointer-to-member declarator (void (C::*p)(int)) but has no production for the anonymous / abstract form (C::*) used in type-ids. As a result, every C++ construct whose syntax requires an abstract type-id containing a pointer-to-member fails to parse:

  • alias declarations: using F = void (C::*)(int);
  • sizeof(void (C::*)(int))
  • C-style casts: (void (C::*)(int))p
  • (presumably) function-pointer parameter types, template arguments, decltype, etc.

In every case, has_error is True and the parse tree contains either a MISSING "type_identifier" inside qualified_identifier or a stray abstract_pointer_declarator (*) attached as a sibling.

This bug appears to be the underlying parser misparse behind the error-recovery cascade observed in #352 (which frames the same construct as a template-call / statement-ordering recovery issue). It is distinct from #348 (C++‑style static_cast / typeid misparse) and from #102 / #113 (which fixed only the named declarator).

Steps To Reproduce / Bad Parse Tree

The cleanest reproducer is an alias declaration, because the RHS is required by the C++ grammar to be a type-id — there is no cast / call-expression ambiguity to argue about. The snippet compiles cleanly with g++ -c:

struct C;
using F = void (C::*)(int);

Parses as:

(translation_unit
  (struct_specifier
    (struct)
    name: (type_identifier))
  (;)
  (alias_declaration
    (using)
    name: (type_identifier)
    (=)
    type: (type_descriptor
      type: (primitive_type)
      declarator: (abstract_function_declarator
        declarator: (abstract_function_declarator
          parameters: (parameter_list
            (()
            (parameter_declaration
              type: (qualified_identifier
                scope: (namespace_identifier)
                (::)
                name: (MISSING "type_identifier"))
              declarator: (abstract_pointer_declarator
                (*)))
            ())))
        parameters: (parameter_list
          (()
          (parameter_declaration
            type: (primitive_type))
          ()))))
    (;)))
;; has_error = True

The grammar tries to interpret the inner (C::*) as a parameter_list whose sole parameter_declaration has type qualified_identifier (C::) with a MISSING type_identifier, plus an orphan abstract_pointer_declarator (*).

The same misparse appears in sizeof:

struct C;
auto s = sizeof(void (C::*)(int));
(... (sizeof_expression
       (sizeof)
       value: (parenthesized_expression
         (()
         (call_expression
           function: (call_expression
             function: (primitive_type)
             arguments: (argument_list
               (()
               (qualified_identifier
                 scope: (namespace_identifier)
                 (::)
                 name: (pointer_type_declarator
                   (*)
                   declarator: (MISSING "type_identifier")))
               ())))
           arguments: (argument_list
             (()
             (ERROR
               (primitive_type))
             ())))
         ()))) ...)
;; has_error = True

And in a C-style cast:

struct C;
auto x = (void (C::*)(int))0;
(... value: (parenthesized_expression
              (()
              (call_expression
                function: (call_expression
                  function: (primitive_type)
                  arguments: (argument_list
                    (()
                    (qualified_identifier
                      scope: (namespace_identifier)
                      (::)
                      name: (pointer_type_declarator
                        (*)
                        declarator: (MISSING "type_identifier")))
                    ())))
                arguments: (argument_list
                  (()
                  (ERROR
                    (primitive_type))
                  ())))
              ())) ...)
;; has_error = True

Expected Behavior / Parse Tree

All three abstract-form snippets should parse with has_error = False. The RHS type of the alias should be a type_descriptor whose declarator is some abstract pointer-to-member declarator (mirroring the named form), roughly:

type: (type_descriptor
  type: (primitive_type)                                ; void
  declarator: (abstract_function_declarator
    declarator: (abstract_parenthesized_declarator
      (()
      (qualified_identifier                             ; C::*
        scope: (namespace_identifier)
        (::)
        name: (abstract_pointer_declarator
          (*)))
      ()))
    parameters: (parameter_list (() (parameter_declaration type: (primitive_type)) ())))) ; (int)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions