Skip to content

Commit 93673ff

Browse files
author
Kyle Kavanagh
committed
Add support for decltypes in method return types
1 parent d94f100 commit 93673ff

File tree

2 files changed

+139
-85
lines changed

2 files changed

+139
-85
lines changed

CppHeaderParser/CppHeaderParser.py

Lines changed: 61 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,17 @@ def _params_helper1(self, stack):
936936
elif a.startswith("__attribute__((__const__))"):
937937
stack = stack[6:]
938938

939-
stack = stack[stack.index("(") + 1 :]
939+
last_paren_index = len(stack) - stack[-1::-1].index(")") - 1
940+
open_paren_count = 1
941+
method_paren_start_idx = last_paren_index-1
942+
while open_paren_count > 0:
943+
if stack[method_paren_start_idx] == ")":
944+
open_paren_count+=1
945+
elif stack[method_paren_start_idx] == "(":
946+
open_paren_count-=1
947+
method_paren_start_idx -= 1
948+
949+
stack = stack[method_paren_start_idx + 2 :]
940950
if not stack:
941951
return []
942952
if (
@@ -998,33 +1008,6 @@ def __init__(self, nameStack, curClass, methinfo, curTemplate, doxygen, location
9981008
if doxygen:
9991009
self["doxygen"] = doxygen
10001010

1001-
# Remove leading keywords
1002-
for i, word in enumerate(nameStack):
1003-
if word not in Resolver.C_KEYWORDS:
1004-
nameStack = nameStack[i:]
1005-
break
1006-
1007-
if "operator" in nameStack:
1008-
rtnType = " ".join(nameStack[: nameStack.index("operator")])
1009-
self["name"] = "".join(
1010-
nameStack[nameStack.index("operator") : nameStack.index("(")]
1011-
)
1012-
else:
1013-
rtnType = " ".join(nameStack[: nameStack.index("(") - 1])
1014-
self["name"] = " ".join(
1015-
nameStack[nameStack.index("(") - 1 : nameStack.index("(")]
1016-
)
1017-
1018-
if len(rtnType) == 0 or self["name"] == curClass:
1019-
rtnType = "void"
1020-
1021-
self["rtnType"] = (
1022-
rtnType.replace(" :: ", "::")
1023-
.replace(" < ", "<")
1024-
.replace(" > ", "> ")
1025-
.replace(">>", "> >")
1026-
.replace(" ,", ",")
1027-
)
10281011

10291012
# deal with "noexcept" specifier/operator
10301013
self["noexcept"] = None
@@ -1064,6 +1047,8 @@ def __init__(self, nameStack, curClass, methinfo, curTemplate, doxygen, location
10641047
break
10651048

10661049
self.update(methinfo)
1050+
if len(self["rtnType"]) == 0 or self["name"] == curClass:
1051+
self["rtnType"] = "void"
10671052
set_location_info(self, location)
10681053

10691054
paramsStack = self._params_helper1(nameStack)
@@ -1304,6 +1289,7 @@ def __init__(self, nameStack, doxygen, location, **kwargs):
13041289
def _filter_name(self, name):
13051290
name = name.replace(" :", ":").replace(": ", ":")
13061291
name = name.replace(" < ", "<")
1292+
name = name.replace(" ( ", "(").replace(" ) ", ")")
13071293
name = name.replace(" > ", "> ").replace(">>", "> >")
13081294
name = name.replace(") >", ")>")
13091295
name = name.replace(" {", "{").replace(" }", "}")
@@ -2168,7 +2154,7 @@ def finalize(self):
21682154
}
21692155

21702156
def parse_method_type(self, stack):
2171-
trace_print("meth type info", stack)
2157+
debug_print("meth type info %s", stack)
21722158
info = {
21732159
"debug": " ".join(stack)
21742160
.replace(" :: ", "::")
@@ -2183,13 +2169,57 @@ def parse_method_type(self, stack):
21832169

21842170
info.update(self._method_type_defaults)
21852171

2186-
header = stack[: stack.index("(")]
2172+
last_paren_index = len(stack) - stack[-1::-1].index(")") - 1
2173+
open_paren_count = 1
2174+
method_paren_start_idx = last_paren_index-1
2175+
while open_paren_count > 0:
2176+
if stack[method_paren_start_idx] == ")":
2177+
open_paren_count+=1
2178+
elif stack[method_paren_start_idx] == "(":
2179+
open_paren_count-=1
2180+
method_paren_start_idx -= 1
2181+
2182+
header = stack[: method_paren_start_idx+1]
21872183
header = " ".join(header)
2184+
# Replace fields that would mess up our re-split below
21882185
header = header.replace(" :: ", "::")
21892186
header = header.replace(" < ", "<")
21902187
header = header.replace(" > ", "> ")
2188+
header = header.replace("> >", ">>")
21912189
header = header.replace("default ", "default")
21922190
header = header.strip()
2191+
# Remove leading keywords, splitting on spaces to avoid removing keywords embedded in other words
2192+
2193+
# Re-split to find method declarations like A::B::meth() that were formed by joining separate tokens
2194+
header = header.split()
2195+
name = header.pop()
2196+
for word in Resolver.C_KEYWORDS.union(set(ignoreSymbols)):
2197+
if word in header:
2198+
info[word] = True
2199+
header.remove(word)
2200+
header = " ".join(header)
2201+
# Now replace fields for aesthetics
2202+
header = header.replace(" (", "(")
2203+
header = header.replace("( ", "(")
2204+
header = header.replace(" )", ")")
2205+
header = header.replace(") ", ")")
2206+
header = header.replace(" ,", ",")
2207+
if "operator" in stack:
2208+
info["rtnType"] = " ".join(stack[: stack.index("operator")])
2209+
op = "".join(
2210+
stack[stack.index("operator")+1 : method_paren_start_idx+1]
2211+
)
2212+
if not op:
2213+
if " ".join(["operator", "(", ")", "("]) in " ".join(stack):
2214+
op = "()"
2215+
else:
2216+
debug_print("Error parsing operator")
2217+
return None
2218+
name = "operator"+op
2219+
info["operator"] = op
2220+
else:
2221+
info["rtnType"] = header
2222+
info["returns"] = info["rtnType"]
21932223

21942224
if stack[-1] == "{":
21952225
info["defined"] = True
@@ -2210,32 +2240,9 @@ def parse_method_type(self, stack):
22102240
elif stack[-2] == "delete":
22112241
info["deleted"] = True
22122242

2213-
r = header.split()
2214-
name = None
2215-
if "operator" in stack: # rare case op overload defined outside of class
2216-
op = stack[stack.index("operator") + 1 : stack.index("(")]
2217-
op = "".join(op)
2218-
if not op:
2219-
if " ".join(["operator", "(", ")", "("]) in " ".join(stack):
2220-
op = "()"
2221-
else:
2222-
trace_print("Error parsing operator")
2223-
return None
2224-
2225-
info["operator"] = op
2226-
name = "operator" + op
2227-
a = stack[: stack.index("operator")]
2228-
2229-
elif r:
2230-
name = r[-1]
2231-
a = r[:-1] # strip name
22322243

2233-
if name is None:
2234-
return None
22352244
# if name.startswith('~'): name = name[1:]
22362245

2237-
while a and a[0] == "}": # strip - can have multiple } }
2238-
a = a[1:]
22392246

22402247
if "::" in name:
22412248
# klass,name = name.split('::') # methods can be defined outside of class
@@ -2254,35 +2261,14 @@ def parse_method_type(self, stack):
22542261
info["defined"] = True
22552262
info["default"] = True
22562263
name = name[1:]
2257-
elif not a or (name == self.curClass and len(self.curClass)):
2264+
elif (name == self.curClass and len(self.curClass)):
22582265
info["constructor"] = True
22592266
if "default;" in stack:
22602267
info["defined"] = True
22612268
info["default"] = True
22622269

22632270
info["name"] = name
22642271

2265-
for tag in self.C_KEYWORDS:
2266-
if tag in a:
2267-
info[tag] = True
2268-
a.remove(tag) # inplace
2269-
if "template" in a:
2270-
a.remove("template")
2271-
b = " ".join(a)
2272-
if ">" in b:
2273-
info["template"] = b[: b.index(">") + 1]
2274-
info["returns"] = b[
2275-
b.index(">") + 1 :
2276-
] # find return type, could be incorrect... TODO
2277-
if "<typename" in info["template"].split():
2278-
typname = info["template"].split()[-1]
2279-
typname = typname[:-1] # strip '>'
2280-
if typname not in self._template_typenames:
2281-
self._template_typenames.append(typname)
2282-
else:
2283-
info["returns"] = " ".join(a)
2284-
else:
2285-
info["returns"] = " ".join(a)
22862272
info["returns"] = info["returns"].replace(" <", "<").strip()
22872273

22882274
## be careful with templates, do not count pointers inside template

test/test_CppHeaderParser.py

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,7 +1535,7 @@ def test_name_0(self):
15351535
def test_type_0(self):
15361536
self.assertEqual(
15371537
self.cppHeader.classes["DriverFuncs"]["properties"]["public"][0]["type"],
1538-
"void * ( * ) ( )",
1538+
"void *(* )()",
15391539
)
15401540

15411541
def test_function_pointer_field_0(self):
@@ -1555,7 +1555,7 @@ def test_name_1(self):
15551555
def test_type_1(self):
15561556
self.assertEqual(
15571557
self.cppHeader.classes["DriverFuncs"]["properties"]["public"][1]["type"],
1558-
"void ( * ) ( void * buf, int buflen )",
1558+
"void(* )(void * buf, int buflen )",
15591559
)
15601560

15611561
def test_function_pointer_field_1(self):
@@ -1631,7 +1631,7 @@ def setUp(self):
16311631
def test_rtn_type(self):
16321632
self.assertEqual(
16331633
self.cppHeader.classes["AlmondClass"]["methods"]["public"][0]["rtnType"],
1634-
"std::map<unsigned, std::pair<unsigned, SnailTemplateClass<SnailNamespace::SnailClass> > >",
1634+
"std::map<unsigned, std::pair<unsigned, SnailTemplateClass<SnailNamespace::SnailClass>>>",
16351635
)
16361636

16371637
def test_param_1_name(self):
@@ -2538,7 +2538,7 @@ def test_set_callback(self):
25382538
)
25392539
self.assertEqual(
25402540
self.cppHeader.functions[8]["parameters"][1]["type"],
2541-
"long ( * ) ( struct test_st *, int, const char *, int long, long, long )",
2541+
"long(* )(struct test_st *, int, const char *, int long, long, long )",
25422542
)
25432543

25442544

@@ -2851,8 +2851,8 @@ def test_using(self):
28512851
"desc": None,
28522852
"name": "",
28532853
"namespace": "std::",
2854-
"raw_type": "std::function<void ( )>",
2855-
"type": "function<void ( )>",
2854+
"raw_type": "std::function<void()>",
2855+
"type": "function<void()>",
28562856
"typealias": "VoidFunction",
28572857
"using_type": "typealias",
28582858
}
@@ -2909,11 +2909,11 @@ def test_fn(self):
29092909
"raw_type": "std::string",
29102910
},
29112911
{
2912-
"type": "function<void ( )>",
2912+
"type": "function<void()>",
29132913
"name": "fn",
29142914
"desc": None,
29152915
"namespace": "std::",
2916-
"raw_type": "std::function<void ( )>",
2916+
"raw_type": "std::function<void()>",
29172917
},
29182918
{
29192919
"type": "thing",
@@ -2946,11 +2946,11 @@ def test_class(self):
29462946
"raw_type": "std::string",
29472947
},
29482948
{
2949-
"type": "function<int ( )>",
2949+
"type": "function<int()>",
29502950
"name": "fn",
29512951
"desc": None,
29522952
"namespace": "std::",
2953-
"raw_type": "std::function<int ( )>",
2953+
"raw_type": "std::function<int()>",
29542954
},
29552955
{
29562956
"type": "thing",
@@ -4063,5 +4063,73 @@ def test_fn(self):
40634063
self.assertEqual(c.typedefs["mmmmp"], "typedef int ( * ) ( int , int )")
40644064

40654065

4066+
class DecltypeMethodReturnClass_meth1(unittest.TestCase):
4067+
def setUp(self):
4068+
self.cppHeader = CppHeaderParser.CppHeader(
4069+
"""
4070+
#include <vector>
4071+
#include <string>
4072+
using namespace std;
4073+
4074+
class DecltypeMethodReturnClass
4075+
{
4076+
public:
4077+
const int returnInt();
4078+
4079+
decltype(returnInt()) meth1(decltype(returnInt()) arg1);
4080+
4081+
const std::optional<decltype(returnInt())> meth2(const decltype(returnInt()) v1);
4082+
4083+
template<typename T>
4084+
decltype(T::Q) meth3(int v1);
4085+
4086+
};
4087+
""",
4088+
"string",
4089+
)
4090+
4091+
def test_name(self):
4092+
self.assertEqual(
4093+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][1]["name"],
4094+
"meth1",
4095+
)
4096+
self.assertEqual(
4097+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][2]["name"],
4098+
"meth2",
4099+
)
4100+
self.assertEqual(
4101+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][3]["name"],
4102+
"meth3",
4103+
)
4104+
4105+
def test_rtntype(self):
4106+
self.assertEqual(
4107+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][1]["rtnType"],
4108+
"decltype(returnInt())",
4109+
)
4110+
self.assertEqual(
4111+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][2]["rtnType"],
4112+
"const std::optional<decltype(returnInt())>",
4113+
)
4114+
self.assertEqual(
4115+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][3]["rtnType"],
4116+
"decltype(T::Q)",
4117+
)
4118+
4119+
def test_parameters(self):
4120+
self.assertEqual(
4121+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][1]["parameters"][0]['type'],
4122+
"decltype(returnInt() )",
4123+
)
4124+
self.assertEqual(
4125+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][2]["parameters"][0]['type'],
4126+
"const decltype(returnInt() )",
4127+
)
4128+
self.assertEqual(
4129+
self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][3]["parameters"][0]['type'],
4130+
"int",
4131+
)
4132+
4133+
40664134
if __name__ == "__main__":
40674135
unittest.main()

0 commit comments

Comments
 (0)