diff --git a/cxxheaderparser/lexer.py b/cxxheaderparser/lexer.py index 0fb579e..77aac52 100644 --- a/cxxheaderparser/lexer.py +++ b/cxxheaderparser/lexer.py @@ -71,6 +71,7 @@ class PlyLexer: keywords = { "__attribute__", + "__extension__", "alignas", "alignof", "asm", @@ -87,6 +88,7 @@ class PlyLexer: "concept", "const", "constexpr", + "consteval", "const_cast", "continue", "decltype", @@ -95,6 +97,7 @@ class PlyLexer: "delete", "do", "double", + "_Complex", "dynamic_cast", "else", "enum", @@ -110,12 +113,14 @@ class PlyLexer: "goto", "if", "__inline", + "__inline__", "inline", "int", "__int8", "__int16", "__int32", "__int64", + "__int128", "long", "mutable", "namespace", @@ -135,15 +140,18 @@ class PlyLexer: "return", "short", "signed", + "__signed__", "sizeof", "static", "static_assert", + "_Static_assert", "static_cast", "struct", "switch", "template", "this", "thread_local", + "__thread", "throw", "true", "try", @@ -156,6 +164,7 @@ class PlyLexer: "virtual", "void", "volatile", + "__volatile__", "wchar_t", "while", } diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 93a9446..00fd9bf 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -359,17 +359,20 @@ def parse(self) -> None: _translation_unit_tokens: typing.Dict[ str, typing.Callable[[LexToken, typing.Optional[str]], typing.Any] ] = { + "__extension__": lambda _1, _2: None, "__attribute__": self._consume_gcc_attribute, "__declspec": self._consume_declspec, "alignas": self._consume_attribute_specifier_seq, "extern": self._parse_extern, "friend": self._parse_friend_decl, "inline": self._parse_inline, + "__inline__": self._parse_inline, "namespace": self._parse_namespace, "private": self._process_access_specifier, "protected": self._process_access_specifier, "public": self._process_access_specifier, "static_assert": self._consume_static_assert, + "_Static_assert": self._consume_static_assert, "template": self._parse_template, "typedef": self._parse_typedef, "using": self._parse_using, @@ -381,7 +384,13 @@ def parse(self) -> None: ";": lambda _1, _2: None, } - _keep_doxygen = {"__declspec", "alignas", "__attribute__", "DBL_LBRACKET"} + _keep_doxygen = { + "__declspec", + "alignas", + "__attribute__", + "DBL_LBRACKET", + "__extension__", + } tok = None @@ -715,15 +724,16 @@ def _parse_template(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: self._parse_declarations(tok, doxygen, templates) return + if tok.type == "requires": + template.raw_requires_pre = self._parse_requires(tok) + tok = self.lex.token() + if tok.type == "using": self._parse_using(tok, doxygen, template) elif tok.type == "friend": self._parse_friend_decl(tok, doxygen, template) elif tok.type == "concept": self._parse_concept(tok, doxygen, template) - elif tok.type == "requires": - template.raw_requires_pre = self._parse_requires(tok) - self._parse_declarations(self.lex.token(), doxygen, template) else: self._parse_declarations(tok, doxygen, template) @@ -891,9 +901,14 @@ def _parse_requires( tok = self.lex.token() elif tok.type == "requires": rawtoks.append(tok) - for tt in ("(", "{"): - tok = self._next_token_must_be(tt) + tok = self._next_token_must_be("(", "{") + if tok.type == "(": rawtoks.extend(self._consume_balanced_tokens(tok)) + tok = self._next_token_must_be("{") + rawtoks.extend(self._consume_balanced_tokens(tok)) + tok = self.lex.token() + elif tok.type in ("true", "false"): + rawtoks.append(tok) tok = self.lex.token() else: tok = self._parse_requires_segment(tok, rawtoks) @@ -1177,7 +1192,12 @@ def _parse_using_declaration( typename, _ = self._parse_pqname( tok, fn_ok=True, compound_ok=True, fund_ok=True ) - decl = UsingDecl(typename, self._current_access, doxygen) + decl = UsingDecl( + typename, + self._current_access, + doxygen, + param_pack=self.lex.token_if("ELLIPSIS") is not None, + ) self.visitor.on_using_declaration(self.state, decl) @@ -1191,9 +1211,18 @@ def _parse_using_typealias( alias_declaration: "using" IDENTIFIER "=" type_id ";" """ + alias_attributes = self._take_pending_attributes() dtype = self._parse_type_id(None, "parsing typealias") + alias_attributes.extend(self._take_pending_attributes()) - alias = UsingAlias(id_tok.value, dtype, template, self._current_access, doxygen) + alias = UsingAlias( + id_tok.value, + dtype, + template, + self._current_access, + doxygen, + alias_attributes, + ) self.visitor.on_using_alias(self.state, alias) @@ -1209,6 +1238,9 @@ def _parse_using( "NAME", "DBL_COLON", "namespace", "typename", "enum" ) + if tok.type == "NAME": + self._record_attributes(self._consume_decl_attributes()) + if tok.type == "namespace": if template: raise CxxParseError( @@ -1334,9 +1366,7 @@ def _parse_enumerator_list(self) -> typing.List[Enumerator]: if doxygen is None: doxygen = self.lex.get_doxygen_after() - tok = self.lex.token_if("DBL_LBRACKET") - if tok: - attributes = self._consume_attribute_specifier_seq(tok, record=False) + attributes = self._consume_decl_attributes() if doxygen is None: doxygen = self.lex.get_doxygen_after() @@ -1552,6 +1582,12 @@ def _discard_ctor_initializer(self) -> None: # each initializer is either foo() or foo{}, so look for that while True: + if tok.type == "<": + # Parentheses in template arguments belong to the + # initializer-id, not to its argument list. + self._consume_balanced_tokens(tok) + tok = get_token() + continue if tok.type not in ("{", "("): tok = get_token() continue @@ -1650,6 +1686,8 @@ def _parse_field( bits = self._parse_bitfield() + attributes.extend(self._consume_decl_attributes()) + # default values tok = self.lex.token_if("=") if tok: @@ -1691,6 +1729,7 @@ def _parse_field( attributes=attributes, constexpr=mods.constexpr is not None, mutable=mods.mutable is not None, + thread_local=mods.thread_local is not None, static=mods.static is not None, inline=mods.inline is not None, ) @@ -1706,6 +1745,7 @@ def _parse_field( attributes=attributes, constexpr=mods.constexpr is not None, extern=self._make_extern_value(mods), + thread_local=mods.thread_local is not None, static=mods.static is not None, inline=mods.inline is not None, ) @@ -1728,20 +1768,24 @@ def _parse_pqname_decltype_specifier(self) -> DecltypeSpecifier: _compound_fundamentals = { "unsigned", "signed", + "__signed__", "short", "int", "long", "float", "double", + "_Complex", "char", "__int8", "__int16", "__int32", "__int64", + "__int128", } _fundamentals = _compound_fundamentals | { "bool", + "char8_t", "char16_t", "char32_t", "nullptr_t", @@ -1921,7 +1965,7 @@ def _parse_pqname( segments.append(self._parse_pqname_decltype_specifier()) # check for fundamentals, consume them - elif tok_value in _fundamentals: + elif tok_value in _fundamentals and (fund_ok or tok.type != "NAME"): if not fund_ok: raise self._parse_error(tok) @@ -2116,7 +2160,11 @@ def _parse_parameters( ) ) - tok = self._next_token_must_be(",", ")") + tok = self._next_token_must_be(",", ")", "ELLIPSIS") + if tok.type == "ELLIPSIS": + vararg = True + self._next_token_must_be(")") + break if tok.value == ")": break @@ -2201,19 +2249,21 @@ def _parse_fn_end(self, fn: Function) -> None: if otok: toks = self._consume_balanced_tokens(otok)[1:-1] fn.noexcept = self._create_value(toks) - else: - rtok = self.lex.token_if("requires") - if rtok: - # requires on a function must always be accompanied by a template - if fn.template is None: - raise self._parse_error(rtok) - fn.raw_requires = self._parse_requires(rtok) + + fn.attributes.extend(self._consume_decl_attributes()) if self.lex.token_if("ARROW"): return_type = self._parse_trailing_return_type(fn.return_type) fn.has_trailing_return = True fn.return_type = return_type + rtok = self.lex.token_if("requires") + if rtok: + # Free-function constraints require a template. + if fn.template is None: + raise self._parse_error(rtok) + fn.raw_requires = self._parse_requires(rtok) + extra_attrs = self._consume_decl_attributes() if extra_attrs: fn.attributes.extend(extra_attrs) @@ -2264,7 +2314,9 @@ def _parse_method_end(self, method: Method) -> None: raise self._parse_error(tok, "0/delete/default") break - elif tok_value in ("const", "volatile", "override", "final"): + elif tok.type in ("volatile", "__volatile__"): + method.volatile = True + elif tok_value in ("const", "override", "final"): setattr(method, tok_value, True) elif tok_value in ("&", "&&"): method.ref_qualifier = tok_value @@ -2287,6 +2339,8 @@ def _parse_method_end(self, method: Method) -> None: method.noexcept = self._create_value(toks) elif tok_value == "requires": method.raw_requires = self._parse_requires(tok) + elif tok.type in self._attribute_start_tokens: + method.attributes.extend(self._consume_attribute(tok, record=False)) else: self.lex.return_token(tok) break @@ -2373,6 +2427,7 @@ def _parse_function( template=template, operator=op, access=self._current_access, + consteval=mods.consteval is not None, constexpr=mods.constexpr is not None, extern=extern, static=mods.static is not None, @@ -2432,6 +2487,7 @@ def _parse_function( attributes=attributes, template=template, operator=op, + consteval=mods.consteval is not None, constexpr=mods.constexpr is not None, extern=extern, static=mods.static is not None, @@ -2453,7 +2509,7 @@ def _parse_function( if fn.constexpr: raise CxxParseError("typedef function may not be constexpr") - if fn.extern: + if mods.extern is not None: raise CxxParseError("typedef function may not be extern") if fn.static: raise CxxParseError("typedef function may not be static") @@ -2498,7 +2554,7 @@ def _parse_function_type_qualifiers(self) -> _FunctionTypeQualifiers: const = False volatile = False while True: - tok = self.lex.token_if("const", "volatile") + tok = self.lex.token_if("const", "volatile", "__volatile__") if not tok: break if tok.type == "const": @@ -2660,7 +2716,14 @@ def _parse_cv_ptr_or_fn( continue tok = self.lex.token_if( - "*", "const", "volatile", "__restrict__", "__restrict", "restrict", "(" + "*", + "const", + "volatile", + "__volatile__", + "__restrict__", + "__restrict", + "restrict", + "(", ) if not tok: break @@ -2675,7 +2738,7 @@ def _parse_cv_ptr_or_fn( if not isinstance(dtype, (Pointer, MemberPointer, Type)): raise self._parse_error(tok) dtype.const = True - elif tok.type == "volatile": + elif tok.type in ("volatile", "__volatile__"): if not isinstance(dtype, (Pointer, MemberPointer, Type)): raise self._parse_error(tok) dtype.volatile = True @@ -2775,7 +2838,16 @@ def _parse_cv_ptr_or_fn( return dtype # Applies to variables and return values - _type_kwd_both = {"const", "constexpr", "extern", "inline", "static"} + _type_kwd_both = { + "const", + "constexpr", + "consteval", + "extern", + "inline", + "static", + "thread_local", + "__thread", + } # Only found on methods _type_kwd_meth = {"explicit", "virtual"} @@ -2789,8 +2861,10 @@ def _parse_cv_ptr_or_fn( | _type_kwd_meth | { "volatile", + "__volatile__", "mutable", "__inline", + "__inline__", "__forceinline", "this", "ELLIPSIS", @@ -2854,16 +2928,20 @@ def _parse_type( break elif tok_type == "const": const = True - elif tok_type == "friend" and pqname is None: + elif tok_type == "friend": mods.friend = tok elif tok_type == "constexpr": mods.constexpr = tok + elif tok_type == "consteval": + mods.consteval = tok + elif tok_type in ("thread_local", "__thread"): + mods.thread_local = tok elif tok_type == "extern": linkage_tok = self.lex.token_if("STRING_LITERAL") if linkage_tok: mods.extern_linkage = linkage_tok.value mods.extern = tok - elif tok_type in ("__inline", "__forceinline", "inline"): + elif tok_type in ("__inline", "__inline__", "__forceinline", "inline"): mods.inline = tok elif tok_type == "static": mods.static = tok @@ -2879,7 +2957,7 @@ def _parse_type( mods.virtual = tok elif tok_type == "mutable": mods.mutable = tok - elif tok_type == "volatile": + elif tok_type in ("volatile", "__volatile__"): volatile = True elif tok_type in _attribute_start: self._consume_attribute(tok) diff --git a/cxxheaderparser/parserstate.py b/cxxheaderparser/parserstate.py index 90c7254..bf96c3e 100644 --- a/cxxheaderparser/parserstate.py +++ b/cxxheaderparser/parserstate.py @@ -13,12 +13,14 @@ class ParsedTypeModifiers: #: Modifiers allowed on variables and functions. constexpr: typing.Optional[LexToken] = None + consteval: typing.Optional[LexToken] = None extern: typing.Optional[LexToken] = None inline: typing.Optional[LexToken] = None static: typing.Optional[LexToken] = None #: Modifiers only allowed on variables/fields. mutable: typing.Optional[LexToken] = None + thread_local: typing.Optional[LexToken] = None #: Modifiers only allowed on methods. explicit: typing.Optional[LexToken] = None @@ -38,7 +40,7 @@ def validate( ) -> None: # Almost there! Do any checks the caller asked for if not var_ok: - for tok in (self.mutable,): + for tok in (self.mutable, self.thread_local): if tok is not None: raise CxxParseError(f"{msg}: unexpected '{tok.value}'") @@ -48,7 +50,13 @@ def validate( raise CxxParseError(f"{msg}: unexpected '{tok.value}'") if not meth_ok and not var_ok: - for tok in (self.constexpr, self.extern, self.inline, self.static): + for tok in ( + self.constexpr, + self.consteval, + self.extern, + self.inline, + self.static, + ): if tok is not None: raise CxxParseError(f"{msg}: unexpected '{tok.value}'") diff --git a/cxxheaderparser/types.py b/cxxheaderparser/types.py index eb25cfb..1a7b9cc 100644 --- a/cxxheaderparser/types.py +++ b/cxxheaderparser/types.py @@ -841,6 +841,9 @@ class Function: #: template int main() requires ... raw_requires: typing.Optional[Value] = None + #: True for an immediate function declared with ``consteval``. + consteval: bool = False + @dataclass class Method(Function): @@ -947,6 +950,9 @@ class Variable: #: Any attributes attached to this variable attributes: typing.List[Attribute] = field(default_factory=list) + #: Thread-local storage (``thread_local`` or GNU ``__thread``). + thread_local: bool = False + @dataclass class Field: @@ -972,6 +978,9 @@ class Field: #: Any attributes attached to this field attributes: typing.List[Attribute] = field(default_factory=list) + #: Thread-local storage (``thread_local`` or GNU ``__thread``). + thread_local: bool = False + @dataclass class UsingDecl: @@ -989,6 +998,9 @@ class UsingDecl: #: Documentation if present doxygen: typing.Optional[str] = None + #: True if the using-declaration is a pack expansion. + param_pack: bool = False + @dataclass class UsingAlias: @@ -1013,6 +1025,9 @@ class UsingAlias: #: Documentation if present doxygen: typing.Optional[str] = None + #: Attributes attached to this alias declaration + attributes: typing.List[Attribute] = field(default_factory=list) + @dataclass class DeductionGuide: diff --git a/tests/test_fix_alias_attributes.py b/tests/test_fix_alias_attributes.py new file mode 100644 index 0000000..d1c29a8 --- /dev/null +++ b/tests/test_fix_alias_attributes.py @@ -0,0 +1,71 @@ +from cxxheaderparser.simple import NamespaceScope, ParsedData, parse_string +from cxxheaderparser.types import ( + Attribute, + AttributeStyle, + FundamentalSpecifier, + NameSpecifier, + PQName, + Type, + UsingAlias, + Variable, +) + + +def test_alias_attribute_variant_01() -> None: + content = """ + using T __attribute__((deprecated)) = int; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + using_alias=[ + UsingAlias( + alias="T", + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + attributes=[Attribute(style=AttributeStyle.GCC, name="deprecated")], + ) + ], + ) + ) + + +def test_alias_attribute_variant_02() -> None: + content = """ + using T [[deprecated]] = int; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + using_alias=[ + UsingAlias( + alias="T", + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + attributes=[Attribute(style=AttributeStyle.CXX, name="deprecated")], + ) + ], + ) + ) diff --git a/tests/test_fix_consteval.py b/tests/test_fix_consteval.py new file mode 100644 index 0000000..7b9ed11 --- /dev/null +++ b/tests/test_fix_consteval.py @@ -0,0 +1,102 @@ +from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string +from cxxheaderparser.types import ( + ClassDecl, + Function, + FundamentalSpecifier, + Method, + NameSpecifier, + PQName, + Type, +) + + +def test_consteval_functions_and_methods() -> None: + content = """ + consteval int f(); + constexpr int g(); + int ordinary(); + struct S { + consteval int f() const { return 1; } + static consteval int g() { return 2; } + int ordinary(); + }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + has_body=True, + consteval=True, + access="public", + const=True, + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name=PQName(segments=[NameSpecifier(name="g")]), + parameters=[], + static=True, + has_body=True, + consteval=True, + access="public", + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name=PQName(segments=[NameSpecifier(name="ordinary")]), + parameters=[], + access="public", + ), + ], + ) + ], + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + consteval=True, + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="g")]), + parameters=[], + constexpr=True, + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="ordinary")]), + parameters=[], + ), + ], + ) + ) diff --git a/tests/test_fix_thread_local.py b/tests/test_fix_thread_local.py new file mode 100644 index 0000000..53e0d60 --- /dev/null +++ b/tests/test_fix_thread_local.py @@ -0,0 +1,188 @@ +from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string +from cxxheaderparser.types import ( + ClassDecl, + Field, + FundamentalSpecifier, + NameSpecifier, + PQName, + Type, + Variable, +) + + +def test_thread_storage_variant_01() -> None: + content = """ + thread_local int x; + static thread_local int local; + extern thread_local int shared; + thread_local static int reversed; + int ordinary; + struct S { static thread_local int member; static int ordinary; }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + fields=[ + Field( + access="public", + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="member", + static=True, + thread_local=True, + ), + Field( + access="public", + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="ordinary", + static=True, + ), + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + thread_local=True, + ), + Variable( + name=PQName(segments=[NameSpecifier(name="local")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + static=True, + thread_local=True, + ), + Variable( + name=PQName(segments=[NameSpecifier(name="shared")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + extern=True, + thread_local=True, + ), + Variable( + name=PQName(segments=[NameSpecifier(name="reversed")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + static=True, + thread_local=True, + ), + Variable( + name=PQName(segments=[NameSpecifier(name="ordinary")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ], + ) + ) + + +def test_thread_storage_variant_02() -> None: + content = """ + __thread int x; + static __thread int local; + extern __thread int shared; + __thread static int reversed; + int ordinary; + struct S { static __thread int member; static int ordinary; }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + fields=[ + Field( + access="public", + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="member", + static=True, + thread_local=True, + ), + Field( + access="public", + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="ordinary", + static=True, + ), + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + thread_local=True, + ), + Variable( + name=PQName(segments=[NameSpecifier(name="local")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + static=True, + thread_local=True, + ), + Variable( + name=PQName(segments=[NameSpecifier(name="shared")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + extern=True, + thread_local=True, + ), + Variable( + name=PQName(segments=[NameSpecifier(name="reversed")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + static=True, + thread_local=True, + ), + Variable( + name=PQName(segments=[NameSpecifier(name="ordinary")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ], + ) + ) diff --git a/tests/test_fix_trailing_return_attributes.py b/tests/test_fix_trailing_return_attributes.py new file mode 100644 index 0000000..36ab488 --- /dev/null +++ b/tests/test_fix_trailing_return_attributes.py @@ -0,0 +1,47 @@ +from cxxheaderparser.simple import NamespaceScope, ParsedData, parse_string +from cxxheaderparser.types import ( + Attribute, + AttributeStyle, + Function, + FundamentalSpecifier, + NameSpecifier, + PQName, + Type, + Value, + Variable, +) + + +def test_noexcept_attribute_before_trailing_return() -> None: + content = """ + auto f() noexcept [[vendor::tag]] -> int; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + attributes=[ + Attribute(style=AttributeStyle.CXX, name="vendor::tag") + ], + has_trailing_return=True, + noexcept=Value(tokens=[]), + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) diff --git a/tests/test_fix_typedef_attributes.py b/tests/test_fix_typedef_attributes.py new file mode 100644 index 0000000..ed6dcde --- /dev/null +++ b/tests/test_fix_typedef_attributes.py @@ -0,0 +1,66 @@ +from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string +from cxxheaderparser.types import ( + AnonymousName, + Attribute, + AttributeStyle, + ClassDecl, + Field, + FundamentalSpecifier, + NameSpecifier, + PQName, + Type, + Typedef, + Variable, +) + + +def test_gnu_typedef_struct_attribute() -> None: + content = """ + typedef struct { int x; } S __attribute__((aligned)); int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[AnonymousName(id=1)], classkey="struct" + ) + ), + fields=[ + Field( + access="public", + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="x", + ) + ], + ) + ], + typedefs=[ + Typedef( + type=Type( + typename=PQName( + segments=[AnonymousName(id=1)], classkey="struct" + ) + ), + name="S", + attributes=[Attribute(style=AttributeStyle.GCC, name="aligned")], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) diff --git a/tests/test_fix_using_pack.py b/tests/test_fix_using_pack.py new file mode 100644 index 0000000..de59464 --- /dev/null +++ b/tests/test_fix_using_pack.py @@ -0,0 +1,106 @@ +from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string +from cxxheaderparser.types import ( + BaseClass, + ClassDecl, + FundamentalSpecifier, + Method, + NameSpecifier, + PQName, + TemplateDecl, + TemplateTypeParam, + Type, + UsingDecl, +) + + +def test_using_pack_expansion() -> None: + content = """ + template struct S : T... { using T::f...; }; + struct B { void f(); }; + struct D : B { using B::f; }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ), + bases=[ + BaseClass( + access="public", + typename=PQName(segments=[NameSpecifier(name="T")]), + param_pack=True, + ) + ], + template=TemplateDecl( + params=[ + TemplateTypeParam( + typekey="class", name="T", param_pack=True + ) + ] + ), + ), + using=[ + UsingDecl( + typename=PQName( + segments=[ + NameSpecifier(name="T"), + NameSpecifier(name="f"), + ] + ), + access="public", + param_pack=True, + ) + ], + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="B")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + access="public", + ) + ], + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="D")], classkey="struct" + ), + bases=[ + BaseClass( + access="public", + typename=PQName(segments=[NameSpecifier(name="B")]), + ) + ], + ), + using=[ + UsingDecl( + typename=PQName( + segments=[ + NameSpecifier(name="B"), + NameSpecifier(name="f"), + ] + ), + access="public", + ) + ], + ), + ] + ) + ) diff --git a/tests/test_trivial_base_initializer_decltype.py b/tests/test_trivial_base_initializer_decltype.py new file mode 100644 index 0000000..12fef39 --- /dev/null +++ b/tests/test_trivial_base_initializer_decltype.py @@ -0,0 +1,347 @@ +from cxxheaderparser.simple import ( + ClassDecl, + ClassScope, + Method, + NamespaceScope, + ParsedData, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + BaseClass, + DecltypeSpecifier, + FundamentalSpecifier, + NameSpecifier, + PQName, + Parameter, + TemplateArgument, + TemplateDecl, + TemplateSpecialization, + TemplateTypeParam, + Token, + Type, +) + + +def test_base_initializer_parentheses() -> None: + content = """ + template struct A { A(int); }; struct B : A { B() : A(0) {} void after(); }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="A")], classkey="struct" + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + ), + methods=[ + Method( + return_type=None, + name=PQName(segments=[NameSpecifier(name="A")]), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ) + ) + ], + access="public", + constructor=True, + ) + ], + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="B")], classkey="struct" + ), + bases=[ + BaseClass( + access="public", + typename=PQName( + segments=[ + NameSpecifier( + name="A", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + DecltypeSpecifier( + tokens=[ + Token( + value="0" + ) + ] + ) + ] + ) + ) + ) + ] + ), + ) + ] + ), + ) + ], + ), + methods=[ + Method( + return_type=None, + name=PQName(segments=[NameSpecifier(name="B")]), + parameters=[], + has_body=True, + access="public", + constructor=True, + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="after")]), + parameters=[], + access="public", + ), + ], + ), + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_base_initializer_braces() -> None: + content = """ + template struct A { A(int); }; struct B : A { B() : A{0} {} void after(); }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="A")], classkey="struct" + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + ), + methods=[ + Method( + return_type=None, + name=PQName(segments=[NameSpecifier(name="A")]), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ) + ) + ], + access="public", + constructor=True, + ) + ], + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="B")], classkey="struct" + ), + bases=[ + BaseClass( + access="public", + typename=PQName( + segments=[ + NameSpecifier( + name="A", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + DecltypeSpecifier( + tokens=[ + Token( + value="0" + ) + ] + ) + ] + ) + ) + ) + ] + ), + ) + ] + ), + ) + ], + ), + methods=[ + Method( + return_type=None, + name=PQName(segments=[NameSpecifier(name="B")]), + parameters=[], + has_body=True, + access="public", + constructor=True, + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="after")]), + parameters=[], + access="public", + ), + ], + ), + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_base_initializer_nested_parentheses() -> None: + content = """ + template struct A { A(int); }; struct B : A { B() : A(0) {} void after(); }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="A")], classkey="struct" + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + ), + methods=[ + Method( + return_type=None, + name=PQName(segments=[NameSpecifier(name="A")]), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ) + ) + ], + access="public", + constructor=True, + ) + ], + ), + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="B")], classkey="struct" + ), + bases=[ + BaseClass( + access="public", + typename=PQName( + segments=[ + NameSpecifier( + name="A", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + DecltypeSpecifier( + tokens=[ + Token( + value="0" + ) + ] + ) + ] + ) + ) + ) + ] + ), + ) + ] + ), + ) + ], + ), + methods=[ + Method( + return_type=None, + name=PQName(segments=[NameSpecifier(name="B")]), + parameters=[], + has_body=True, + access="public", + constructor=True, + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="after")]), + parameters=[], + access="public", + ), + ], + ), + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) diff --git a/tests/test_trivial_c_complex.py b/tests/test_trivial_c_complex.py new file mode 100644 index 0000000..077373a --- /dev/null +++ b/tests/test_trivial_c_complex.py @@ -0,0 +1,97 @@ +from cxxheaderparser.simple import NamespaceScope, ParsedData, Variable, parse_string +from cxxheaderparser.types import FundamentalSpecifier, NameSpecifier, PQName, Type +from cxxheaderparser.tokfmt import PlyLexer + + +def test_complex_spelling_1() -> None: + content = """ + float _Complex x; int after; + """ + + 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="float _Complex")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_complex_spelling_2() -> None: + content = """ + _Complex double x; int after; + """ + + 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="_Complex double")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_complex_spelling_3() -> None: + content = """ + long double _Complex x; int after; + """ + + 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="long double _Complex")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_c_complex_raw_keyword(): + lexer = PlyLexer() + lexer.input("_Complex") + token = lexer.token() + assert (token.type, token.value) == ("_Complex", "_Complex") diff --git a/tests/test_trivial_c_static_assert.py b/tests/test_trivial_c_static_assert.py new file mode 100644 index 0000000..e1304ff --- /dev/null +++ b/tests/test_trivial_c_static_assert.py @@ -0,0 +1,72 @@ +from cxxheaderparser.simple import ( + ClassDecl, + ClassScope, + Field, + NamespaceScope, + ParsedData, + Variable, + parse_string, +) +from cxxheaderparser.types import FundamentalSpecifier, NameSpecifier, PQName, Type +from cxxheaderparser.tokfmt import PlyLexer + + +def test_c_static_assert_namespace() -> None: + content = """ + _Static_assert(1, "ok"); 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")]) + ), + ) + ] + ) + ) + + +def test_c_static_assert_class() -> None: + content = """ + struct S { _Static_assert(1, "ok"); int x; }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + fields=[ + Field( + access="public", + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="x", + ) + ], + ) + ] + ) + ) + + +def test_c_static_assert_raw_keyword(): + lexer = PlyLexer() + lexer.input("_Static_assert") + token = lexer.token() + assert (token.type, token.value) == ("_Static_assert", "_Static_assert") diff --git a/tests/test_trivial_char8_t.py b/tests/test_trivial_char8_t.py new file mode 100644 index 0000000..6a601bc --- /dev/null +++ b/tests/test_trivial_char8_t.py @@ -0,0 +1,52 @@ +from cxxheaderparser.simple import ( + Function, + NamespaceScope, + ParsedData, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + FundamentalSpecifier, + NameSpecifier, + PQName, + Parameter, + Type, +) + + +def test_char8_t_is_fundamental() -> None: + content = """ + char8_t x; char8_t f(char8_t); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="char8_t")]) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="char8_t")] + ) + ) + ) + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="char8_t")]) + ), + ) + ], + ) + ) diff --git a/tests/test_trivial_constrained_alias.py b/tests/test_trivial_constrained_alias.py new file mode 100644 index 0000000..0c6f9a5 --- /dev/null +++ b/tests/test_trivial_constrained_alias.py @@ -0,0 +1,54 @@ +from cxxheaderparser.simple import ( + NamespaceScope, + ParsedData, + UsingAlias, + Value, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + FundamentalSpecifier, + NameSpecifier, + PQName, + TemplateDecl, + TemplateTypeParam, + Token, + Type, +) + + +def test_constrained_alias() -> None: + content = """ + template requires (true) using A = T; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + using_alias=[ + UsingAlias( + alias="A", + type=Type(typename=PQName(segments=[NameSpecifier(name="T")])), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="("), + Token(value="true"), + Token(value=")"), + ] + ), + ), + ) + ], + ) + ) diff --git a/tests/test_trivial_ellipsis_without_comma.py b/tests/test_trivial_ellipsis_without_comma.py new file mode 100644 index 0000000..304e70f --- /dev/null +++ b/tests/test_trivial_ellipsis_without_comma.py @@ -0,0 +1,98 @@ +from cxxheaderparser.simple import ( + Function, + NamespaceScope, + ParsedData, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + FundamentalSpecifier, + NameSpecifier, + PQName, + Parameter, + Pointer, + Type, +) + + +def test_ellipsis_without_comma_int() -> None: + content = """ + void f(int x...); int after; + """ + + 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="x", + ) + ], + vararg=True, + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_ellipsis_without_comma_pointer() -> None: + content = """ + void f(const char *format...); int after; + """ + + 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=Pointer( + ptr_to=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="char")] + ), + const=True, + ) + ), + name="format", + ) + ], + vararg=True, + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) diff --git a/tests/test_trivial_extern_c_function_typedef.py b/tests/test_trivial_extern_c_function_typedef.py new file mode 100644 index 0000000..035d053 --- /dev/null +++ b/tests/test_trivial_extern_c_function_typedef.py @@ -0,0 +1,79 @@ +from cxxheaderparser.simple import ( + Function, + NamespaceScope, + ParsedData, + Typedef, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + FunctionType, + FundamentalSpecifier, + NameSpecifier, + PQName, + Type, +) +import re +import pytest +from cxxheaderparser.errors import CxxParseError + + +def test_inherited_linkage_function_typedef() -> None: + content = """ + extern "C" { typedef void F(); void f(); extern int x; } int after; + """ + + 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=[], + extern='"C"', + ) + ], + typedefs=[ + Typedef( + type=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[], + ), + name="F", + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + extern='"C"', + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ], + ) + ) + + +def test_explicit_extern_function_typedef_rejected() -> None: + content = """ + typedef extern void F(); + """ + + err = ":1: parse error evaluating 'typedef': parsing typedef: unexpected 'extern'" + with pytest.raises(CxxParseError, match=re.escape(err)): + parse_string(content, cleandoc=True) diff --git a/tests/test_trivial_friend_after_return_type.py b/tests/test_trivial_friend_after_return_type.py new file mode 100644 index 0000000..fe32929 --- /dev/null +++ b/tests/test_trivial_friend_after_return_type.py @@ -0,0 +1,68 @@ +from cxxheaderparser.simple import ( + ClassDecl, + ClassScope, + FriendDecl, + Method, + NamespaceScope, + ParsedData, + Variable, + parse_string, +) +from cxxheaderparser.types import FundamentalSpecifier, NameSpecifier, PQName, Type +import re +import pytest +from cxxheaderparser.errors import CxxParseError + + +def test_friend_after_return_type() -> None: + content = """ + struct S { void friend f(); }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + friends=[ + FriendDecl( + fn=Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + access="public", + ) + ) + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_friend_outside_class_rejected() -> None: + content = """ + void friend f(); + """ + + err = ":1: parse error evaluating 'friend': unexpected 'friend'" + with pytest.raises(CxxParseError, match=re.escape(err)): + parse_string(content, cleandoc=True) diff --git a/tests/test_trivial_gnu_enumerator_attribute.py b/tests/test_trivial_gnu_enumerator_attribute.py new file mode 100644 index 0000000..eff8f10 --- /dev/null +++ b/tests/test_trivial_gnu_enumerator_attribute.py @@ -0,0 +1,95 @@ +from cxxheaderparser.simple import ( + EnumDecl, + NamespaceScope, + ParsedData, + Value, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + Attribute, + AttributeStyle, + Enumerator, + FundamentalSpecifier, + NameSpecifier, + PQName, + Token, + Type, +) + + +def test_gnu_enumerator_attributes_implicit() -> None: + content = """ + enum E { a __attribute__((deprecated)) __attribute__((unused)), b }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + enums=[ + EnumDecl( + typename=PQName( + segments=[NameSpecifier(name="E")], classkey="enum" + ), + values=[ + Enumerator( + name="a", + attributes=[ + Attribute(style=AttributeStyle.GCC, name="deprecated"), + Attribute(style=AttributeStyle.GCC, name="unused"), + ], + ), + Enumerator(name="b"), + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_gnu_enumerator_attributes_explicit() -> None: + content = """ + enum E { a __attribute__((deprecated)) __attribute__((unused)) = 3, b }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + enums=[ + EnumDecl( + typename=PQName( + segments=[NameSpecifier(name="E")], classkey="enum" + ), + values=[ + Enumerator( + name="a", + value=Value(tokens=[Token(value="3")]), + attributes=[ + Attribute(style=AttributeStyle.GCC, name="deprecated"), + Attribute(style=AttributeStyle.GCC, name="unused"), + ], + ), + Enumerator(name="b"), + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) diff --git a/tests/test_trivial_gnu_extension.py b/tests/test_trivial_gnu_extension.py new file mode 100644 index 0000000..b6fa3fb --- /dev/null +++ b/tests/test_trivial_gnu_extension.py @@ -0,0 +1,99 @@ +from cxxheaderparser.simple import ( + ClassDecl, + ClassScope, + Field, + NamespaceScope, + ParsedData, + Typedef, + Variable, + parse_string, +) +from cxxheaderparser.types import FundamentalSpecifier, NameSpecifier, PQName, Type +from cxxheaderparser.tokfmt import PlyLexer + + +def test_gnu_extension_preserves_doxygen_typedef() -> None: + content = """ + /// Documentation + __extension__ typedef int T; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + typedefs=[ + Typedef( + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name="T", + ) + ] + ) + ) + + +def test_gnu_extension_preserves_doxygen_variable() -> None: + content = """ + /// Documentation + __extension__ 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")]) + ), + doxygen="/// Documentation", + ) + ] + ) + ) + + +def test_gnu_extension_preserves_doxygen_class() -> None: + content = """ + /// Documentation + __extension__ struct S { int x; }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ), + doxygen="/// Documentation", + ), + fields=[ + Field( + access="public", + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + name="x", + ) + ], + ) + ] + ) + ) + + +def test_gnu_extension_raw_keyword(): + lexer = PlyLexer() + lexer.input("__extension__") + token = lexer.token() + assert (token.type, token.value) == ("__extension__", "__extension__") diff --git a/tests/test_trivial_gnu_inline.py b/tests/test_trivial_gnu_inline.py new file mode 100644 index 0000000..a925f2f --- /dev/null +++ b/tests/test_trivial_gnu_inline.py @@ -0,0 +1,131 @@ +from cxxheaderparser.simple import ( + ClassDecl, + ClassScope, + Function, + Method, + NamespaceScope, + ParsedData, + Variable, + parse_string, +) +from cxxheaderparser.types import FundamentalSpecifier, NameSpecifier, PQName, Type +from cxxheaderparser.tokfmt import PlyLexer + + +def test_gnu_inline_definition() -> None: + content = """ + __inline__ void f() {} + """ + + 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=[], + inline=True, + has_body=True, + ) + ] + ) + ) + + +def test_gnu_inline_static() -> None: + content = """ + static __inline__ int f(); + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + static=True, + inline=True, + ) + ] + ) + ) + + +def test_gnu_inline_method() -> None: + content = """ + struct S { __inline__ void f(); }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + inline=True, + access="public", + ) + ], + ) + ] + ) + ) + + +def test_gnu_inline_namespace() -> None: + content = """ + __inline__ namespace N { int x; } + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + namespaces={ + "N": NamespaceScope( + name="N", + inline=True, + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + ) + ], + ) + } + ) + ) + + +def test_gnu_inline_raw_keyword(): + lexer = PlyLexer() + lexer.input("__inline__") + token = lexer.token() + assert (token.type, token.value) == ("__inline__", "__inline__") diff --git a/tests/test_trivial_gnu_int128.py b/tests/test_trivial_gnu_int128.py new file mode 100644 index 0000000..2525067 --- /dev/null +++ b/tests/test_trivial_gnu_int128.py @@ -0,0 +1,97 @@ +from cxxheaderparser.simple import NamespaceScope, ParsedData, Variable, parse_string +from cxxheaderparser.types import FundamentalSpecifier, NameSpecifier, PQName, Type +from cxxheaderparser.tokfmt import PlyLexer + + +def test_int128_spelling_1() -> None: + content = """ + __int128 x; int after; + """ + + 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="__int128")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_int128_spelling_2() -> None: + content = """ + unsigned __int128 x; int after; + """ + + 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="unsigned __int128")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_int128_spelling_3() -> None: + content = """ + signed __int128 x; int after; + """ + + 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="signed __int128")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_gnu_int128_raw_keyword(): + lexer = PlyLexer() + lexer.input("__int128") + token = lexer.token() + assert (token.type, token.value) == ("__int128", "__int128") diff --git a/tests/test_trivial_gnu_member_function_attribute.py b/tests/test_trivial_gnu_member_function_attribute.py new file mode 100644 index 0000000..6210737 --- /dev/null +++ b/tests/test_trivial_gnu_member_function_attribute.py @@ -0,0 +1,243 @@ +from cxxheaderparser.simple import ( + ClassDecl, + ClassScope, + Method, + NamespaceScope, + ParsedData, + Value, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + Attribute, + AttributeStyle, + FundamentalSpecifier, + NameSpecifier, + PQName, + Type, +) + + +def test_gnu_member_function_attributes_definition() -> None: + content = """ + struct S { void f() __attribute__((unused)) {} void g(); }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + attributes=[ + Attribute(style=AttributeStyle.GCC, name="unused") + ], + has_body=True, + access="public", + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="g")]), + parameters=[], + access="public", + ), + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_gnu_member_function_attributes_const() -> None: + content = """ + struct S { void f() __attribute__((unused)) const {} void g(); }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + attributes=[ + Attribute(style=AttributeStyle.GCC, name="unused") + ], + has_body=True, + access="public", + const=True, + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="g")]), + parameters=[], + access="public", + ), + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_gnu_member_function_attributes_noexcept() -> None: + content = """ + struct S { void f() __attribute__((unused)) noexcept {} void g(); }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + attributes=[ + Attribute(style=AttributeStyle.GCC, name="unused") + ], + has_body=True, + noexcept=Value(tokens=[]), + access="public", + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="g")]), + parameters=[], + access="public", + ), + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_gnu_member_function_attributes_declaration() -> None: + content = """ + struct S { void f() __attribute__((unused)) ; void g(); }; int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + attributes=[ + Attribute(style=AttributeStyle.GCC, name="unused") + ], + access="public", + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="g")]), + parameters=[], + access="public", + ), + ], + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) diff --git a/tests/test_trivial_gnu_signed.py b/tests/test_trivial_gnu_signed.py new file mode 100644 index 0000000..f1590a1 --- /dev/null +++ b/tests/test_trivial_gnu_signed.py @@ -0,0 +1,97 @@ +from cxxheaderparser.simple import NamespaceScope, ParsedData, Variable, parse_string +from cxxheaderparser.types import FundamentalSpecifier, NameSpecifier, PQName, Type +from cxxheaderparser.tokfmt import PlyLexer + + +def test_gnu_signed_spelling_1() -> None: + content = """ + __signed__ x; int after; + """ + + 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="__signed__")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_gnu_signed_spelling_2() -> None: + content = """ + __signed__ char x; int after; + """ + + 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="__signed__ char")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_gnu_signed_spelling_3() -> None: + content = """ + long __signed__ int x; int after; + """ + + 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="long __signed__ int")] + ) + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ] + ) + ) + + +def test_gnu_signed_raw_keyword(): + lexer = PlyLexer() + lexer.input("__signed__") + token = lexer.token() + assert (token.type, token.value) == ("__signed__", "__signed__") diff --git a/tests/test_trivial_gnu_volatile.py b/tests/test_trivial_gnu_volatile.py new file mode 100644 index 0000000..a4591ab --- /dev/null +++ b/tests/test_trivial_gnu_volatile.py @@ -0,0 +1,187 @@ +from cxxheaderparser.simple import ( + ClassDecl, + ClassScope, + Method, + NamespaceScope, + ParsedData, + Typedef, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + FunctionType, + FundamentalSpecifier, + MemberPointer, + NameSpecifier, + PQName, + Pointer, + Type, +) +from cxxheaderparser.tokfmt import PlyLexer + + +def test_gnu_volatile_prefix() -> None: + content = """ + __volatile__ 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")]), + volatile=True, + ), + ) + ] + ) + ) + + +def test_gnu_volatile_suffix() -> None: + content = """ + int __volatile__ 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")]), + volatile=True, + ), + ) + ] + ) + ) + + +def test_gnu_volatile_pointer() -> None: + content = """ + int * __volatile__ p; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="p")]), + type=Pointer( + ptr_to=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + volatile=True, + ), + ) + ] + ) + ) + + +def test_gnu_volatile_method() -> None: + content = """ + struct S { void f() __volatile__; }; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="S")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + access="public", + volatile=True, + ) + ], + ) + ] + ) + ) + + +def test_gnu_volatile_function_typedef() -> None: + content = """ + typedef void F() __volatile__; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + typedefs=[ + Typedef( + type=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[], + volatile=True, + ), + name="F", + ) + ] + ) + ) + + +def test_gnu_volatile_member_pointer_typedef() -> None: + content = """ + typedef void (S::*F)() __volatile__; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + typedefs=[ + Typedef( + type=MemberPointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[], + volatile=True, + ), + classname=PQName(segments=[NameSpecifier(name="S")]), + ), + name="F", + ) + ] + ) + ) + + +def test_gnu_volatile_raw_keyword(): + lexer = PlyLexer() + lexer.input("__volatile__") + token = lexer.token() + assert (token.type, token.value) == ("__volatile__", "__volatile__") diff --git a/tests/test_trivial_nullptr_t_typedef.py b/tests/test_trivial_nullptr_t_typedef.py new file mode 100644 index 0000000..d861a0e --- /dev/null +++ b/tests/test_trivial_nullptr_t_typedef.py @@ -0,0 +1,50 @@ +from cxxheaderparser.simple import ( + NamespaceScope, + ParsedData, + Typedef, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + DecltypeSpecifier, + FundamentalSpecifier, + NameSpecifier, + PQName, + Token, + Type, +) + + +def test_nullptr_t_typedef_name() -> None: + content = """ + typedef decltype(nullptr) nullptr_t; nullptr_t x; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + typedefs=[ + Typedef( + type=Type( + typename=PQName( + segments=[ + DecltypeSpecifier(tokens=[Token(value="nullptr")]) + ] + ) + ), + name="nullptr_t", + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="x")]), + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="nullptr_t")] + ) + ), + ) + ], + ) + ) diff --git a/tests/test_trivial_requires_requires.py b/tests/test_trivial_requires_requires.py new file mode 100644 index 0000000..f1cc166 --- /dev/null +++ b/tests/test_trivial_requires_requires.py @@ -0,0 +1,112 @@ +from cxxheaderparser.simple import ( + Function, + NamespaceScope, + ParsedData, + Value, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + FundamentalSpecifier, + NameSpecifier, + PQName, + TemplateDecl, + TemplateTypeParam, + Token, + Type, +) + + +def test_requires_expression_without_parameters() -> None: + content = """ + template requires requires { typename T::type; } void f(); int after; + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="requires"), + Token(value="{"), + Token(value="typename"), + Token(value="T"), + Token(value="::"), + Token(value="type"), + Token(value=";"), + Token(value="}"), + ] + ), + ), + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_requires_expression_with_parameters() -> None: + content = """ + template requires requires (T t) { t.f(); } void f(); int after; + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="requires"), + Token(value="("), + Token(value="T"), + Token(value="t"), + Token(value=")"), + Token(value="{"), + Token(value="t"), + Token(value="."), + Token(value="f"), + Token(value="("), + Token(value=")"), + Token(value=";"), + Token(value="}"), + ] + ), + ), + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) diff --git a/tests/test_trivial_requires_true.py b/tests/test_trivial_requires_true.py new file mode 100644 index 0000000..21460f7 --- /dev/null +++ b/tests/test_trivial_requires_true.py @@ -0,0 +1,248 @@ +from cxxheaderparser.simple import ( + Function, + NamespaceScope, + ParsedData, + Value, + parse_string, +) +from cxxheaderparser.types import ( + FundamentalSpecifier, + NameSpecifier, + PQName, + TemplateDecl, + TemplateTypeParam, + Token, + Type, +) + + +def test_boolean_constraint_leading_true() -> None: + content = """ + template requires true void f(); + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")], + raw_requires_pre=Value(tokens=[Token(value="true")]), + ), + ) + ] + ) + ) + + +def test_boolean_constraint_leading_false() -> None: + content = """ + template requires false void f(); + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")], + raw_requires_pre=Value(tokens=[Token(value="false")]), + ), + ) + ] + ) + ) + + +def test_boolean_constraint_leading_or() -> None: + content = """ + template requires false || true void f(); + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="false"), + Token(value="||"), + Token(value="true"), + ] + ), + ), + ) + ] + ) + ) + + +def test_boolean_constraint_leading_and() -> None: + content = """ + template requires true && false void f(); + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="true"), + Token(value="&&"), + Token(value="false"), + ] + ), + ), + ) + ] + ) + ) + + +def test_boolean_constraint_trailing_true() -> None: + content = """ + template void f() requires true; + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + raw_requires=Value(tokens=[Token(value="true")]), + ) + ] + ) + ) + + +def test_boolean_constraint_trailing_false() -> None: + content = """ + template void f() requires false; + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + raw_requires=Value(tokens=[Token(value="false")]), + ) + ] + ) + ) + + +def test_boolean_constraint_trailing_or() -> None: + content = """ + template void f() requires false || true; + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + raw_requires=Value( + tokens=[ + Token(value="false"), + Token(value="||"), + Token(value="true"), + ] + ), + ) + ] + ) + ) + + +def test_boolean_constraint_trailing_and() -> None: + content = """ + template void f() requires true && false; + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + raw_requires=Value( + tokens=[ + Token(value="true"), + Token(value="&&"), + Token(value="false"), + ] + ), + ) + ] + ) + ) diff --git a/tests/test_trivial_trailing_requires_noexcept.py b/tests/test_trivial_trailing_requires_noexcept.py new file mode 100644 index 0000000..10658dd --- /dev/null +++ b/tests/test_trivial_trailing_requires_noexcept.py @@ -0,0 +1,179 @@ +from cxxheaderparser.simple import ( + Function, + NamespaceScope, + ParsedData, + Value, + Variable, + parse_string, +) +from cxxheaderparser.types import ( + FundamentalSpecifier, + NameSpecifier, + PQName, + TemplateDecl, + TemplateTypeParam, + Token, + Type, +) +import re +import pytest +from cxxheaderparser.errors import CxxParseError + + +def test_sequential_function_suffixes_noexcept() -> None: + content = """ + template void f() noexcept requires (true); int after; + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + noexcept=Value(tokens=[]), + raw_requires=Value( + tokens=[Token(value="("), Token(value="true"), Token(value=")")] + ), + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_sequential_function_suffixes_conditional_noexcept() -> None: + content = """ + template void f() noexcept(false) requires (true); int after; + """ + + 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=[], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + noexcept=Value(tokens=[Token(value="false")]), + raw_requires=Value( + tokens=[Token(value="("), Token(value="true"), Token(value=")")] + ), + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_sequential_function_suffixes_return_type() -> None: + content = """ + template auto f() -> int requires (true); int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + has_trailing_return=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + raw_requires=Value( + tokens=[Token(value="("), Token(value="true"), Token(value=")")] + ), + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_sequential_function_suffixes_noexcept_return_type() -> None: + content = """ + template auto f() noexcept -> int requires (true); int after; + """ + + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="f")]), + parameters=[], + has_trailing_return=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + noexcept=Value(tokens=[]), + raw_requires=Value( + tokens=[Token(value="("), Token(value="true"), Token(value=")")] + ), + ) + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="after")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ) + ], + ) + ) + + +def test_non_template_constraint_still_rejected() -> None: + content = """ + void f() noexcept requires (true); + """ + + err = ":1: parse error evaluating 'requires': unexpected 'requires'" + with pytest.raises(CxxParseError, match=re.escape(err)): + parse_string(content, cleandoc=True)