Repository: robotpy/robotpy-cppheaderparser

Issue:
Doesn't resolve namespaces for types imported via `using`
```
#include <string>
namespace a {
    using std::string;
    using VoidFunction = std::function<void()>;

    void fn(string &s, VoidFunction fn);
}
```
It needs to tack on the namespace to the string/VoidFunction types. This does not cover `using namespace foo`, as that isn't doable using the information available to CppHeaderParser.

Discussion:

Patch:
diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py
index 6bcd961..9de8d6a 100644
--- a/CppHeaderParser/CppHeaderParser.py
+++ b/CppHeaderParser/CppHeaderParser.py
@@ -377,6 +377,48 @@ def filter_out_attribute_keyword(stack):
         return stack
 
 
+_nhack = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
+
+
+def _split_namespace(namestack):
+    """
+        Given a list of name elements, find the namespace portion
+        and return that as a string
+
+        :rtype: Tuple[str, list]
+    """
+    last_colon = None
+    for i, n in enumerate(namestack):
+        if n == ":":
+            last_colon = i
+        if i and n != ":" and not _nhack.match(n):
+            break
+
+    if last_colon:
+        ns, namestack = (
+            "".join(namestack[: last_colon + 1]),
+            namestack[last_colon + 1 :],
+        )
+    else:
+        ns = ""
+
+    return ns, namestack
+
+
+def _iter_ns_str_reversed(namespace):
+    """
+        Take a namespace string, and yield successively smaller portions
+        of it (ex foo::bar::baz::, foo::bar::, foo::). The last item yielded
+        will always be an empty string
+    """
+    if namespace:
+        parts = namespace.split("::")
+        for i in range(len(parts) - 1, 0, -1):
+            yield "::".join(parts[:i]) + "::"
+
+    yield ""
+
+
 class TagStr(str):
     """Wrapper for a string that allows us to store the line number associated with it"""
 
@@ -1577,6 +1619,19 @@ class Resolver(object):
                 result["fundamental"] = False
                 result["class"] = klass
                 result["unresolved"] = False
+            elif self.using:
+                # search for type in all enclosing namespaces
+                for ns in _iter_ns_str_reversed(result.get("namespace", "")):
+                    nsalias = ns + alias
+                    used = self.using.get(nsalias)
+                    if used:
+                        for i in ("type", "namespace", "ctypes_type", "raw_type"):
+                            if i in used:
+                                result[i] = used[i]
+                        result["unresolved"] = False
+                        break
+                else:
+                    result["unresolved"] = True
             else:
                 result["unresolved"] = True
         else:
@@ -2567,6 +2622,10 @@ class CppHeader(_CppHeader):
         self.anon_union_counter = [-1, 0]
         self.templateRegistry = []
 
+        #: Using directives in this header: key is full name for lookup, value
+        #: is :class:`.CppVariable`
+        self.using = {}
+
         if len(self.headerFileName):
             fd = io.open(self.headerFileName, "r", encoding=encoding)
             headerFileStr = "".join(fd.readlines())
@@ -3035,12 +3094,25 @@ class CppHeader(_CppHeader):
             len(self.nameStack) == 2 and self.nameStack[0] == "friend"
         ):  # friend class declaration
             pass
-        elif (
-            len(self.nameStack) >= 2
-            and self.nameStack[0] == "using"
-            and self.nameStack[1] == "namespace"
-        ):
-            pass  # TODO
+        elif len(self.nameStack) >= 2 and self.nameStack[0] == "using":
+            if self.nameStack[1] == "namespace":
+                pass
+            else:
+                if len(self.nameStack) > 3 and self.nameStack[2] == "=":
+                    # using foo = ns::bar
+                    alias = self.nameStack[1]
+                    ns, stack = _split_namespace(self.nameStack[3:])
+                    atype = CppVariable(stack)
+                else:
+                    # using foo::bar
+                    ns, stack = _split_namespace(self.nameStack[1:])
+                    atype = CppVariable(stack)
+                    alias = atype["type"]
+
+                atype["namespace"] = ns
+                atype["raw_type"] = ns + atype["type"]
+                alias = self.current_namespace() + alias
+                self.using[alias] = atype
 
         elif is_enum_namestack(self.nameStack):
             debug_print("trace")


Test patch:
diff --git a/CppHeaderParser/test/test_CppHeaderParser.py b/CppHeaderParser/test/test_CppHeaderParser.py
index c877c85..2769419 100644
--- a/CppHeaderParser/test/test_CppHeaderParser.py
+++ b/CppHeaderParser/test/test_CppHeaderParser.py
@@ -5,11 +5,14 @@ import sys
 import CppHeaderParser as CppHeaderParser
 
 
-def filter_pameters(p):
+def filter_pameters(p, extra=[]):
     "Reduce a list of dictionaries to the desired keys for function parameter testing"
     rtn = []
     for d in p:
-        rtn.append({"name": d["name"], "desc": d["desc"], "type": d["type"]})
+        rd = {}
+        for k in ["name", "desc", "type"] + extra:
+            rd[k] = d.get(k)
+        rtn.append(rd)
     return rtn
 
 
@@ -2759,5 +2762,58 @@ class VarargFunc_TestCase(unittest.TestCase):
         self.assertEqual(len(vf["parameters"]), len(nvf["parameters"]))
 
 
+class UsingNamespace_TestCase(unittest.TestCase):
+    def setUp(self):
+        self.cppHeader = CppHeaderParser.CppHeader(
+            """
+using std::thing;
+namespace a {
+    using std::string;
+    using VoidFunction = std::function<void()>;
+
+    void fn(string &s, VoidFunction fn, thing * t);
+}
+""",
+            "string",
+        )
+
+    def test_using(self):
+        self.assertIn("a::string", self.cppHeader.using)
+        self.assertIn("a::VoidFunction", self.cppHeader.using)
+        self.assertIn("thing", self.cppHeader.using)
+
+    def test_fn(self):
+        self.maxDiff = None
+        self.assertEqual(len(self.cppHeader.functions), 1)
+        fn = self.cppHeader.functions[0]
+        self.assertEqual(fn["name"], "fn")
+        self.assertEqual(
+            filter_pameters(fn["parameters"], ["namespace", "raw_type"]),
+            [
+                {
+                    "type": "string",
+                    "name": "s",
+                    "desc": None,
+                    "namespace": "std::",
+                    "raw_type": "std::string",
+                },
+                {
+                    "type": "function<void ( )>",
+                    "name": "fn",
+                    "desc": None,
+                    "namespace": "std::",
+                    "raw_type": "std::function<void ( )>",
+                },
+                {
+                    "type": "thing",
+                    "name": "t",
+                    "desc": None,
+                    "namespace": "std::",
+                    "raw_type": "std::thing",
+                },
+            ],
+        )
+
+
 if __name__ == "__main__":
     unittest.main()