Add support for C++20 abbreviated function templates
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
This commit is contained in:
parent
e935959ad3
commit
2957e70823
@ -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,17 +1648,32 @@ 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
|
||||||
|
|
||||||
|
if not tok:
|
||||||
|
tok = self.lex.token()
|
||||||
|
|
||||||
|
# placeholder type, skip typename
|
||||||
|
if tok.type == "auto":
|
||||||
|
at_type = parsed_type = Type(PQName([AutoSpecifier()]))
|
||||||
|
else:
|
||||||
# required typename + decorators
|
# required typename + decorators
|
||||||
parsed_type, mods = self._parse_type(tok)
|
parsed_type, mods = self._parse_type(tok)
|
||||||
if parsed_type is None:
|
if parsed_type is None:
|
||||||
@ -1662,6 +1681,11 @@ class CxxParser:
|
|||||||
|
|
||||||
mods.validate(var_ok=False, meth_ok=False, msg="parsing parameter")
|
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)
|
||||||
|
|
||||||
# optional parameter pack
|
# optional parameter pack
|
||||||
@ -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)
|
||||||
|
@ -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
353
tests/test_abv_template.py
Normal 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,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user