diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 46efef5..93a9446 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -1,5 +1,6 @@ from collections import deque +import contextlib import inspect import re import typing @@ -37,6 +38,7 @@ FunctionType, FundamentalSpecifier, Method, + MemberPointer, MoveReference, NameSpecifier, NamespaceAlias, @@ -56,6 +58,7 @@ TemplateTypeParam, Token, Type, + TypeId, Typedef, UsingAlias, UsingDecl, @@ -70,6 +73,13 @@ PT = typing.TypeVar("PT", Parameter, TemplateNonTypeParam) +class _FunctionTypeQualifiers(typing.NamedTuple): + const: bool + volatile: bool + ref_qualifier: typing.Optional[str] + noexcept: typing.Optional[Value] + + class CxxParser: """ Single-use parser object @@ -173,6 +183,23 @@ def _next_token_must_be(self, *tokenTypes: str) -> LexToken: raise self._parse_error(tok, "' or '".join(tokenTypes)) return tok + @contextlib.contextmanager + def _bounded_token_stream( + self, toks: LexTokenList + ) -> typing.Iterator[lexer.BoundedTokenStream]: + old_lex = self.lex + old_pending_attributes = self._pending_attributes + old_anon_id = self.anon_id + bounded_lex = lexer.BoundedTokenStream(toks) + try: + self.lex = bounded_lex + self._pending_attributes = [] + yield bounded_lex + finally: + self.lex = old_lex + self._pending_attributes = old_pending_attributes + self.anon_id = old_anon_id + # def _next_token_in_set(self, tokenTypes: typing.Set[str]) -> LexToken: # tok = self.lex.token() # if tok.type not in tokenTypes: @@ -273,39 +300,34 @@ def _consume_balanced_tokens( if next_end: match_stack.append(next_end) - def _consume_balanced_tokens_with_inner( - self, - *init_tokens: LexToken, - token_map: typing.Optional[typing.Dict[str, str]] = None, - ) -> typing.Tuple[LexTokenList, LexTokenList]: - toks = self._consume_balanced_tokens(*init_tokens, token_map=token_map) - inner_toks = toks[1:-1] - + @staticmethod + def _strip_enclosing_parens(toks: LexTokenList) -> LexTokenList: # Redundant declarator grouping is valid: int ((*p))(int), # int ((*p))[3], and void ((f))(int). Strip only parens that - # enclose the whole inner token list. - while ( - len(inner_toks) >= 2 - and inner_toks[0].type == "(" - and inner_toks[-1].type == ")" - ): + # enclose the whole token list. + while len(toks) >= 2 and toks[0].type == "(" and toks[-1].type == ")": depth = 0 - encloses_all = True - for i, itok in enumerate(inner_toks): - if itok.type == "(": + for i, tok in enumerate(toks): + if tok.type == "(": depth += 1 - elif itok.type == ")": + elif tok.type == ")": depth -= 1 - if depth == 0 and i != len(inner_toks) - 1: - encloses_all = False + if depth == 0: break - if not encloses_all or depth != 0: + if i != len(toks) - 1: break - inner_toks = inner_toks[1:-1] + toks = toks[1:-1] - return toks, inner_toks + return toks + + def _consume_paren_group( + self, tok: LexToken + ) -> typing.Tuple[LexTokenList, LexTokenList]: + assert tok.type == "(" + toks = self._consume_balanced_tokens(tok) + return toks, self._strip_enclosing_parens(toks[1:-1]) def _discard_contents(self, start_type: str, end_type: str) -> None: # use this instead of consume_balanced_tokens because @@ -740,19 +762,9 @@ def _parse_template_specialization(self) -> TemplateSpecialization: # append a token to make other parsing components happy raw_toks.append(PhonyEnding) - old_lex = self.lex - try: - # set up a temporary token stream with the tokens we need to parse - tmp_lex = lexer.BoundedTokenStream(raw_toks) - self.lex = tmp_lex - + with self._bounded_token_stream(raw_toks) as tmp_lex: try: - parsed_type, mods = self._parse_type(None) - if parsed_type is None: - raise self._parse_error(None) - - mods.validate(var_ok=False, meth_ok=False, msg="") - dtype = self._parse_cv_ptr_or_fn(parsed_type, nonptr_fn=True) + dtype = self._parse_type_id(None, "") self._next_token_must_be(PhonyEnding.type) except CxxParseError: dtype = None @@ -760,9 +772,6 @@ def _parse_template_specialization(self) -> TemplateSpecialization: if tmp_lex.has_tokens(): dtype = None - finally: - self.lex = old_lex - if self.lex.token_if("ELLIPSIS"): param_pack = True @@ -1182,13 +1191,7 @@ def _parse_using_typealias( alias_declaration: "using" IDENTIFIER "=" type_id ";" """ - parsed_type, mods = self._parse_type(None) - if parsed_type is None: - raise self._parse_error(None) - - mods.validate(var_ok=False, meth_ok=False, msg="parsing typealias") - - dtype = self._parse_cv_ptr(parsed_type) + dtype = self._parse_type_id(None, "parsing typealias") alias = UsingAlias(id_tok.value, dtype, template, self._current_access, doxygen) @@ -2021,17 +2024,20 @@ def _parse_parameter( at_type = Type(parsed_type.typename) parsed_type.typename = PQName([AutoSpecifier()]) - dtype = self._parse_cv_ptr(parsed_type) + dtype = self._parse_cv_ptr_or_fn( + parsed_type, nonptr_fn=True, grouped_parameter_name_ok=True + ) + if isinstance(dtype, FunctionType): + dtype = Pointer(dtype) # optional parameter pack if self.lex.token_if("ELLIPSIS"): param_pack = True - - # name can be surrounded by parens - tok = self.lex.token_if("(") - if tok: - toks = self._consume_balanced_tokens(tok) - self.lex.return_tokens(toks[1:-1]) + # Preserve the accepted ``T ...(name)`` spelling. + tok = self.lex.token_if("(") + if tok: + _, name_toks = self._consume_paren_group(tok) + self.lex.return_tokens(name_toks) # optional name tok = self.lex.token_if("NAME", "final") @@ -2043,8 +2049,7 @@ def _parse_parameter( # are adjusted to pointers to functions, matching the explicit # ``bool (*predicate)(const T&)`` spelling. if param_name and self.lex.token_if("("): - fn_params, vararg, _ = self._parse_parameters(False, False) - dtype = Pointer(FunctionType(dtype, fn_params, vararg)) + dtype = Pointer(self._parse_function_type(dtype)) # optional array parameter tok = self.lex.token_if("[") @@ -2143,34 +2148,35 @@ def _parse_trailing_return_type( f"function with trailing return type must specify return type of 'auto', not {return_type}" ) - parsed_type, mods = self._parse_type(None) + dtype = self._parse_type_id(None, "parsing trailing return type") + # Bare arrays and functions are invalid return types, but wrapped forms + # are DecoratedType instances and remain valid. + if isinstance(dtype, Array): + raise self._parse_error(None) + if isinstance(dtype, FunctionType): + raise self._parse_error(None) + return dtype + + def _parse_type_id(self, tok: typing.Optional[LexToken], msg: str) -> TypeId: + parsed_type, mods = self._parse_type(tok) if parsed_type is None: raise self._parse_error(None) - mods.validate(var_ok=False, meth_ok=False, msg="parsing trailing return type") + mods.validate(var_ok=False, meth_ok=False, msg=msg) + dtype = self._parse_cv_ptr_or_fn(parsed_type, nonptr_fn=True) - dtype = self._parse_cv_ptr(parsed_type) + atok = self.lex.token_if("[") + while atok: + dtype = self._parse_array_type(atok, dtype) + atok = self.lex.token_if("[") return dtype - def parse_typename(self) -> DecoratedType: + def parse_typename(self) -> TypeId: """ - Parse a single C++ type name from the current token stream. + Parse a single C++ type-id from the current token stream. """ - parsed_type, mods = self._parse_type(None) - if parsed_type is None: - raise CxxParseError("missing type name") - - mods.validate(var_ok=False, meth_ok=False, msg="parsing type name") - - dtype = self._parse_cv_ptr_or_fn(parsed_type) - if isinstance(dtype, FunctionType): - raise CxxParseError("function types are not supported") - - tok = self.lex.token_if("[") - while tok: - dtype = self._parse_array_type(tok, dtype) - tok = self.lex.token_if("[") + dtype = self._parse_type_id(None, "parsing type name") self.lex.token_if(";") extra = self.lex.token_eof_ok() @@ -2432,6 +2438,8 @@ def _parse_function( inline=mods.inline is not None, msvc_convention=msvc_convention_value, ) + if is_typedef: + fntype_qualifiers = self._parse_function_type_qualifiers() self._parse_fn_end(fn) if is_typedef: @@ -2465,8 +2473,11 @@ def _parse_function( fn.parameters, fn.vararg, fn.has_trailing_return, - noexcept=fn.noexcept, + noexcept=fntype_qualifiers.noexcept, msvc_convention=fn.msvc_convention, + const=fntype_qualifiers.const, + volatile=fntype_qualifiers.volatile, + ref_qualifier=fntype_qualifiers.ref_qualifier, ) typedef = Typedef(fntype, name, self._current_access, attributes or []) @@ -2483,11 +2494,62 @@ def _parse_function( # Decorated type parsing # - def _parse_array_type(self, tok: LexToken, dtype: DecoratedType) -> Array: + def _parse_function_type_qualifiers(self) -> _FunctionTypeQualifiers: + const = False + volatile = False + while True: + tok = self.lex.token_if("const", "volatile") + if not tok: + break + if tok.type == "const": + const = True + else: + volatile = True + + tok = self.lex.token_if("&", "DBL_AMP") + ref_qualifier = None + if tok: + ref_qualifier = "&" if tok.type == "&" else "&&" + + noexcept = None + if self.lex.token_if("noexcept"): + toks = [] + otok = self.lex.token_if("(") + if otok: + toks = self._consume_balanced_tokens(otok)[1:-1] + noexcept = self._create_value(toks) + + return _FunctionTypeQualifiers(const, volatile, ref_qualifier, noexcept) + + def _parse_function_type( + self, + return_type: DecoratedType, + msvc_convention: typing.Optional[str] = None, + ) -> FunctionType: + parameters, vararg, _ = self._parse_parameters(False, False) + qualifiers = self._parse_function_type_qualifiers() + fntype = FunctionType( + return_type, + parameters, + vararg, + noexcept=qualifiers.noexcept, + msvc_convention=msvc_convention, + const=qualifiers.const, + volatile=qualifiers.volatile, + ref_qualifier=qualifiers.ref_qualifier, + ) + if self.lex.token_if("ARROW"): + fntype.return_type = self._parse_trailing_return_type(fntype.return_type) + fntype.has_trailing_return = True + return fntype + + def _parse_array_type(self, tok: LexToken, dtype: TypeId) -> Array: assert tok.type == "[" if isinstance(dtype, (Reference, MoveReference)): raise CxxParseError("arrays of references are illegal", tok) + if isinstance(dtype, FunctionType): + raise self._parse_error(tok) toks = self._consume_balanced_tokens(tok) otok = self.lex.token_if("[") @@ -2503,6 +2565,65 @@ def _parse_array_type(self, tok: LexToken, dtype: DecoratedType) -> Array: return Array(dtype, size) + def _parse_member_pointer_classname(self, toks: LexTokenList) -> PQName: + class_toks = toks + [PhonyEnding] + with self._bounded_token_stream(class_toks): + classname, _ = self._parse_pqname( + None, compound_ok=False, fn_ok=False, fund_ok=False + ) + self._next_token_must_be(PhonyEnding.type) + + return classname + + def _try_parse_member_pointer_operator( + self, dtype: TypeId, tok: LexToken + ) -> typing.Optional[MemberPointer]: + toks = [tok] + + if tok.type == "DBL_COLON": + next_tok = self.lex.token_if("NAME", "final", "decltype") + if not next_tok: + self.lex.return_tokens(toks) + return None + tok = next_tok + toks.append(tok) + + while True: + if tok.type == "decltype": + ptok = self.lex.token_if("(") + if not ptok: + self.lex.return_tokens(toks) + return None + toks.extend(self._consume_balanced_tokens(ptok)) + else: + ltok = self.lex.token_if("<") + if ltok: + toks.extend(self._consume_balanced_tokens(ltok)) + + colon = self.lex.token_if("DBL_COLON") + if not colon: + self.lex.return_tokens(toks) + return None + + star = self.lex.token_if("*") + if star: + classname = self._parse_member_pointer_classname(toks) + if isinstance(dtype, (Reference, MoveReference)): + raise self._parse_error(star) + return MemberPointer(dtype, classname) + + toks.append(colon) + template_tok = self.lex.token_if("template") + if template_tok: + toks.append(template_tok) + + next_tok = self.lex.token_if("NAME", "final", "decltype") + if not next_tok: + self.lex.return_tokens(toks) + return None + tok = next_tok + toks.append(tok) + def _parse_cv_ptr( self, dtype: DecoratedType, @@ -2514,14 +2635,30 @@ def _parse_cv_ptr( def _parse_cv_ptr_or_fn( self, - dtype: typing.Union[ - Array, Pointer, MoveReference, Reference, Type, FunctionType - ], + dtype: TypeId, nonptr_fn: bool = False, - ) -> typing.Union[Array, Pointer, MoveReference, Reference, Type, FunctionType]: + grouped_parameter_name_ok: bool = False, + group_probe: bool = False, + ) -> TypeId: # nonptr_fn is for parsing function types directly in template specialization while True: + # A member-pointer operator can only start with one of these tokens. + # Keep this constant-time rejection visible before trying to parse + # the qualified owner name. + member_pointer_tok = self.lex.token_if( + "NAME", "final", "decltype", "DBL_COLON" + ) + if member_pointer_tok: + member_pointer = self._try_parse_member_pointer_operator( + dtype, member_pointer_tok + ) + if member_pointer: + dtype = member_pointer + if group_probe: + return dtype + continue + tok = self.lex.token_if( "*", "const", "volatile", "__restrict__", "__restrict", "restrict", "(" ) @@ -2532,104 +2669,88 @@ def _parse_cv_ptr_or_fn( if isinstance(dtype, (Reference, MoveReference)): raise self._parse_error(tok) dtype = Pointer(dtype) + if group_probe: + return dtype elif tok.type == "const": - if not isinstance(dtype, (Pointer, Type)): + if not isinstance(dtype, (Pointer, MemberPointer, Type)): raise self._parse_error(tok) dtype.const = True elif tok.type == "volatile": - if not isinstance(dtype, (Pointer, Type)): + if not isinstance(dtype, (Pointer, MemberPointer, Type)): raise self._parse_error(tok) dtype.volatile = True elif tok.type in ("__restrict__", "__restrict", "restrict"): - if not isinstance(dtype, (Pointer, Reference)): + if not isinstance(dtype, (Pointer, MemberPointer, Reference)): raise self._parse_error(tok) dtype.restrict = True - elif nonptr_fn: - # remove any inner grouping parens - while True: - gtok = self.lex.token_if("(") - if not gtok: - break + else: + toks, inner_toks = self._consume_paren_group(tok) - _, inner_toks = self._consume_balanced_tokens_with_inner(gtok) - self.lex.return_tokens(inner_toks) + msvc_convention = None + if inner_toks and inner_toks[0].value in self._msvc_conventions: + msvc_convention = inner_toks[0].value + inner_toks = self._strip_enclosing_parens(inner_toks[1:]) + + grouped_declarator = grouped_parameter_name_ok and ( + (len(inner_toks) == 1 and inner_toks[0].type in ("NAME", "final")) + or ( + len(inner_toks) == 2 + and inner_toks[0].type == "ELLIPSIS" + and inner_toks[1].type in ("NAME", "final") + ) + ) + if inner_toks and not grouped_declarator: + # Let the normal declarator parser decide whether the group + # contains pointer/reference syntax. A dummy base type keeps + # this bounded probe from changing the real type. + probe_type = Type(PQName([])) + with self._bounded_token_stream(inner_toks + [PhonyEnding]): + parsed_probe_type = self._parse_cv_ptr_or_fn( + probe_type, + grouped_parameter_name_ok=grouped_parameter_name_ok, + group_probe=True, + ) + grouped_declarator = parsed_probe_type is not probe_type - fn_params, vararg, _ = self._parse_parameters(False, False) + if grouped_declarator and group_probe: + # The probe result is tested only by identity. Stop before + # parsing the same nested declarator a second time. + return Type(PQName([])) - assert not isinstance(dtype, FunctionType) - dtype = dtype_fn = FunctionType(dtype, fn_params, vararg) - if self.lex.token_if("ARROW"): - return_type = self._parse_trailing_return_type(dtype_fn.return_type) - dtype_fn.has_trailing_return = True - dtype_fn.return_type = return_type + if not grouped_declarator: + if not nonptr_fn: + self.lex.return_tokens(toks) + break - else: - msvc_convention = None - msvc_convention_tok = self.lex.token_if_val(*self._msvc_conventions) - if msvc_convention_tok: - msvc_convention = msvc_convention_tok.value - - # this might be a grouping paren, so consume it and inspect it - toks, inner_toks = self._consume_balanced_tokens_with_inner(tok) - member_ptr_idx = next( - ( - i - for i, itok in enumerate(inner_toks) - if itok.type == "*" - and i > 0 - and inner_toks[i - 1].type == "DBL_COLON" - ), - None, - ) + self.lex.return_tokens(toks[1:]) + # Parentheses around the first parameter do not make this + # a grouped declarator: int((double), char). + while True: + gtok = self.lex.token_if("(") + if not gtok: + break + _, parameter_toks = self._consume_paren_group(gtok) + self.lex.return_tokens(parameter_toks) - # Check to see if this is a grouping paren or something else - if not inner_toks or ( - inner_toks[0].type not in ("*", "&") and member_ptr_idx is None - ): - self.lex.return_tokens(toks) - break + assert not isinstance(dtype, FunctionType) + dtype = self._parse_function_type(dtype) + continue - # Now check to see if we have either an array or a function pointer + # Postfix operators outside the group bind before pointer and + # reference operators inside it. aptok = self.lex.token_if("[", "(") if aptok: if aptok.type == "[": assert not isinstance(dtype, FunctionType) dtype = self._parse_array_type(aptok, dtype) - elif aptok.type == "(": - fn_params, vararg, _ = self._parse_parameters(False, False) - # the type we already have is the return type of the function pointer - + else: assert not isinstance(dtype, FunctionType) + dtype = self._parse_function_type(dtype, msvc_convention) - dtype = FunctionType( - dtype, fn_params, vararg, msvc_convention=msvc_convention - ) - - if isinstance(dtype, FunctionType) and member_ptr_idx is not None: - # Keep the * and declarator name for the normal pointer/name - # parsing below, and store the qualified class name on the - # function type. - class_toks = inner_toks[: member_ptr_idx - 1] + [PhonyEnding] - old_lex = self.lex - try: - self.lex = lexer.BoundedTokenStream(class_toks) - classname, _ = self._parse_pqname( - None, compound_ok=False, fn_ok=False, fund_ok=False - ) - self._next_token_must_be(PhonyEnding.type) - finally: - self.lex = old_lex - - dtype.classname = classname - inner_toks = [inner_toks[member_ptr_idx]] + inner_toks[ - member_ptr_idx + 1 : - ] - - # return the inner toks and recurse - # -> this could return some weird results for invalid code, but - # we don't support that anyways so it's fine? self.lex.return_tokens(inner_toks) - dtype = self._parse_cv_ptr_or_fn(dtype, nonptr_fn) + dtype = self._parse_cv_ptr_or_fn( + dtype, nonptr_fn, grouped_parameter_name_ok + ) break tok = self.lex.token_if("&", "DBL_AMP") @@ -2641,10 +2762,15 @@ def _parse_cv_ptr_or_fn( else: dtype = MoveReference(dtype) + if group_probe: + return dtype + # peek at the next token and see if it's a paren. If so, it might # be a nasty function pointer if self.lex.token_peek_if("(", "__restrict__", "__restrict", "restrict"): - dtype = self._parse_cv_ptr_or_fn(dtype, nonptr_fn) + dtype = self._parse_cv_ptr_or_fn( + dtype, nonptr_fn, grouped_parameter_name_ok + ) return dtype @@ -2849,7 +2975,7 @@ def _parse_decl( if dtype: # if it's not a constructor/destructor, it could be a # grouping paren like "void (name(int x));" - toks, inner_toks = self._consume_balanced_tokens_with_inner(tok) + toks, inner_toks = self._consume_paren_group(tok) # check to see if the next token is an arrow, and thus a trailing return if self.lex.token_peek_if("ARROW"): @@ -2860,6 +2986,7 @@ def _parse_decl( else: # .. not sure what it's grouping, so put it back? self.lex.return_tokens(inner_toks) + dtype = self._parse_cv_ptr(dtype) if dtype: msvc_convention = self.lex.token_if_val(*self._msvc_conventions) diff --git a/cxxheaderparser/simple.py b/cxxheaderparser/simple.py index 127df4a..c8908ce 100644 --- a/cxxheaderparser/simple.py +++ b/cxxheaderparser/simple.py @@ -36,7 +36,6 @@ ClassDecl, Concept, DeductionGuide, - DecoratedType, EnumDecl, Field, ForwardDecl, @@ -45,6 +44,7 @@ Method, NamespaceAlias, TemplateInst, + TypeId, Typedef, UsingAlias, UsingDecl, @@ -354,9 +354,9 @@ def parse_typename( *, filename: str = "", options: typing.Optional[ParserOptions] = None, -) -> DecoratedType: +) -> TypeId: """ - Parse a C++ type name and return a DecoratedType. + Parse a C++ type name and return a TypeId. """ parser = CxxParser(filename, f"{typename};", null_visitor, options) return parser.parse_typename() diff --git a/cxxheaderparser/types.py b/cxxheaderparser/types.py index c9d9f42..eb25cfb 100644 --- a/cxxheaderparser/types.py +++ b/cxxheaderparser/types.py @@ -229,9 +229,9 @@ class TemplateArgument: """ - #: If this argument is a type, it is stored here as a DecoratedType, - #: otherwise it's stored as an unparsed set of values - arg: typing.Union["DecoratedType", "FunctionType", Value] + #: If this argument is a type, it is stored here as a TypeId, otherwise + #: it's stored as an unparsed set of values + arg: typing.Union["TypeId", Value] param_pack: bool = False @@ -260,13 +260,26 @@ def format(self) -> str: return f"<{', '.join(arg.format() for arg in self.args)}>" +def _join_prefix(prefix: str, declarator: str) -> str: + separator = " " if declarator and declarator[0] not in "*&[" else "" + return f"{prefix}{separator}{declarator}" + + +def _format_prefixed_type(target: typing.Any, prefix: str, name: str) -> str: + declarator = _join_prefix(prefix, name) + if isinstance(target, (Array, FunctionType)): + declarator = f"({declarator})" + return target.format_decl(declarator) + + @dataclass class FunctionType: """ - A function type, currently only used in a function pointer + A function type. It may be used bare in a TypeId or wrapped by a + pointer-like DecoratedType. - .. note:: There can only be one of FunctionType or Type in a DecoratedType - chain + Its return type is always a DecoratedType; functions cannot return bare + function types. """ return_type: "DecoratedType" @@ -290,25 +303,43 @@ class FunctionType: #: calling convention msvc_convention: typing.Optional[str] = None - #: If a member function pointer, the class that owns the member function. - classname: typing.Optional[PQName] = None + const: bool = False + volatile: bool = False + + #: Ref qualifier, either ``&`` or ``&&``. + ref_qualifier: typing.Optional[str] = None + + def _format_qualifiers(self) -> str: + qualifiers = [] + if self.const: + qualifiers.append("const") + if self.volatile: + qualifiers.append("volatile") + if self.ref_qualifier: + qualifiers.append(self.ref_qualifier) + return f" {' '.join(qualifiers)}" if qualifiers else "" def format(self) -> str: - vararg = "..." if self.vararg else "" - params = ", ".join(p.format() for p in self.parameters) - if self.has_trailing_return: - return f"auto ({params}{vararg}) -> {self.return_type.format()}" - else: - return f"{self.return_type.format()} ({params}{vararg})" + return self.format_decl("") def format_decl(self, name: str) -> str: """Format as a named declaration""" - vararg = "..." if self.vararg else "" - params = ", ".join(p.format() for p in self.parameters) + params = [p.format() for p in self.parameters] + if self.vararg: + params.append("...") + params_str = ", ".join(params) + qualifiers = self._format_qualifiers() + noexcept = "" + if self.noexcept is not None: + noexcept = " noexcept" + if self.noexcept.tokens: + noexcept += f"({self.noexcept.format()})" if self.has_trailing_return: - return f"auto {name}({params}{vararg}) -> {self.return_type.format()}" + return f"auto {name}({params_str}){qualifiers}{noexcept} -> {self.return_type.format()}" else: - return f"{self.return_type.format()} {name}({params}{vararg})" + return self.return_type.format_decl( + f"{name}({params_str}){qualifiers}{noexcept}" + ) @dataclass @@ -327,11 +358,12 @@ def format(self) -> str: v = "volatile " if self.volatile else "" return f"{c}{v}{self.typename.format()}" - def format_decl(self, name: str): + def format_decl(self, name: str) -> str: """Format as a named declaration""" c = "const " if self.const else "" v = "volatile " if self.volatile else "" - return f"{c}{v}{self.typename.format()} {name}" + separator = " " if name and name[0] not in "*&[" else "" + return f"{c}{v}{self.typename.format()}{separator}{name}" @dataclass @@ -342,7 +374,7 @@ class Array: """ #: The type that this is an array of - array_of: typing.Union["Array", "Pointer", Type] + array_of: typing.Union["Array", "MemberPointer", "Pointer", Type] #: Size of the array #: @@ -353,12 +385,11 @@ class Array: size: typing.Optional[Value] def format(self) -> str: - s = self.size.format() if self.size else "" - return f"{self.array_of.format()}[{s}]" + return self.format_decl("") def format_decl(self, name: str) -> str: - s = self.size.format() if self.size else "" - return f"{self.array_of.format()} {name}[{s}]" + size = self.size.format() if self.size else "" + return self.array_of.format_decl(f"{name}[{size}]") @dataclass @@ -368,38 +399,50 @@ class Pointer: """ #: Thing that this points to - ptr_to: typing.Union[Array, FunctionType, "Pointer", Type] + ptr_to: typing.Union[Array, FunctionType, "MemberPointer", "Pointer", Type] const: bool = False volatile: bool = False restrict: bool = False def format(self) -> str: + return self.format_decl("") + + def format_decl(self, name: str) -> str: + """Format as a named declaration""" c = " const" if self.const else "" v = " volatile" if self.volatile else "" r = " __restrict__" if self.restrict else "" - ptr_to = self.ptr_to - if isinstance(ptr_to, FunctionType) and ptr_to.classname: - return ptr_to.format_decl(f"({ptr_to.classname.format()}::*{r}{c}{v})") - elif isinstance(ptr_to, (Array, FunctionType)): - return ptr_to.format_decl(f"(*{r}{c}{v})") - else: - return f"{ptr_to.format()}*{r}{c}{v}" + return _format_prefixed_type(self.ptr_to, f"*{r}{c}{v}", name) + - def format_decl(self, name: str): +@dataclass +class MemberPointer: + """ + A pointer to a class member. The member may be an object or a function. + """ + + #: Thing that this points to + ptr_to: typing.Union[Array, FunctionType, "MemberPointer", Pointer, Type] + + #: Class that owns the member + classname: PQName + + const: bool = False + volatile: bool = False + restrict: bool = False + + def format(self) -> str: + return self.format_decl("") + + def format_decl(self, name: str) -> str: """Format as a named declaration""" c = " const" if self.const else "" v = " volatile" if self.volatile else "" r = " __restrict__" if self.restrict else "" - ptr_to = self.ptr_to - if isinstance(ptr_to, FunctionType) and ptr_to.classname: - return ptr_to.format_decl( - f"({ptr_to.classname.format()}::*{r}{c}{v} {name})" - ) - elif isinstance(ptr_to, (Array, FunctionType)): - return ptr_to.format_decl(f"(*{r}{c}{v} {name})") - else: - return f"{ptr_to.format()}*{r}{c}{v} {name}" + return _format_prefixed_type( + self.ptr_to, f"{self.classname.format()}::*{r}{c}{v}", name + ) @dataclass @@ -408,27 +451,16 @@ class Reference: A lvalue (``&``) reference """ - ref_to: typing.Union[Array, FunctionType, Pointer, Type] + ref_to: typing.Union[Array, FunctionType, MemberPointer, Pointer, Type] restrict: bool = False def format(self) -> str: - ref_to = self.ref_to + return self.format_decl("") - if isinstance(ref_to, Array): - return ref_to.format_decl("(&)") - else: - r = " __restrict__" if self.restrict else "" - return f"{ref_to.format()}&{r}" - - def format_decl(self, name: str): + def format_decl(self, name: str) -> str: """Format as a named declaration""" - ref_to = self.ref_to - - if isinstance(ref_to, Array): - return ref_to.format_decl(f"(& {name})") - else: - r = " __restrict__" if self.restrict else "" - return f"{ref_to.format()}&{r} {name}" + r = " __restrict__" if self.restrict else "" + return _format_prefixed_type(self.ref_to, f"&{r}", name) @dataclass @@ -437,21 +469,25 @@ class MoveReference: An rvalue (``&&``) reference """ - moveref_to: typing.Union[Array, FunctionType, Pointer, Type] + moveref_to: typing.Union[Array, FunctionType, MemberPointer, Pointer, Type] def format(self) -> str: - return f"{self.moveref_to.format()}&&" + return self.format_decl("") - def format_decl(self, name: str): + def format_decl(self, name: str) -> str: """Format as a named declaration""" - return f"{self.moveref_to.format()}&& {name}" + return _format_prefixed_type(self.moveref_to, "&&", name) -#: A type or function type that is decorated with various things -#: -#: .. note:: There can only be one of FunctionType or Type in a DecoratedType -#: chain -DecoratedType = typing.Union[Array, Pointer, MoveReference, Reference, Type] +#: An object/declarator type decorated with arrays, pointers, or references. +#: FunctionType can occur as the target of a pointer-like wrapper, but is not +#: itself a DecoratedType. +DecoratedType = typing.Union[ + Array, MemberPointer, Pointer, MoveReference, Reference, Type +] + +#: A type-id, including a bare FunctionType in contexts that accept one. +TypeId = typing.Union[DecoratedType, FunctionType] @dataclass @@ -872,7 +908,7 @@ class Typedef: #: #: typedef type *pname; #: ~~~~~~ - type: typing.Union[DecoratedType, FunctionType] + type: TypeId #: The alias introduced for the specified type #: @@ -967,7 +1003,7 @@ class UsingAlias: """ alias: str - type: DecoratedType + type: TypeId template: typing.Optional[TemplateDecl] = None diff --git a/docs/types.rst b/docs/types.rst index 757c4e5..a15654a 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -4,6 +4,19 @@ Types parser types ------------ +.. versionchanged:: 2.0 + Member object pointers and member function pointers are now represented by + :py:class:`cxxheaderparser.types.MemberPointer`. Previously, member function + pointers were represented by a :py:class:`cxxheaderparser.types.Pointer` + containing a :py:class:`cxxheaderparser.types.FunctionType` whose + ``classname`` attribute identified the owning class. This is a breaking + change to the parsed type dataclasses. + + ``FunctionType`` is included in ``TypeId`` for contexts that accept a bare + function type, including aliases, typedefs, template arguments, and + ``parse_typename``. ``DecoratedType`` remains the set of object/declarator + types used by variables, fields, adjusted parameters, and function returns. + .. automodule:: cxxheaderparser.types :members: :undoc-members: diff --git a/tests/test_class.py b/tests/test_class.py index 9a2b679..5907f1e 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -11,6 +11,7 @@ ForwardDecl, FundamentalSpecifier, Method, + MemberPointer, MoveReference, NameSpecifier, PQName, @@ -3368,3 +3369,73 @@ def test_class_inline_static() -> None: ] ) ) + + +def test_class_method_object_member_pointer() -> None: + content = """ + class TunableTable { + public: + template + void Publish(Class* tunable, T Class::* member) {} + }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="TunableTable")], + classkey="class", + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="Publish")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=Type( + typename=PQName( + segments=[NameSpecifier(name="Class")] + ) + ) + ), + name="tunable", + ), + Parameter( + type=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[NameSpecifier(name="T")] + ) + ), + classname=PQName( + segments=[NameSpecifier(name="Class")] + ), + ), + name="member", + ), + ], + has_body=True, + template=TemplateDecl( + params=[ + TemplateTypeParam(typekey="typename", name="T"), + TemplateTypeParam(typekey="typename", name="Class"), + ] + ), + access="public", + ) + ], + ) + ] + ) + ) diff --git a/tests/test_fn.py b/tests/test_fn.py index fe27df1..eb5667d 100644 --- a/tests/test_fn.py +++ b/tests/test_fn.py @@ -1,5 +1,8 @@ # Note: testcases generated via `python -m cxxheaderparser.gentest` +import pytest + +from cxxheaderparser.errors import CxxParseError from cxxheaderparser.types import ( Array, AutoSpecifier, @@ -10,6 +13,7 @@ FunctionType, FundamentalSpecifier, Method, + MemberPointer, MoveReference, NameSpecifier, PQName, @@ -62,6 +66,68 @@ def test_fn_grouped_declarator() -> None: ) +def test_abstract_grouped_member_function_pointer_parameter() -> None: + content = """ + struct C {}; + struct Arg {}; + void g(int ((C::*)(Arg))); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="Arg")], classkey="struct" + ) + ) + ), + ], + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="g")]), + parameters=[ + Parameter( + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[NameSpecifier(name="Arg")] + ) + ) + ) + ], + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ) + ) + ], + ) + ], + ) + ) + + def test_fn_parameter_named_function_declarator() -> None: content = """ template void RemoveIf(bool predicate(const T& key)); @@ -924,6 +990,58 @@ def test_fn_trailing_return_simple() -> None: ) +@pytest.mark.parametrize("return_type", ["int[3]", "int()"]) +def test_fn_rejects_invalid_trailing_return_type(return_type: str) -> None: + with pytest.raises(CxxParseError): + parse_string(f"auto fn() -> {return_type};") + + +def test_trailing_return_wrapped_types() -> None: + content = """ + auto pointer_return() -> int (*)(); + auto reference_return() -> int (&)(); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[], + ) + ), + name=PQName(segments=[NameSpecifier(name="pointer_return")]), + parameters=[], + has_trailing_return=True, + ), + Function( + return_type=Reference( + ref_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[], + ) + ), + name=PQName(segments=[NameSpecifier(name="reference_return")]), + parameters=[], + has_trailing_return=True, + ), + ] + ) + ) + + def test_fn_trailing_return_std_function() -> None: content = """ auto fn() -> std::function; @@ -1509,6 +1627,342 @@ def test_msvc_inline() -> None: ) +def test_abstract_function_parameter_adjustment() -> None: + content = """ + void named(int callback(double)); + void abstract(int(double)); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="named")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="double" + ) + ] + ) + ) + ) + ], + ) + ), + name="callback", + ) + ], + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="abstract")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="double" + ) + ] + ) + ) + ) + ], + ) + ) + ) + ], + ), + ] + ) + ) + + +def test_nested_parameter_template_argument_classification() -> None: + content = """ + void named(void callback(H)); + void abstract(void(H)); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="named")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="H", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=FunctionType( + return_type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="int" + ) + ] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="T" + ) + ] + ) + ) + ) + ], + ) + ) + ] + ), + ) + ] + ) + ) + ) + ], + ) + ), + name="callback", + ) + ], + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="abstract")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="H", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=FunctionType( + return_type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="int" + ) + ] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="T" + ) + ] + ) + ) + ) + ], + ) + ) + ] + ), + ) + ] + ) + ) + ) + ], + ) + ) + ) + ], + ), + ] + ) + ) + + +def test_grouped_parameter_name() -> None: + content = """ + void f(int ((name))); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="name", + ) + ], + ) + ] + ) + ) + + +def test_grouped_parameter_pack() -> None: + content = """ + template void f(T (...args)); + template void g(T ((...args))); + template void h(T ...(args)); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="args", + param_pack=True, + ) + ], + template=TemplateDecl( + params=[ + TemplateTypeParam( + typekey="class", name="T", param_pack=True + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="g")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="args", + param_pack=True, + ) + ], + template=TemplateDecl( + params=[ + TemplateTypeParam( + typekey="class", name="T", param_pack=True + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="h")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="args", + param_pack=True, + ) + ], + template=TemplateDecl( + params=[ + TemplateTypeParam( + typekey="class", name="T", param_pack=True + ) + ] + ), + ), + ] + ) + ) + + def test_deleted_function() -> None: content = """ void trim() = delete; diff --git a/tests/test_parse_typename.py b/tests/test_parse_typename.py index 3b28748..b4018d4 100644 --- a/tests/test_parse_typename.py +++ b/tests/test_parse_typename.py @@ -3,11 +3,12 @@ import pytest from cxxheaderparser.errors import CxxParseError -from cxxheaderparser.simple import parse_typename +from cxxheaderparser.simple import parse_string, parse_typename from cxxheaderparser.types import ( Array, FundamentalSpecifier, FunctionType, + MemberPointer, NameSpecifier, Parameter, Pointer, @@ -89,6 +90,73 @@ def test_parse_typename_function_pointer() -> None: ) +def test_parse_typename_qualified_function() -> None: + dtype = parse_typename("int(double) volatile &&") + + assert dtype == FunctionType( + return_type=Type(typename=PQName(segments=[FundamentalSpecifier(name="int")])), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="double")]) + ) + ) + ], + volatile=True, + ref_qualifier="&&", + ) + + +def test_parse_typename_function_with_bare_member_pointer_parameter() -> None: + dtype = parse_typename("int(int C::*)") + + assert dtype == FunctionType( + return_type=Type(typename=PQName(segments=[FundamentalSpecifier(name="int")])), + parameters=[ + Parameter( + type=MemberPointer( + ptr_to=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ) + ) + ], + ) + + +def test_parse_typename_qualified_function_with_member_pointer_parameter() -> None: + dtype = parse_typename("int(void (C::*)(double)) const") + + assert dtype == FunctionType( + return_type=Type(typename=PQName(segments=[FundamentalSpecifier(name="int")])), + parameters=[ + Parameter( + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ) + ) + ], + const=True, + ) + + def test_parse_typename_array() -> None: content = """ int[3] @@ -102,6 +170,37 @@ def test_parse_typename_array() -> None: ) +def test_parse_typename_trailing_return_function_pointer() -> None: + content = """ + auto (*)() -> int + """ + + dtype = parse_typename(content.strip()) + + assert dtype == Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[], + has_trailing_return=True, + ) + ) + assert dtype.format() == "auto (*)() -> int" + + +def test_member_pointer_owner_template_argument_classification() -> None: + dtype = parse_typename("int Owner::*") + parameter_type = ( + parse_string("void f(int Owner::*);") + .namespace.functions[0] + .parameters[0] + .type + ) + + assert dtype == parameter_type + + def test_parse_typename_rejects_modifiers() -> None: content = """ static int diff --git a/tests/test_tokfmt.py b/tests/test_tokfmt.py index 5269af7..641305e 100644 --- a/tests/test_tokfmt.py +++ b/tests/test_tokfmt.py @@ -35,6 +35,7 @@ "operator[]", "operator*", "operator>=", + "int x[2][3];", ], ) def test_tokfmt(instr: str) -> None: diff --git a/tests/test_typedef.py b/tests/test_typedef.py index b21f073..a3ecbbe 100644 --- a/tests/test_typedef.py +++ b/tests/test_typedef.py @@ -11,6 +11,7 @@ Field, FunctionType, FundamentalSpecifier, + MemberPointer, NameSpecifier, PQName, Parameter, @@ -616,7 +617,7 @@ def test_typedef_member_fnptr() -> None: namespace=NamespaceScope( typedefs=[ Typedef( - type=Pointer( + type=MemberPointer( ptr_to=FunctionType( return_type=Type( typename=PQName( @@ -643,8 +644,8 @@ def test_typedef_member_fnptr() -> None: name="y", ), ], - classname=PQName(segments=[NameSpecifier(name="Fred")]), - ) + ), + classname=PQName(segments=[NameSpecifier(name="Fred")]), ), name="FredMemFn", ) @@ -948,6 +949,71 @@ def test_volatile_typedef() -> None: ) +def test_function_type_noexcept_typedef() -> None: + content = """ + typedef int T(double) noexcept; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + typedefs=[ + Typedef( + type=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + noexcept=Value(tokens=[]), + ), + name="T", + ) + ] + ) + ) + + +def test_qualified_function_typedef() -> None: + content = """ + typedef int TypedefConst(double) const; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + typedefs=[ + Typedef( + type=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + const=True, + ), + name="TypedefConst", + ) + ] + ) + ) + + def test_function_typedef() -> None: content = """ typedef void fn(int); diff --git a/tests/test_typefmt.py b/tests/test_typefmt.py index 0fdd6b4..219c9b6 100644 --- a/tests/test_typefmt.py +++ b/tests/test_typefmt.py @@ -9,6 +9,7 @@ FunctionType, FundamentalSpecifier, Method, + MemberPointer, MoveReference, NameSpecifier, PQName, @@ -19,6 +20,7 @@ TemplateSpecialization, TemplateDecl, Type, + TypeId, Value, ) @@ -138,6 +140,89 @@ "int (int)", "int name(int)", ), + ( + FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + const=True, + ), + "int (double) const", + "int name(double) const", + ), + ( + FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + volatile=True, + ref_qualifier="&&", + ), + "int (double) volatile &&", + "int name(double) volatile &&", + ), + ( + MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + const=True, + ref_qualifier="&", + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + "int (C::*)(double) const &", + "int (C::* name)(double) const &", + ), + ( + FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + has_trailing_return=True, + const=True, + ref_qualifier="&", + ), + "auto (double) const & -> int", + "auto name(double) const & -> int", + ), ( FunctionType( return_type=Type( @@ -205,7 +290,7 @@ "int (* name)(int)", ), ( - Pointer( + MemberPointer( ptr_to=FunctionType( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) @@ -228,8 +313,8 @@ name="y", ), ], - classname=PQName(segments=[NameSpecifier(name="Fred")]), - ) + ), + classname=PQName(segments=[NameSpecifier(name="Fred")]), ), "int (Fred::*)(char x, float y)", "int (Fred::* name)(char x, float y)", @@ -333,11 +418,219 @@ ), ], ) -def test_typefmt( - pytype: typing.Union[DecoratedType, FunctionType], typestr: str, declstr: str -): +def test_typefmt(pytype: TypeId, typestr: str, declstr: str): # basic formatting assert pytype.format() == typestr # as a type declaration assert pytype.format_decl("name") == declstr + + +def test_function_type_fixed_parameter_and_varargs_format() -> None: + dtype = FunctionType( + return_type=Type(typename=PQName(segments=[FundamentalSpecifier(name="int")])), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[FundamentalSpecifier(name="char")])) + ) + ], + vararg=True, + ) + + assert dtype.format() == "int (char, ...)" + assert dtype.format_decl("fn") == "int fn(char, ...)" + + +def test_function_type_noexcept_format() -> None: + int_type = Type(typename=PQName(segments=[FundamentalSpecifier(name="int")])) + parameters = [ + Parameter( + type=Type(typename=PQName(segments=[FundamentalSpecifier(name="double")])) + ) + ] + + plain = FunctionType( + return_type=int_type, + parameters=parameters, + noexcept=Value(tokens=[]), + ) + assert plain.format() == "int (double) noexcept" + assert plain.format_decl("name") == "int name(double) noexcept" + + conditional = FunctionType( + return_type=int_type, + parameters=parameters, + noexcept=Value(tokens=[Token(value="false")]), + const=True, + ref_qualifier="&", + ) + assert conditional.format() == "int (double) const & noexcept(false)" + assert conditional.format_decl("name") == "int name(double) const & noexcept(false)" + + trailing = FunctionType( + return_type=int_type, + parameters=parameters, + has_trailing_return=True, + noexcept=Value(tokens=[]), + ) + assert trailing.format() == "auto (double) noexcept -> int" + assert trailing.format_decl("name") == "auto name(double) noexcept -> int" + + +def test_decorated_member_function_pointer_format() -> None: + member_pointer = MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[], + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ) + + cases: typing.List[typing.Tuple[DecoratedType, str, str]] = [ + (Pointer(member_pointer), "int (C::**)()", "int (C::** name)()"), + (Reference(member_pointer), "int (C::*&)()", "int (C::*& name)()"), + ( + Reference(member_pointer, restrict=True), + "int (C::*& __restrict__)()", + "int (C::*& __restrict__ name)()", + ), + (MoveReference(member_pointer), "int (C::*&&)()", "int (C::*&& name)()"), + ( + Array(member_pointer, Value(tokens=[Token(value="3")])), + "int (C::*[3])()", + "int (C::* name[3])()", + ), + ] + + for dtype, typestr, declstr in cases: + assert dtype.format() == typestr + assert dtype.format_decl("name") == declstr + + +def test_recursive_member_pointer_declarator_format() -> None: + dtype = MemberPointer( + ptr_to=Pointer( + ptr_to=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + ) + ) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ) + + assert dtype.format() == "int (** C::*)(double)" + assert dtype.format_decl("p") == "int (** C::* p)(double)" + + +def _recursive_declarator_cases() -> ( + typing.List[typing.Tuple[DecoratedType, str, str, str]] +): + int_type = Type(typename=PQName(segments=[FundamentalSpecifier(name="int")])) + function_type = FunctionType( + return_type=int_type, + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="double")]) + ) + ) + ], + ) + size = Value(tokens=[Token(value="3")]) + classname = PQName(segments=[NameSpecifier(name="C")]) + + return [ + ( + Pointer(ptr_to=Pointer(ptr_to=Array(array_of=int_type, size=size))), + "array_pointer", + "int (**)[3]", + "int (** array_pointer)[3]", + ), + ( + Array( + array_of=MemberPointer( + ptr_to=Pointer(ptr_to=Pointer(ptr_to=function_type)), + classname=classname, + ), + size=size, + ), + "array_member", + "int (** C::*[3])(double)", + "int (** C::* array_member[3])(double)", + ), + ( + Pointer(ptr_to=Pointer(ptr_to=function_type), const=True), + "qualified_pointer", + "int (** const)(double)", + "int (** const qualified_pointer)(double)", + ), + ( + Reference(ref_to=Pointer(ptr_to=function_type)), + "lvalue_reference", + "int (*&)(double)", + "int (*& lvalue_reference)(double)", + ), + ( + MoveReference(moveref_to=Pointer(ptr_to=function_type)), + "rvalue_reference", + "int (*&&)(double)", + "int (*&& rvalue_reference)(double)", + ), + ( + Pointer( + ptr_to=MemberPointer( + ptr_to=FunctionType( + return_type=int_type, + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + const=True, + ref_qualifier="&", + ), + classname=classname, + ) + ), + "qualified_member_pointer", + "int (C::**)(double) const &", + "int (C::** qualified_member_pointer)(double) const &", + ), + ] + + +@pytest.mark.parametrize( + "dtype,name,typestr,declstr", + _recursive_declarator_cases(), + ids=[ + "nested-pointer-array", + "array-member-pointer-function", + "qualified-pointer-function", + "reference-pointer-function", + "move-reference-pointer-function", + "qualified-member-function-pointer", + ], +) +def test_recursive_declarator_chain_format( + dtype: DecoratedType, name: str, typestr: str, declstr: str +) -> None: + assert dtype.format() == typestr + assert dtype.format_decl(name) == declstr diff --git a/tests/test_using.py b/tests/test_using.py index de25763..3f2c684 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -3,10 +3,13 @@ from cxxheaderparser.types import ( BaseClass, ClassDecl, + DecltypeSpecifier, Function, FunctionType, FundamentalSpecifier, Method, + MemberPointer, + MoveReference, NameSpecifier, PQName, Parameter, @@ -20,6 +23,8 @@ Type, UsingAlias, UsingDecl, + Value, + Variable, ) from cxxheaderparser.simple import ( ClassScope, @@ -748,6 +753,690 @@ def test_using_enum_global() -> None: ) +def test_grouped_member_function_pointer_conditional_noexcept_alias() -> None: + content = """ + struct C {}; + struct Arg {}; + using U = int ((C::*)(Arg) noexcept(false)); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="Arg")], classkey="struct" + ) + ) + ), + ], + using_alias=[ + UsingAlias( + alias="U", + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[NameSpecifier(name="Arg")] + ) + ) + ) + ], + noexcept=Value(tokens=[Token(value="false")]), + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ) + ], + ) + ) + + +def test_function_type_noexcept_aliases() -> None: + content = """ + struct C {}; + using F = int(double) noexcept; + using FE = int(double) noexcept(false); + using P = int (*)(double) noexcept; + using M = int (C::*)(double) const & noexcept; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ) + ], + using_alias=[ + UsingAlias( + alias="F", + type=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + noexcept=Value(tokens=[]), + ), + ), + UsingAlias( + alias="FE", + type=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + noexcept=Value(tokens=[Token(value="false")]), + ), + ), + UsingAlias( + alias="P", + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + noexcept=Value(tokens=[]), + ) + ), + ), + UsingAlias( + alias="M", + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + noexcept=Value(tokens=[]), + const=True, + ref_qualifier="&", + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ), + ], + ) + ) + + +def test_qualified_function_type_aliases() -> None: + content = """ + struct C {}; + using BareConst = int(double) const; + using BareVolatileRvalue = int(double) volatile &&; + using Member = int (C::*)(double) const &; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ) + ], + using_alias=[ + UsingAlias( + alias="BareConst", + type=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + const=True, + ), + ), + UsingAlias( + alias="BareVolatileRvalue", + type=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + volatile=True, + ref_qualifier="&&", + ), + ), + UsingAlias( + alias="Member", + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + const=True, + ref_qualifier="&", + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ), + ], + ) + ) + + +def test_template_function_type_with_member_pointer_parameter() -> None: + content = """ + template struct Holder {}; + struct C {}; + using Nested = Holder; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="Holder")], classkey="struct" + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")] + ), + ) + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ), + ], + using_alias=[ + UsingAlias( + alias="Nested", + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="Holder", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=FunctionType( + return_type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="int" + ) + ] + ) + ), + parameters=[ + Parameter( + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="void" + ) + ] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="double" + ) + ] + ) + ) + ) + ], + ), + classname=PQName( + segments=[ + NameSpecifier( + name="C" + ) + ] + ), + ) + ) + ], + ) + ) + ] + ), + ) + ] + ) + ), + ) + ], + ) + ) + + +def test_bare_member_pointer_parameter_aliases() -> None: + content = """ + struct C {}; + template struct Holder {}; + using BareMemberParameter = int(int C::*); + using NestedBareMemberParameter = Holder; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="Holder")], classkey="struct" + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")] + ), + ) + ), + ], + using_alias=[ + UsingAlias( + alias="BareMemberParameter", + type=FunctionType( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + parameters=[ + Parameter( + type=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + classname=PQName( + segments=[NameSpecifier(name="C")] + ), + ) + ) + ], + ), + ), + UsingAlias( + alias="NestedBareMemberParameter", + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="Holder", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=FunctionType( + return_type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="int" + ) + ] + ) + ), + parameters=[ + Parameter( + type=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="int" + ) + ] + ) + ), + classname=PQName( + segments=[ + NameSpecifier( + name="C" + ) + ] + ), + ) + ) + ], + ) + ) + ] + ), + ) + ] + ) + ), + ), + ], + ) + ) + + +def test_relational_expression_member_pointer_alias() -> None: + content = """ + struct C {}; + using RelationalScope = int (decltype((1 < 2, C{}))::*)(double); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ) + ], + using_alias=[ + UsingAlias( + alias="RelationalScope", + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + ), + classname=PQName( + segments=[ + DecltypeSpecifier( + tokens=[ + Token(value="("), + Token(value="1"), + Token(value="<"), + Token(value="2"), + Token(value=","), + Token(value="C"), + Token(value="{"), + Token(value="}"), + Token(value=")"), + ] + ) + ] + ), + ), + ) + ], + ) + ) + + +def test_rvalue_function_reference_alias() -> None: + content = """ + using RvalueFunctionReference = int (&&)(double); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + using_alias=[ + UsingAlias( + alias="RvalueFunctionReference", + type=MoveReference( + moveref_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + ) + ), + ) + ] + ) + ) + + +def test_member_pointer_to_function_pointer() -> None: + content = """ + using X = int (* C::*)(); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + using_alias=[ + UsingAlias( + alias="X", + type=MemberPointer( + ptr_to=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[], + ) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ) + ] + ) + ) + + +def test_bounded_type_probe_does_not_leak_attributes() -> None: + content = """ + using X = Foo; + int x; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + using_alias=[ + UsingAlias( + alias="X", + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="Foo", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="int" + ) + ] + ) + ) + ) + ] + ), + ) + ] + ) + ), + ) + ], + ) + ) + + +def test_array_and_trailing_return_type_aliases() -> None: + content = """ + struct C {}; + using A = int[3]; + using P = auto (*)() -> int; + using M = auto (C::*)() -> int; + """ + + data = parse_string(content, cleandoc=True) + aliases = {alias.alias: alias.type for alias in data.namespace.using_alias} + + assert aliases["A"].format() == "int[3]" + + pointer = aliases["P"] + assert isinstance(pointer, Pointer) + assert isinstance(pointer.ptr_to, FunctionType) + assert pointer.ptr_to.has_trailing_return + assert pointer.format() == "auto (*)() -> int" + + member = aliases["M"] + assert isinstance(member, MemberPointer) + assert isinstance(member.ptr_to, FunctionType) + assert member.ptr_to.has_trailing_return + assert member.format() == "auto (C::*)() -> int" + + def test_using_enum_in_struct() -> None: content = """ struct S { diff --git a/tests/test_var.py b/tests/test_var.py index ad6c10c..19ddc81 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -3,12 +3,16 @@ from cxxheaderparser.errors import CxxParseError from cxxheaderparser.types import ( Array, + Attribute, + AttributeStyle, ClassDecl, EnumDecl, Enumerator, Field, + Function, FunctionType, FundamentalSpecifier, + MemberPointer, NameSpecifier, PQName, Parameter, @@ -364,104 +368,753 @@ def test_var_fnptr_grouped_declarator() -> None: ) +def test_redundant_grouped_function_pointer_declarators() -> None: + content = """ + class C {}; + void ((*p)(int C::*)); + void ((**r)(int C::*)); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="class" + ) + ) + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="int") + ] + ) + ), + classname=PQName( + segments=[NameSpecifier(name="C")] + ), + ) + ) + ], + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="r")]), + type=Pointer( + ptr_to=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="int") + ] + ) + ), + classname=PQName( + segments=[NameSpecifier(name="C")] + ), + ) + ) + ], + ) + ) + ), + ), + ], + ) + ) + + p_type = data.namespace.variables[0].type + r_type = data.namespace.variables[1].type + assert p_type.format_decl("p") == "void (* p)(int C::*)" + assert r_type.format_decl("r") == "void (** r)(int C::*)" + + +def test_grouped_msvc_function_pointers() -> None: + content = """ + void (__cdecl *p)(int); + void ((__cdecl *q))(double); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ) + ) + ], + msvc_convention="__cdecl", + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="q")]), + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + msvc_convention="__cdecl", + ) + ), + ), + ] + ) + ) + + +def test_grouped_function_pointer_parameter_attribute() -> None: + content = """ + void ((*p)([[maybe_unused]] int)); + int x; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ) + ) + ], + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + attributes=[ + Attribute(style=AttributeStyle.CXX, name="maybe_unused") + ], + ), + ] + ) + ) + + def test_var_ptr_to_array_grouped_declarator() -> None: content = """ - int ((*arrayPtr))[3]; + int ((*arrayPtr))[3]; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="arrayPtr")]), + type=Pointer( + ptr_to=Array( + array_of=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + size=Value(tokens=[Token(value="3")]), + ) + ), + ) + ] + ) + ) + + +def test_var_ref_to_array_grouped_declarator() -> None: + content = """ + int ((&arrayRef))[3]; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="arrayRef")]), + type=Reference( + ref_to=Array( + array_of=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + size=Value(tokens=[Token(value="3")]), + ) + ), + ) + ] + ) + ) + + +def test_redundant_grouped_member_function_pointer() -> None: + content = """ + struct C {}; + int ((C::*p)(double)); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ) + ], + ) + ) + + +def test_noncrossing_member_pointer_postfix() -> None: + content = """ + struct C {}; + int (C::*p[3]); + int (C::*f(double)); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ) + ], + functions=[ + Function( + return_type=MemberPointer( + ptr_to=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="double")] + ) + ) + ) + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=Array( + array_of=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + size=Value(tokens=[Token(value="3")]), + ), + ) + ], + ) + ) + + +def test_grouped_member_pointer_mixed_precedence() -> None: + content = """ + struct C {}; + int (* (C::*p)(double))[2]; + int ((* C::*q)()); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=MemberPointer( + ptr_to=FunctionType( + return_type=Pointer( + ptr_to=Array( + array_of=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + size=Value(tokens=[Token(value="2")]), + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="q")]), + type=MemberPointer( + ptr_to=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[], + ) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ), + ], + ) + ) + p_type = data.namespace.variables[0].type + q_type = data.namespace.variables[1].type + assert p_type.format_decl("p") == "int (* (C::* p)(double))[2]" + assert q_type.format_decl("q") == "int (* C::* q)()" + + +def test_grouped_member_pointer_suffix_context() -> None: + content = """ + struct C {}; + int ((C::*(p))(double)); + int ((C::*q)[sizeof(double)]); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="double") + ] + ) + ) + ) + ], + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="q")]), + type=MemberPointer( + ptr_to=Array( + array_of=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + size=Value( + tokens=[ + Token(value="sizeof"), + Token(value="("), + Token(value="double"), + Token(value=")"), + ] + ), + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ), + ], + ) + ) + + +def test_var_member_fnptr_with_initializer() -> None: + content = """ + int (Calculator::*funcPtr)(int) = &Calculator::multiply; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="funcPtr")]), + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ) + ) + ], + ), + classname=PQName(segments=[NameSpecifier(name="Calculator")]), + ), + value=Value( + tokens=[ + Token(value="&"), + Token(value="Calculator"), + Token(value="::"), + Token(value="multiply"), + ] + ), + ) + ] + ) + ) + + +def test_nested_member_object_pointers() -> None: + content = """ + struct A { + int value; + int A::* member; + }; + + int A::* A::* p1 = &A::member; + + int A::* p2 = &A::value; + int A::*& ref = p2; """ + data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="A")], classkey="struct" + ) + ), + fields=[ + Field( + access="public", + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="value", + ), + Field( + access="public", + type=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + classname=PQName(segments=[NameSpecifier(name="A")]), + ), + name="member", + ), + ], + ) + ], variables=[ Variable( - name=PQName(segments=[NameSpecifier(name="arrayPtr")]), - type=Pointer( - ptr_to=Array( - array_of=Type( + name=PQName(segments=[NameSpecifier(name="p1")]), + type=MemberPointer( + ptr_to=MemberPointer( + ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), - size=Value(tokens=[Token(value="3")]), + classname=PQName(segments=[NameSpecifier(name="A")]), + ), + classname=PQName(segments=[NameSpecifier(name="A")]), + ), + value=Value( + tokens=[ + Token(value="&"), + Token(value="A"), + Token(value="::"), + Token(value="member"), + ] + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="p2")]), + type=MemberPointer( + ptr_to=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + classname=PQName(segments=[NameSpecifier(name="A")]), + ), + value=Value( + tokens=[ + Token(value="&"), + Token(value="A"), + Token(value="::"), + Token(value="value"), + ] + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="ref")]), + type=Reference( + ref_to=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + classname=PQName(segments=[NameSpecifier(name="A")]), ) ), - ) - ] + value=Value(tokens=[Token(value="p2")]), + ), + ], ) ) -def test_var_ref_to_array_grouped_declarator() -> None: +def test_nested_array_dimension_order() -> None: content = """ - int ((&arrayRef))[3]; + int x[2][3]; """ + data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( - name=PQName(segments=[NameSpecifier(name="arrayRef")]), - type=Reference( - ref_to=Array( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Array( + array_of=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=Value(tokens=[Token(value="3")]), - ) + ), + size=Value(tokens=[Token(value="2")]), ), ) ] ) ) + dtype = data.namespace.variables[0].type + assert dtype.format() == "int[2][3]" + assert dtype.format_decl("x") == "int x[2][3]" -def test_var_member_fnptr_with_initializer() -> None: +def test_nested_member_pointer_array() -> None: content = """ - int (Calculator::*funcPtr)(int) = &Calculator::multiply; + struct C { + int value; + }; + + int C::*a[2][3]; """ + data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( - variables=[ - Variable( - name=PQName(segments=[NameSpecifier(name="funcPtr")]), - type=Pointer( - ptr_to=FunctionType( - return_type=Type( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ), + fields=[ + Field( + access="public", + type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), - parameters=[ - Parameter( - type=Type( - typename=PQName( - segments=[FundamentalSpecifier(name="int")] - ) + name="value", + ) + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="a")]), + type=Array( + array_of=Array( + array_of=MemberPointer( + ptr_to=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] ) - ) - ], - classname=PQName( - segments=[NameSpecifier(name="Calculator")] + ), + classname=PQName(segments=[NameSpecifier(name="C")]), ), - ) - ), - value=Value( - tokens=[ - Token(value="&"), - Token(value="Calculator"), - Token(value="::"), - Token(value="multiply"), - ] + size=Value(tokens=[Token(value="3")]), + ), + size=Value(tokens=[Token(value="2")]), ), ) - ] + ], ) ) + dtype = data.namespace.variables[0].type + assert dtype.format() == "int C::*[2][3]" + assert dtype.format_decl("a") == "int C::* a[2][3]" def test_var_fnptr_moreparens() -> None: @@ -1021,3 +1674,133 @@ def test_balanced_bad_mismatch() -> None: err = ":1: parse error evaluating ']': unexpected ']', expected ')'" with pytest.raises(CxxParseError, match=re.escape(err)): parse_string(content, cleandoc=True) + + +def test_recursive_declarator_formatting() -> None: + content = """ + struct C {}; + int (**C::*p)(double); + int (**C::*a[3])(double); + int (*(*C::*q)[3])(double); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="C")], classkey="struct" + ) + ) + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=MemberPointer( + ptr_to=Pointer( + ptr_to=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="double" + ) + ] + ) + ) + ) + ], + ) + ) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="a")]), + type=Array( + array_of=MemberPointer( + ptr_to=Pointer( + ptr_to=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="int") + ] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="double" + ) + ] + ) + ) + ) + ], + ) + ) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + size=Value(tokens=[Token(value="3")]), + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="q")]), + type=MemberPointer( + ptr_to=Pointer( + ptr_to=Array( + array_of=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier(name="int") + ] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + FundamentalSpecifier( + name="double" + ) + ] + ) + ) + ) + ], + ) + ), + size=Value(tokens=[Token(value="3")]), + ) + ), + classname=PQName(segments=[NameSpecifier(name="C")]), + ), + ), + ], + ) + ) + + variables = data.namespace.variables + assert variables[2].type.format_decl("q") == "int (* (* C::* q)[3])(double)"