Implement basic concept parsing

- requires clauses are collected into a Value and not interpreted at
  this time
This commit is contained in:
Dustin Spicuzza 2023-10-10 01:51:53 -04:00
parent e23c4db96d
commit e935959ad3
6 changed files with 479 additions and 1 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",

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(
@ -640,6 +641,8 @@ 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)
else:
self._parse_declarations(tok, doxygen, template)
@ -750,6 +753,32 @@ 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,
),
)
#
# Attributes
#

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

@ -551,6 +551,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:

403
tests/test_concepts.py Normal file
View File

@ -0,0 +1,403 @@
from cxxheaderparser.simple import NamespaceScope, ParsedData, parse_string
from cxxheaderparser.tokfmt import Token
from cxxheaderparser.types import (
Concept,
Function,
FundamentalSpecifier,
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="}"),
]
),
)
]
)
)