Add support for C++20 abbreviated function templates

Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
This commit is contained in:
Dustin Spicuzza 2023-10-10 04:05:34 -04:00
parent e935959ad3
commit 2957e70823
3 changed files with 429 additions and 18 deletions

View File

@ -606,9 +606,13 @@ class CxxParser:
lex.return_token(ptok) lex.return_token(ptok)
param = self._parse_template_type_parameter(tok, None) param = self._parse_template_type_parameter(tok, None)
else: else:
param = self._parse_parameter(ptok, TemplateNonTypeParam, ">") param, _ = self._parse_parameter(
ptok, TemplateNonTypeParam, False, ">"
)
else: else:
param = self._parse_parameter(tok, TemplateNonTypeParam, ">") param, _ = self._parse_parameter(
tok, TemplateNonTypeParam, concept_ok=False, end=">"
)
params.append(param) params.append(param)
@ -1644,23 +1648,43 @@ class CxxParser:
# #
def _parse_parameter( def _parse_parameter(
self, tok: typing.Optional[LexToken], cls: typing.Type[PT], end: str = ")" self,
) -> PT: 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 Parses a single parameter (excluding vararg parameters). Also used
to parse template non-type parameters to parse template non-type parameters
Returns parameter type, abbreviated template type
""" """
param_name = None param_name = None
default = None default = None
param_pack = False param_pack = False
parsed_type: typing.Optional[Type]
at_type: typing.Optional[Type] = None
# required typename + decorators if not tok:
parsed_type, mods = self._parse_type(tok) tok = self.lex.token()
if parsed_type is None:
raise self._parse_error(None)
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) dtype = self._parse_cv_ptr(parsed_type)
@ -1688,23 +1712,32 @@ class CxxParser:
if self.lex.token_if("="): if self.lex.token_if("="):
default = self._create_value(self._consume_value_until([], ",", end)) 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) param = cls(type=dtype, name=param_name, default=default, param_pack=param_pack)
self.debug_print("parameter: %s", param) 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 ( # starting at a (
# special case: zero parameters # special case: zero parameters
if self.lex.token_if(")"): if self.lex.token_if(")"):
return [], False return [], False, []
params: typing.List[Parameter] = [] params: typing.List[Parameter] = []
vararg = False vararg = False
at_params: typing.List[TemplateParam] = []
while True: while True:
if self.lex.token_if("ELLIPSIS"): if self.lex.token_if("ELLIPSIS"):
@ -1712,8 +1745,17 @@ class CxxParser:
self._next_token_must_be(")") self._next_token_must_be(")")
break break
param = self._parse_parameter(None, Parameter) param, at_type = self._parse_parameter(None, Parameter, concept_ok)
params.append(param) 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(",", ")") tok = self._next_token_must_be(",", ")")
if tok.value == ")": if tok.value == ")":
break break
@ -1728,7 +1770,7 @@ class CxxParser:
): ):
params = [] params = []
return params, vararg return params, vararg, at_params
_auto_return_typename = PQName([AutoSpecifier()]) _auto_return_typename = PQName([AutoSpecifier()])
@ -1875,7 +1917,16 @@ class CxxParser:
state.location = location state.location = location
is_class_block = isinstance(state, ClassBlockState) 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 # A method outside of a class has multiple name segments
multiple_name_segments = len(pqname.segments) > 1 multiple_name_segments = len(pqname.segments) > 1
@ -2048,7 +2099,7 @@ class CxxParser:
toks = self._consume_balanced_tokens(gtok) toks = self._consume_balanced_tokens(gtok)
self.lex.return_tokens(toks[1:-1]) 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) assert not isinstance(dtype, FunctionType)
dtype = dtype_fn = FunctionType(dtype, fn_params, vararg) dtype = dtype_fn = FunctionType(dtype, fn_params, vararg)
@ -2076,7 +2127,7 @@ class CxxParser:
assert not isinstance(dtype, FunctionType) assert not isinstance(dtype, FunctionType)
dtype = self._parse_array_type(aptok, dtype) dtype = self._parse_array_type(aptok, dtype)
elif aptok.type == "(": 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 # the type we already have is the return type of the function pointer
assert not isinstance(dtype, FunctionType) assert not isinstance(dtype, FunctionType)

View File

@ -454,12 +454,19 @@ class TemplateNonTypeParam:
template <auto T> template <auto T>
~~~~~~ ~~~~~~
// abbreviated template parameters are converted to this and param_idx is set
void fn(C auto p)
~~~~~~
""" """
type: DecoratedType type: DecoratedType
name: typing.Optional[str] = None name: typing.Optional[str] = None
default: typing.Optional[Value] = 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 ``...`` #: Contains a ``...``
param_pack: bool = False param_pack: bool = False

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,
),
]
),
),
]
)
)