diff --git a/cxxheaderparser/lexer.py b/cxxheaderparser/lexer.py index 341ef76..15dec34 100644 --- a/cxxheaderparser/lexer.py +++ b/cxxheaderparser/lexer.py @@ -83,6 +83,7 @@ class PlyLexer: "char16_t", "char32_t", "class", + "concept", "const", "constexpr", "const_cast", @@ -121,6 +122,7 @@ class PlyLexer: "public", "register", "reinterpret_cast", + "requires", "return", "short", "signed", @@ -186,6 +188,7 @@ class PlyLexer: "DBL_RBRACKET", "DBL_COLON", "DBL_AMP", + "DBL_PIPE", "ARROW", "SHIFT_LEFT", ] + list(keywords) @@ -471,6 +474,7 @@ class PlyLexer: t_DBL_RBRACKET = r"\]\]" t_DBL_COLON = r"::" t_DBL_AMP = r"&&" + t_DBL_PIPE = r"\|\|" t_ARROW = r"->" t_SHIFT_LEFT = r"<<" # SHIFT_RIGHT introduces ambiguity diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 18614c5..a4217a2 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -22,6 +22,7 @@ from .types import ( AutoSpecifier, BaseClass, ClassDecl, + Concept, DecltypeSpecifier, DecoratedType, EnumDecl, @@ -537,7 +538,7 @@ class CxxParser: self._finish_class_decl(old_state) # - # Template parsing + # Template and concept parsing # def _parse_template_type_parameter( @@ -605,9 +606,13 @@ class CxxParser: lex.return_token(ptok) param = self._parse_template_type_parameter(tok, None) else: - param = self._parse_parameter(ptok, TemplateNonTypeParam, ">") + param, _ = self._parse_parameter( + ptok, TemplateNonTypeParam, False, ">" + ) else: - param = self._parse_parameter(tok, TemplateNonTypeParam, ">") + param, _ = self._parse_parameter( + tok, TemplateNonTypeParam, concept_ok=False, end=">" + ) params.append(param) @@ -640,6 +645,11 @@ class CxxParser: 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) @@ -750,6 +760,117 @@ class CxxParser: self.state, TemplateInst(typename, extern, doxygen) ) + def _parse_concept( + self, + tok: LexToken, + doxygen: typing.Optional[str], + template: TemplateDecl, + ) -> None: + name = self._next_token_must_be("NAME") + self._next_token_must_be("=") + + # not trying to understand this for now + raw_constraint = self._create_value(self._consume_value_until([], ",", ";")) + + state = self.state + if isinstance(state, ClassBlockState): + raise CxxParseError("concept cannot be defined in a class") + + self.visitor.on_concept( + state, + Concept( + template=template, + name=name.value, + raw_constraint=raw_constraint, + doxygen=doxygen, + ), + ) + + # fmt: off + _expr_operators = { + "<", ">", "|", "%", "^", "!", "*", "-", "+", "&", "=", + "&&", "||", "<<" + } + # fmt: on + + def _parse_requires( + self, + tok: LexToken, + ) -> Value: + tok = self.lex.token() + + rawtoks: typing.List[LexToken] = [] + + # The easier case -- requires requires + if tok.type == "requires": + rawtoks.append(tok) + for tt in ("(", "{"): + tok = self._next_token_must_be(tt) + rawtoks.extend(self._consume_balanced_tokens(tok)) + # .. and that's it? + + # this is either a parenthesized expression or a primary clause + elif tok.type == "(": + rawtoks.extend(self._consume_balanced_tokens(tok)) + else: + while True: + if tok.type == "(": + rawtoks.extend(self._consume_balanced_tokens(tok)) + else: + tok = self._parse_requires_segment(tok, rawtoks) + + # If this is not an operator of some kind, we don't know how + # to proceed so let the next parser figure it out + if tok.value not in self._expr_operators: + break + + rawtoks.append(tok) + + # check once more for compound operator? + tok = self.lex.token() + if tok.value in self._expr_operators: + rawtoks.append(tok) + tok = self.lex.token() + + self.lex.return_token(tok) + + return self._create_value(rawtoks) + + def _parse_requires_segment( + self, tok: LexToken, rawtoks: typing.List[LexToken] + ) -> LexToken: + # first token could be a name or :: + if tok.type == "DBL_COLON": + rawtoks.append(tok) + tok = self.lex.token() + + while True: + # This token has to be a name or some other valid name-like thing + if tok.value == "decltype": + rawtoks.append(tok) + tok = self._next_token_must_be("(") + rawtoks.extend(self._consume_balanced_tokens(tok)) + elif tok.type == "NAME": + rawtoks.append(tok) + else: + # not sure what I expected, but I didn't find it + raise self._parse_error(tok) + + tok = self.lex.token() + + # Maybe there's a specialization + if tok.value == "<": + rawtoks.extend(self._consume_balanced_tokens(tok)) + tok = self.lex.token() + + # Maybe we keep trying to parse this name + if tok.type == "DBL_COLON": + tok = self.lex.token() + continue + + # Let the caller decide + return tok + # # Attributes # @@ -1615,23 +1736,43 @@ class CxxParser: # def _parse_parameter( - self, tok: typing.Optional[LexToken], cls: typing.Type[PT], end: str = ")" - ) -> PT: + self, + tok: typing.Optional[LexToken], + cls: typing.Type[PT], + concept_ok: bool, + end: str = ")", + ) -> typing.Tuple[PT, typing.Optional[Type]]: """ Parses a single parameter (excluding vararg parameters). Also used to parse template non-type parameters + + Returns parameter type, abbreviated template type """ param_name = None default = None param_pack = False + parsed_type: typing.Optional[Type] + at_type: typing.Optional[Type] = None - # required typename + decorators - parsed_type, mods = self._parse_type(tok) - if parsed_type is None: - raise self._parse_error(None) + if not tok: + tok = self.lex.token() - mods.validate(var_ok=False, meth_ok=False, msg="parsing parameter") + # placeholder type, skip typename + if tok.type == "auto": + at_type = parsed_type = Type(PQName([AutoSpecifier()])) + else: + # required typename + decorators + 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 parameter") + + # Could be a concept + if concept_ok and self.lex.token_if("auto"): + at_type = Type(parsed_type.typename) + parsed_type.typename = PQName([AutoSpecifier()]) dtype = self._parse_cv_ptr(parsed_type) @@ -1659,23 +1800,32 @@ class CxxParser: if self.lex.token_if("="): default = self._create_value(self._consume_value_until([], ",", end)) + # abbreviated template pack + if at_type and self.lex.token_if("ELLIPSIS"): + param_pack = True + param = cls(type=dtype, name=param_name, default=default, param_pack=param_pack) self.debug_print("parameter: %s", param) - return param + return param, at_type - def _parse_parameters(self) -> typing.Tuple[typing.List[Parameter], bool]: + def _parse_parameters( + self, concept_ok: bool + ) -> typing.Tuple[typing.List[Parameter], bool, typing.List[TemplateParam]]: """ - Consumes function parameters and returns them, and vararg if found + Consumes function parameters and returns them, and vararg if found, and + promotes abbreviated template parameters to actual template parameters + if concept_ok is True """ # starting at a ( # special case: zero parameters if self.lex.token_if(")"): - return [], False + return [], False, [] params: typing.List[Parameter] = [] vararg = False + at_params: typing.List[TemplateParam] = [] while True: if self.lex.token_if("ELLIPSIS"): @@ -1683,8 +1833,17 @@ class CxxParser: self._next_token_must_be(")") break - param = self._parse_parameter(None, Parameter) + param, at_type = self._parse_parameter(None, Parameter, concept_ok) params.append(param) + if at_type: + at_params.append( + TemplateNonTypeParam( + type=at_type, + param_idx=len(params) - 1, + param_pack=param.param_pack, + ) + ) + tok = self._next_token_must_be(",", ")") if tok.value == ")": break @@ -1699,7 +1858,7 @@ class CxxParser: ): params = [] - return params, vararg + return params, vararg, at_params _auto_return_typename = PQName([AutoSpecifier()]) @@ -1745,6 +1904,15 @@ class CxxParser: 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: + fn_template = fn.template + if fn_template is None: + raise self._parse_error(rtok) + elif isinstance(fn_template, list): + fn_template = fn_template[0] + fn_template.raw_requires_post = self._parse_requires(rtok) if self.lex.token_if("{"): self._discard_contents("{", "}") @@ -1805,6 +1973,13 @@ class CxxParser: if otok: toks = self._consume_balanced_tokens(otok)[1:-1] method.noexcept = self._create_value(toks) + elif tok_value == "requires": + method_template = method.template + if method_template is None: + raise self._parse_error(tok) + elif isinstance(method_template, list): + method_template = method_template[0] + method_template.raw_requires_post = self._parse_requires(tok) else: self.lex.return_token(tok) break @@ -1846,7 +2021,16 @@ class CxxParser: state.location = location is_class_block = isinstance(state, ClassBlockState) - params, vararg = self._parse_parameters() + params, vararg, at_params = self._parse_parameters(True) + + # Promote abbreviated template parameters + if at_params: + if template is None: + template = TemplateDecl(at_params) + elif isinstance(template, TemplateDecl): + template.params.extend(at_params) + else: + template[-1].params.extend(at_params) # A method outside of a class has multiple name segments multiple_name_segments = len(pqname.segments) > 1 @@ -2019,7 +2203,7 @@ class CxxParser: toks = self._consume_balanced_tokens(gtok) self.lex.return_tokens(toks[1:-1]) - fn_params, vararg = self._parse_parameters() + fn_params, vararg, _ = self._parse_parameters(False) assert not isinstance(dtype, FunctionType) dtype = dtype_fn = FunctionType(dtype, fn_params, vararg) @@ -2047,7 +2231,7 @@ class CxxParser: assert not isinstance(dtype, FunctionType) dtype = self._parse_array_type(aptok, dtype) elif aptok.type == "(": - fn_params, vararg = self._parse_parameters() + fn_params, vararg, _ = self._parse_parameters(False) # the type we already have is the return type of the function pointer assert not isinstance(dtype, FunctionType) diff --git a/cxxheaderparser/simple.py b/cxxheaderparser/simple.py index 3efb048..2538683 100644 --- a/cxxheaderparser/simple.py +++ b/cxxheaderparser/simple.py @@ -34,6 +34,7 @@ from dataclasses import dataclass, field from .types import ( ClassDecl, + Concept, EnumDecl, Field, ForwardDecl, @@ -113,6 +114,9 @@ class NamespaceScope: using_alias: typing.List[UsingAlias] = field(default_factory=list) ns_alias: typing.List[NamespaceAlias] = field(default_factory=list) + #: Concepts + concepts: typing.List[Concept] = field(default_factory=list) + #: Explicit template instantiations template_insts: typing.List[TemplateInst] = field(default_factory=list) @@ -243,6 +247,9 @@ class SimpleCxxVisitor: def on_namespace_end(self, state: SNamespaceBlockState) -> None: pass + def on_concept(self, state: SNonClassBlockState, concept: Concept) -> None: + state.user_data.concepts.append(concept) + def on_namespace_alias( self, state: SNonClassBlockState, alias: NamespaceAlias ) -> None: diff --git a/cxxheaderparser/types.py b/cxxheaderparser/types.py index b71ff6b..ebc15f9 100644 --- a/cxxheaderparser/types.py +++ b/cxxheaderparser/types.py @@ -454,12 +454,19 @@ class TemplateNonTypeParam: template ~~~~~~ + + // abbreviated template parameters are converted to this and param_idx is set + void fn(C auto p) + ~~~~~~ """ type: DecoratedType name: typing.Optional[str] = None default: typing.Optional[Value] = None + #: If this was promoted, the parameter index that this corresponds with + param_idx: typing.Optional[int] = None + #: Contains a ``...`` param_pack: bool = False @@ -513,6 +520,15 @@ class TemplateDecl: params: typing.List[TemplateParam] = field(default_factory=list) + # Currently don't interpret requires, if that changes in the future + # then this API will change. + + #: template requires ... + raw_requires_pre: typing.Optional[Value] = None + + #: template int main() requires ... + raw_requires_post: typing.Optional[Value] = None + #: If no template, this is None. This is a TemplateDecl if this there is a single #: declaration: @@ -551,6 +567,31 @@ class TemplateInst: doxygen: typing.Optional[str] = None +@dataclass +class Concept: + """ + Preliminary support for consuming headers that contain concepts, but + not trying to actually make sense of them at this time. If this is + something you care about, pull requests are welcomed! + + .. code-block:: c++ + + template + concept Meowable = is_meowable; + + template + concept Addable = requires (T x) { x + x; }; + """ + + template: TemplateDecl + name: str + + #: In the future this will be removed if we fully parse the expression + raw_constraint: Value + + doxygen: typing.Optional[str] = None + + @dataclass class ForwardDecl: """ diff --git a/cxxheaderparser/visitor.py b/cxxheaderparser/visitor.py index aca2b2a..df0129d 100644 --- a/cxxheaderparser/visitor.py +++ b/cxxheaderparser/visitor.py @@ -8,6 +8,7 @@ else: from .types import ( + Concept, EnumDecl, Field, ForwardDecl, @@ -89,6 +90,14 @@ class CxxVisitor(Protocol): Called when a ``namespace`` alias is encountered """ + def on_concept(self, state: NonClassBlockState, concept: Concept) -> None: + """ + .. code-block:: c++ + + template + concept Meowable = is_meowable; + """ + def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: """ Called when a forward declaration is encountered @@ -254,6 +263,9 @@ class NullVisitor: def on_namespace_end(self, state: NamespaceBlockState) -> None: return None + def on_concept(self, state: NonClassBlockState, concept: Concept) -> None: + return None + def on_namespace_alias( self, state: NonClassBlockState, alias: NamespaceAlias ) -> None: diff --git a/tests/test_abv_template.py b/tests/test_abv_template.py new file mode 100644 index 0000000..288c2d3 --- /dev/null +++ b/tests/test_abv_template.py @@ -0,0 +1,353 @@ +# Note: testcases generated via `python -m cxxheaderparser.gentest` +# +# Tests various aspects of abbreviated function templates +# + +from cxxheaderparser.simple import NamespaceScope, ParsedData, parse_string +from cxxheaderparser.types import ( + AutoSpecifier, + Function, + FundamentalSpecifier, + NameSpecifier, + PQName, + Parameter, + Pointer, + Reference, + TemplateDecl, + TemplateNonTypeParam, + Type, +) + + +def test_abv_template_f1() -> None: + content = """ + void f1(auto); // same as template void f1(T) + void f1p(auto p); + """ + 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="f1")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])) + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + param_idx=0, + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f1p")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + name="p", + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + param_idx=0, + ) + ] + ), + ), + ] + ) + ) + + +def test_abv_template_f2() -> None: + content = """ + void f2(C1 auto); // same as template void f2(T), if C1 is a concept + void f2p(C1 auto p); + """ + 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="f2")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])) + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C1")]) + ), + param_idx=0, + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f2p")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + name="p", + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C1")]) + ), + param_idx=0, + ) + ] + ), + ), + ] + ) + ) + + +def test_abv_template_f3() -> None: + content = """ + void f3(C2 auto...); // same as template void f3(Ts...), if C2 is a + // concept + void f3p(C2 auto p...); + """ + 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="f3")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + param_pack=True, + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C2")]) + ), + param_idx=0, + param_pack=True, + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f3p")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + name="p", + param_pack=True, + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C2")]) + ), + param_idx=0, + param_pack=True, + ) + ] + ), + ), + ] + ) + ) + + +def test_abv_template_f4() -> None: + content = """ + void f4(C2 auto, ...); // same as template void f4(T...), if C2 is a concept + void f4p(C2 auto p,...); + """ + 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="f4")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])) + ) + ], + vararg=True, + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C2")]) + ), + param_idx=0, + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f4p")]), + parameters=[ + Parameter( + type=Type(typename=PQName(segments=[AutoSpecifier()])), + name="p", + ) + ], + vararg=True, + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C2")]) + ), + param_idx=0, + ) + ] + ), + ), + ] + ) + ) + + +def test_abv_template_f5() -> None: + content = """ + void f5(const C3 auto *, C4 auto &); // same as template void f5(const T*, U&); + void f5p(const C3 auto * p1, C4 auto &p2); + """ + 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="f5")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=Type( + typename=PQName( + segments=[AutoSpecifier()], + ), + const=True, + ) + ) + ), + Parameter( + type=Reference( + ref_to=Type(typename=PQName(segments=[AutoSpecifier()])) + ) + ), + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName( + segments=[NameSpecifier(name="C3")] + ), + ), + param_idx=0, + ), + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C4")]) + ), + param_idx=1, + ), + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="void")]) + ), + name=PQName(segments=[NameSpecifier(name="f5p")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=Type( + typename=PQName( + segments=[AutoSpecifier()], + ), + const=True, + ) + ), + name="p1", + ), + Parameter( + type=Reference( + ref_to=Type(typename=PQName(segments=[AutoSpecifier()])) + ), + name="p2", + ), + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName( + segments=[NameSpecifier(name="C3")] + ), + ), + param_idx=0, + ), + TemplateNonTypeParam( + type=Type( + typename=PQName(segments=[NameSpecifier(name="C4")]) + ), + param_idx=1, + ), + ] + ), + ), + ] + ) + ) diff --git a/tests/test_concepts.py b/tests/test_concepts.py new file mode 100644 index 0000000..2be5a8c --- /dev/null +++ b/tests/test_concepts.py @@ -0,0 +1,811 @@ +from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string +from cxxheaderparser.tokfmt import Token +from cxxheaderparser.types import ( + AutoSpecifier, + ClassDecl, + Concept, + Function, + FundamentalSpecifier, + MoveReference, + NameSpecifier, + PQName, + Parameter, + TemplateArgument, + TemplateDecl, + TemplateNonTypeParam, + TemplateSpecialization, + TemplateTypeParam, + Type, + Value, + Variable, +) + + +def test_concept_basic_constraint() -> None: + content = """ + template + concept Derived = std::is_base_of::value; + + template T> void f(T); // T is constrained by Derived + """ + 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")]) + ) + ) + ], + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="Derived", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="Base" + ) + ] + ) + ) + ) + ] + ), + ) + ] + ) + ), + name="T", + ) + ] + ), + ) + ], + concepts=[ + Concept( + template=TemplateDecl( + params=[ + TemplateTypeParam(typekey="class", name="T"), + TemplateTypeParam(typekey="class", name="U"), + ] + ), + name="Derived", + raw_constraint=Value( + tokens=[ + Token(value="std"), + Token(value="::"), + Token(value="is_base_of"), + Token(value="<"), + Token(value="U"), + Token(value=","), + Token(value="T"), + Token(value=">"), + Token(value="::"), + Token(value="value"), + ] + ), + ) + ], + ) + ) + + +def test_concept_basic_constraint2() -> None: + content = """ + template constexpr bool is_meowable = true; + + template constexpr bool is_cat = true; + + template + concept Meowable = is_meowable; + + template + concept BadMeowableCat = is_meowable && is_cat; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="is_meowable")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="bool")]) + ), + value=Value(tokens=[Token(value="true")]), + constexpr=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="is_cat")]), + type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="bool")]) + ), + value=Value(tokens=[Token(value="true")]), + constexpr=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + ), + ], + concepts=[ + Concept( + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + name="Meowable", + raw_constraint=Value( + tokens=[ + Token(value="is_meowable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ), + Concept( + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + name="BadMeowableCat", + raw_constraint=Value( + tokens=[ + Token(value="is_meowable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="&&"), + Token(value="is_cat"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ), + ], + ) + ) + + +def test_concept_basic_requires() -> None: + content = """ + template + concept Hashable = requires(T a) { + { std::hash{}(a) } -> std::convertible_to; + }; + + template void f(T) {} + """ + 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")]) + ) + ) + ], + has_body=True, + template=TemplateDecl( + params=[ + TemplateNonTypeParam( + type=Type( + typename=PQName( + segments=[NameSpecifier(name="Hashable")] + ) + ), + name="T", + ) + ] + ), + ) + ], + concepts=[ + Concept( + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")] + ), + name="Hashable", + raw_constraint=Value( + tokens=[ + Token(value="requires"), + Token(value="("), + Token(value="T"), + Token(value="a"), + Token(value=")"), + Token(value="{"), + Token(value="{"), + Token(value="std"), + Token(value="::"), + Token(value="hash"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="{"), + Token(value="}"), + Token(value="("), + Token(value="a"), + Token(value=")"), + Token(value="}"), + Token(value="->"), + Token(value="std"), + Token(value="::"), + Token(value="convertible_to"), + Token(value="<"), + Token(value="std"), + Token(value="::"), + Token(value="size_t"), + Token(value=">"), + Token(value=";"), + Token(value="}"), + ] + ), + ) + ], + ) + ) + + +def test_concept_nested_requirements() -> None: + content = """ + template + concept Semiregular = DefaultConstructible && + CopyConstructible && CopyAssignable && Destructible && + requires(T a, std::size_t n) + { + requires Same; // nested: "Same<...> evaluates to true" + { a.~T() } noexcept; // compound: "a.~T()" is a valid expression that doesn't throw + requires Same; // nested: "Same<...> evaluates to true" + requires Same; // nested + { delete new T }; // compound + { delete new T[n] }; // compound + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + concepts=[ + Concept( + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")] + ), + name="Semiregular", + raw_constraint=Value( + tokens=[ + Token(value="DefaultConstructible"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="&&"), + Token(value="CopyConstructible"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="&&"), + Token(value="CopyAssignable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="&&"), + Token(value="Destructible"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="&&"), + Token(value="requires"), + Token(value="("), + Token(value="T"), + Token(value="a"), + Token(value=","), + Token(value="std"), + Token(value="::"), + Token(value="size_t"), + Token(value="n"), + Token(value=")"), + Token(value="{"), + Token(value="requires"), + Token(value="Same"), + Token(value="<"), + Token(value="T"), + Token(value="*"), + Token(value=","), + Token(value="decltype"), + Token(value="("), + Token(value="&"), + Token(value="a"), + Token(value=")"), + Token(value=">"), + Token(value=";"), + Token(value="{"), + Token(value="a"), + Token(value="."), + Token(value="~T"), + Token(value="("), + Token(value=")"), + Token(value="}"), + Token(value="noexcept"), + Token(value=";"), + Token(value="requires"), + Token(value="Same"), + Token(value="<"), + Token(value="T"), + Token(value="*"), + Token(value=","), + Token(value="decltype"), + Token(value="("), + Token(value="new"), + Token(value="T"), + Token(value=")"), + Token(value=">"), + Token(value=";"), + Token(value="requires"), + Token(value="Same"), + Token(value="<"), + Token(value="T"), + Token(value="*"), + Token(value=","), + Token(value="decltype"), + Token(value="("), + Token(value="new"), + Token(value="T"), + Token(value="["), + Token(value="n"), + Token(value="]"), + Token(value=")"), + Token(value=">"), + Token(value=";"), + Token(value="{"), + Token(value="delete"), + Token(value="new"), + Token(value="T"), + Token(value="}"), + Token(value=";"), + Token(value="{"), + Token(value="delete"), + Token(value="new"), + Token(value="T"), + Token(value="["), + Token(value="n"), + Token(value="]"), + Token(value="}"), + Token(value=";"), + Token(value="}"), + ] + ), + ) + ] + ) + ) + + +def test_concept_requires_class() -> None: + content = """ + // clang-format off + template + concept Number = std::integral || std::floating_point; + + template + requires Number + struct WrappedNumber {}; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="WrappedNumber")], + classkey="struct", + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="Number"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ), + ) + ) + ], + concepts=[ + Concept( + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")] + ), + name="Number", + raw_constraint=Value( + tokens=[ + Token(value="std"), + Token(value="::"), + Token(value="integral"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="||"), + Token(value="std"), + Token(value="::"), + Token(value="floating_point"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ) + ], + ) + ) + + +def test_requires_last_elem() -> None: + content = """ + template + void f(T&&) requires Eq; // can appear as the last element of a function declarator + """ + 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=MoveReference( + moveref_to=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ) + ) + ) + ], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")], + raw_requires_post=Value( + tokens=[ + Token(value="Eq"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ), + ) + ] + ) + ) + + +def test_requires_first_elem1() -> None: + content = """ + template requires Addable // or right after a template parameter list + T add(T a, T b) { return a + b; } + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name=PQName(segments=[NameSpecifier(name="add")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="a", + ), + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="b", + ), + ], + has_body=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="Addable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ), + ) + ] + ) + ) + + +def test_requires_first_elem2() -> None: + content = """ + template requires std::is_arithmetic_v + T add(T a, T b) { return a + b; } + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name=PQName(segments=[NameSpecifier(name="add")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="a", + ), + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="b", + ), + ], + has_body=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="std"), + Token(value="is_arithmetic_v"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ), + ) + ] + ) + ) + + +def test_requires_compound() -> None: + content = """ + template requires Addable || Subtractable + T add(T a, T b) { return a + b; } + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name=PQName(segments=[NameSpecifier(name="add")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="a", + ), + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="b", + ), + ], + has_body=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="Addable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="||"), + Token(value="Subtractable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ), + ) + ] + ) + ) + + +def test_requires_ad_hoc() -> None: + content = """ + template + requires requires (T x) { x + x; } // ad-hoc constraint, note keyword used twice + T add(T a, T b) { return a + b; } + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name=PQName(segments=[NameSpecifier(name="add")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="a", + ), + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="b", + ), + ], + has_body=True, + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="requires"), + Token(value="("), + Token(value="T"), + Token(value="x"), + Token(value=")"), + Token(value="{"), + Token(value="x"), + Token(value="+"), + Token(value="x"), + Token(value=";"), + Token(value="}"), + ] + ), + ), + ) + ] + ) + ) + + +def test_requires_both() -> None: + content = """ + // clang-format off + template + requires Addable + auto f1(T a, T b) requires Subtractable; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + functions=[ + Function( + return_type=Type(typename=PQName(segments=[AutoSpecifier()])), + name=PQName(segments=[NameSpecifier(name="f1")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="a", + ), + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ), + name="b", + ), + ], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")], + raw_requires_pre=Value( + tokens=[ + Token(value="Addable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + raw_requires_post=Value( + tokens=[ + Token(value="Subtractable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + ] + ), + ), + ) + ] + ) + ) + + +def test_requires_paren() -> None: + content = """ + // clang-format off + template + void h(T) requires (is_purrable()); + """ + 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="h")]), + parameters=[ + Parameter( + type=Type( + typename=PQName(segments=[NameSpecifier(name="T")]) + ) + ) + ], + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="T")], + raw_requires_post=Value( + tokens=[ + Token(value="("), + Token(value="is_purrable"), + Token(value="<"), + Token(value="T"), + Token(value=">"), + Token(value="("), + Token(value=")"), + Token(value=")"), + ] + ), + ), + ) + ] + ) + )