Merge pull request #80 from robotpy/cpp20

Add various C++20 features
This commit is contained in:
Dustin Spicuzza 2023-10-13 02:37:46 -04:00 committed by GitHub
commit 9883a4e247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1431 additions and 19 deletions

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -454,12 +454,19 @@ class TemplateNonTypeParam:
template <auto T>
~~~~~~
// 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 <typename T> requires ...
raw_requires_pre: typing.Optional[Value] = None
#: template <typename T> 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 <class T>
concept Meowable = is_meowable<T>;
template<typename T>
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:
"""

View File

@ -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 <class T>
concept Meowable = is_meowable<T>;
"""
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:

353
tests/test_abv_template.py Normal file
View File

@ -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<class T> 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<C1 T> 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<C2... Ts> 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<C2 T> 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<C3 T, C4 U> 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,
),
]
),
),
]
)
)

811
tests/test_concepts.py Normal file
View File

@ -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 <class T, class U>
concept Derived = std::is_base_of<U, T>::value;
template <Derived<Base> T> void f(T); // T is constrained by Derived<T, Base>
"""
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 <class T> constexpr bool is_meowable = true;
template <class T> constexpr bool is_cat = true;
template <class T>
concept Meowable = is_meowable<T>;
template <class T>
concept BadMeowableCat = is_meowable<T> && is_cat<T>;
"""
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 <typename T>
concept Hashable = requires(T a) {
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
template <Hashable T> 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<class T>
concept Semiregular = DefaultConstructible<T> &&
CopyConstructible<T> && CopyAssignable<T> && Destructible<T> &&
requires(T a, std::size_t n)
{
requires Same<T*, decltype(&a)>; // nested: "Same<...> evaluates to true"
{ a.~T() } noexcept; // compound: "a.~T()" is a valid expression that doesn't throw
requires Same<T*, decltype(new T)>; // nested: "Same<...> evaluates to true"
requires Same<T*, decltype(new T[n])>; // 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 <typename T>
concept Number = std::integral<T> || std::floating_point<T>;
template <typename T>
requires Number<T>
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<typename T>
void f(T&&) requires Eq<T>; // 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<typename T> requires Addable<T> // 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<typename T> requires std::is_arithmetic_v<T>
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<typename T> requires Addable<T> || Subtractable<T>
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<typename T>
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<typename T>
requires Addable<T>
auto f1(T a, T b) requires Subtractable<T>;
"""
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<class T>
void h(T) requires (is_purrable<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="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=")"),
]
),
),
)
]
)
)