Repository: robotpy/robotpy-cppheaderparser

Issue:
Regression in 3.3.0
I use to work with 3.2.0 release, but I have a regression when trying to update to latest revision. It seems a regression was introduced in the 3.3.0 release. Indeed, let ```header.hxx``` be the following header file:

```cpp
class A
{
public:
  A();

private:
  class A : public B<int,int>::C
  {
  };
};
```

Trying to parse with CppHeaderParser 3.3.0, I get the following issue:
```bash
$ python ess.py 
Traceback (most recent call last):
  File "/home/thomas/miniconda3/envs/bindgen/lib/python3.7/site-packages/CppHeaderParser/CppHeaderParser.py", line 2859, in __init__
    self._evaluate_stack()
  File "/home/thomas/miniconda3/envs/bindgen/lib/python3.7/site-packages/CppHeaderParser/CppHeaderParser.py", line 3329, in _evaluate_stack
    self._evaluate_class_stack()
  File "/home/thomas/miniconda3/envs/bindgen/lib/python3.7/site-packages/CppHeaderParser/CppHeaderParser.py", line 2488, in _evaluate_class_stack
    self.curAccessSpecifier,
  File "/home/thomas/miniconda3/envs/bindgen/lib/python3.7/site-packages/CppHeaderParser/CppHeaderParser.py", line 709, in __init__
    self["inherits"] = _parse_cpp_base(baseStack, defaultAccess)
  File "/home/thomas/miniconda3/envs/bindgen/lib/python3.7/site-packages/CppHeaderParser/CppHeaderParser.py", line 567, in _parse_cpp_base
    raise CppParseError("expected comma, found '%s'" % t)
CppHeaderParser.CppHeaderParser.CppParseError: expected comma, found '::'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "ess.py", line 3, in <module>
    CppHeaderParser.CppHeader('test.hxx')
  File "/home/thomas/miniconda3/envs/bindgen/lib/python3.7/site-packages/CppHeaderParser/CppHeaderParser.py", line 2994, in __init__
    CppParseError(msg), e,
  File "<string>", line 1, in raise_exc
CppHeaderParser.CppHeaderParser.CppParseError: Not able to parse test.hxx on line 8 evaluating '{': expected comma, found '::'
Error around: class A : public B < int , int > :: C
```

With 3.2.0, I have no exception raised, the output dict is:
```python
{'classes': {'A': {'nested_classes': [{'nested_classes': [], 'parent': 'A', 'abstract': False, 'namespace': '::A', 'final': False, 'name': 'A', 'filename': 'test.hxx', 'line_number': 7, 'inherits': [{'access': 'public', 'virtual': False, 'class': 'B<int'}, {'access': 'int', 'virtual': False, 'class': '>::C'}], 'methods': {'public': [], 'protected': [], 'private': []}, 'properties': {'public': [], 'protected': [], 'private': []}, 'enums': {'public': [], 'protected': [], 'private': []}, 'structs': {'public': [], 'protected': [], 'private': []}, 'typedefs': {'public': [], 'protected': [], 'private': []}, 'forward_declares': {'public': [], 'protected': [], 'private': []}, 'declaration_method': 'class'}], 'parent': None, 'abstract': False, 'namespace': '', 'final': False, 'name': 'A', 'filename': 'test.hxx', 'line_number': 1, 'inherits': [], 'methods': {'public': [{'rtnType': 'void', 'name': 'A', 'noexcept': None, 'const': False, 'final': False, 'override': False, 'debug': 'A ( ) ;', 'class': None, 'namespace': '', 'defined': False, 'pure_virtual': False, 'operator': False, 'constructor': True, 'destructor': False, 'extern': False, 'template': False, 'virtual': False, 'static': False, 'explicit': False, 'inline': False, 'friend': False, 'returns': '', 'returns_pointer': 0, 'returns_fundamental': True, 'returns_class': False, 'default': False, 'returns_reference': False, 'filename': 'test.hxx', 'line_number': 4, 'vararg': False, 'parameters': [], 'parent': {...}, 'path': 'A'}], 'protected': [], 'private': []}, 'properties': {'public': [], 'protected': [], 'private': []}, 'enums': {'public': [], 'protected': [], 'private': []}, 'structs': {'public': [], 'protected': [], 'private': []}, 'typedefs': {'public': [], 'protected': [], 'private': []}, 'forward_declares': {'public': [], 'protected': [], 'private': []}, 'declaration_method': 'class'}, 'A::A': {'nested_classes': [], 'parent': 'A', 'abstract': False, 'namespace': '::A', 'final': False, 'name': 'A', 'filename': 'test.hxx', 'line_number': 7, 'inherits': [{'access': 'public', 'virtual': False, 'class': 'B<int'}, {'access': 'int', 'virtual': False, 'class': '>::C'}], 'methods': {'public': [], 'protected': [], 'private': []}, 'properties': {'public': [], 'protected': [], 'private': []}, 'enums': {'public': [], 'protected': [], 'private': []}, 'structs': {'public': [], 'protected': [], 'private': []}, 'typedefs': {'public': [], 'protected': [], 'private': []}, 'forward_declares': {'public': [], 'protected': [], 'private': []}, 'declaration_method': 'class'}}, 'functions': [], 'enums': [], 'variables': []}
```

ping @rainman110

Discussion:

Patch:
diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py
index 9686d4e..56739b0 100644
--- a/CppHeaderParser/CppHeaderParser.py
+++ b/CppHeaderParser/CppHeaderParser.py
@@ -408,7 +408,7 @@ class CppBaseDecl(dict):
         if self["..."]:
             s += "..."
 
-        self["class"] = s
+        return s
 
 
 def _consume_parens(stack):
@@ -564,7 +564,14 @@ def _parse_cpp_base(stack, default_access):
             continue
 
         if require_ending:
-            raise CppParseError("expected comma, found '%s'" % t)
+            if t == "::":
+                if "decl_params" in base:
+                    base["decl_name"] = base._fix_classname()
+                    del base["decl_params"]
+                    base["..."]
+                require_ending = False
+            else:
+                raise CppParseError("expected comma, found '%s'" % t)
 
         if t == "(":
             s = stack[i:]
@@ -585,7 +592,7 @@ def _parse_cpp_base(stack, default_access):
     # backwards compat
     inherits.append(base)
     for base in inherits:
-        base._fix_classname()
+        base["class"] = base._fix_classname()
 
     return inherits
 


Test patch:
diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py
index f61acc2..d23cb23 100644
--- a/test/test_CppHeaderParser.py
+++ b/test/test_CppHeaderParser.py
@@ -3792,6 +3792,7 @@ struct Outer {
         self.assertEqual(props[0]["name"], "x")
         self.assertEqual(props[1]["name"], "y")
 
+
 class Deleted_TestCase(unittest.TestCase):
     def setUp(self):
         self.cppHeader = CppHeaderParser.CppHeader(
@@ -3809,5 +3810,33 @@ public:
         self.assertEqual(m["constructor"], True)
         self.assertEqual(m["deleted"], True)
 
+
+class BaseTemplateNs_TestCase(unittest.TestCase):
+    def setUp(self):
+        self.cppHeader = CppHeaderParser.CppHeader(
+            """
+class A : public B<int, int>::C {};
+""",
+            "string",
+        )
+
+    def test_fn(self):
+        c = self.cppHeader.classes["A"]
+        self.assertEqual("A", c["name"])
+        self.assertEqual(
+            [
+                {
+                    "access": "public",
+                    "class": "B<int,int>::C",
+                    "decl_name": "B<int,int>::C",
+                    "virtual": False,
+                    "...": False,
+                    "decltype": False,
+                }
+            ],
+            c["inherits"],
+        )
+
+
 if __name__ == "__main__":
     unittest.main()