>From 8b4b092529b5c999ed52fdb607ec17fed19ecdc8 Mon Sep 17 00:00:00 2001
From: Gregory Szorc <gregory.szorc@gmail.com>
Date: Sun, 19 Feb 2012 09:48:11 -0800
Subject: [PATCH 2/4] [clang.py] Implement Type.argument_types()

---
 bindings/python/clang/cindex.py           |   48 ++++++
 bindings/python/tests/cindex/test_type.py |  242 ++++++++++++++++-------------
 2 files changed, 183 insertions(+), 107 deletions(-)

diff --git a/bindings/python/clang/cindex.py b/bindings/python/clang/cindex.py
index e2ef3c3..1594857 100644
--- a/bindings/python/clang/cindex.py
+++ b/bindings/python/clang/cindex.py
@@ -58,16 +58,17 @@ call is efficient.
 # o expose code completion APIs.
 #
 # o cleanup ctypes wrapping, would be nice to separate the ctypes details more
 #   clearly, and hide from the external interface (i.e., help(cindex)).
 #
 # o implement additional SourceLocation, SourceRange, and File methods.
 
 from ctypes import *
+import collections
 
 def get_cindex_library():
     # FIXME: It's probably not the case that the library is actually found in
     # this location. We need a better system of identifying and loading the
     # CIndex library. It could be on path or elsewhere, or versioned, etc.
     import platform
     name = platform.system()
     if name == 'Darwin':
@@ -1132,16 +1133,54 @@ class Type(Structure):
     """
     _fields_ = [("_kind_id", c_int), ("data", c_void_p * 2)]
 
     @property
     def kind(self):
         """Return the kind of this type."""
         return TypeKind.from_id(self._kind_id)
 
+    def argument_types(self):
+        """Retrieve a container for the non-variadic arguments for this type.
+
+        The returned object is iterable and indexable. Each item in the
+        container is a Type instance.
+        """
+        class ArgumentsIterator(collections.Sequence):
+            def __init__(self, parent):
+                self.parent = parent
+                self.length = None
+
+            def __len__(self):
+                if self.length is None:
+                    self.length = Type_get_num_arg_types(self.parent)
+
+                return self.length
+
+            def __getitem__(self, key):
+                # FIXME Support slice objects.
+                if not isinstance(key, int):
+                    raise TypeError("Must supply a non-negative int.")
+
+                if key < 0:
+                    raise IndexError("Only non-negative indexes are accepted.")
+
+                if key >= len(self):
+                    raise IndexError("Index greater than container length: "
+                                     "%d > %d" % ( key, len(self) ))
+
+                result = Type_get_arg_type(self.parent, key)
+                if result.kind == TypeKind.INVALID:
+                    raise IndexError("Argument could not be retrieved.")
+
+                return result
+
+        assert self.kind == TypeKind.FUNCTIONPROTO
+        return ArgumentsIterator(self)
+
     @property
     def element_type(self):
         """Retrieve the Type of elements within this Type.
 
         If accessed on a type that is not an array, complex, or vector type, an
         exception will be raised.
         """
         result = Type_get_element_type(self)
@@ -1922,17 +1961,26 @@ Type_get_declaration.argtypes = [Type]
 Type_get_declaration.restype = Cursor
 Type_get_declaration.errcheck = Cursor.from_result
 
 Type_get_result = lib.clang_getResultType
 Type_get_result.argtypes = [Type]
 Type_get_result.restype = Type
 Type_get_result.errcheck = Type.from_result
 
+Type_get_num_arg_types = lib.clang_getNumArgTypes
+Type_get_num_arg_types.argtypes = [Type]
+Type_get_num_arg_types.restype = c_uint
+
+Type_get_arg_type = lib.clang_getArgType
+Type_get_arg_type.argtypes = [Type, c_uint]
+Type_get_arg_type.restype = Type
+Type_get_arg_type.errcheck = Type.from_result
 Type_get_element_type = lib.clang_getElementType
+
 Type_get_element_type.argtypes = [Type]
 Type_get_element_type.restype = Type
 Type_get_element_type.errcheck = Type.from_result
 
 Type_get_num_elements = lib.clang_getNumElements
 Type_get_num_elements.argtypes = [Type]
 Type_get_num_elements.restype = c_longlong
 
diff --git a/bindings/python/tests/cindex/test_type.py b/bindings/python/tests/cindex/test_type.py
index b07ef64..86b8142 100644
--- a/bindings/python/tests/cindex/test_type.py
+++ b/bindings/python/tests/cindex/test_type.py
@@ -25,129 +25,176 @@ def get_tu(source=kInput, lang='c'):
     if lang == 'cpp':
         name = 't.cpp'
 
     index = Index.create()
     tu = index.parse(name, unsaved_files=[(name, source)])
     assert tu is not None
     return tu
 
-def test_a_struct():
-    tu = get_tu(kInput)
-
-    for n in tu.cursor.get_children():
-        if n.spelling == 'teststruct':
-            fields = list(n.get_children())
-
-            assert all(x.kind == CursorKind.FIELD_DECL for x in fields)
-
-            assert fields[0].spelling == 'a'
-            assert not fields[0].type.is_const_qualified()
-            assert fields[0].type.kind == TypeKind.INT
-            assert fields[0].type.get_canonical().kind == TypeKind.INT
-
-            assert fields[1].spelling == 'b'
-            assert not fields[1].type.is_const_qualified()
-            assert fields[1].type.kind == TypeKind.TYPEDEF
-            assert fields[1].type.get_canonical().kind == TypeKind.INT
-            assert fields[1].type.get_declaration().spelling == 'I'
-
-            assert fields[2].spelling == 'c'
-            assert not fields[2].type.is_const_qualified()
-            assert fields[2].type.kind == TypeKind.LONG
-            assert fields[2].type.get_canonical().kind == TypeKind.LONG
-
-            assert fields[3].spelling == 'd'
-            assert not fields[3].type.is_const_qualified()
-            assert fields[3].type.kind == TypeKind.ULONG
-            assert fields[3].type.get_canonical().kind == TypeKind.ULONG
-
-            assert fields[4].spelling == 'e'
-            assert not fields[4].type.is_const_qualified()
-            assert fields[4].type.kind == TypeKind.LONG
-            assert fields[4].type.get_canonical().kind == TypeKind.LONG
-
-            assert fields[5].spelling == 'f'
-            assert fields[5].type.is_const_qualified()
-            assert fields[5].type.kind == TypeKind.INT
-            assert fields[5].type.get_canonical().kind == TypeKind.INT
-
-            assert fields[6].spelling == 'g'
-            assert not fields[6].type.is_const_qualified()
-            assert fields[6].type.kind == TypeKind.POINTER
-            assert fields[6].type.get_pointee().kind == TypeKind.INT
-
-            assert fields[7].spelling == 'h'
-            assert not fields[7].type.is_const_qualified()
-            assert fields[7].type.kind == TypeKind.POINTER
-            assert fields[7].type.get_pointee().kind == TypeKind.POINTER
-            assert fields[7].type.get_pointee().get_pointee().kind == TypeKind.POINTER
-            assert fields[7].type.get_pointee().get_pointee().get_pointee().kind == TypeKind.INT
+def get_cursor(tu, spelling):
+    for cursor in tu.cursor.get_children():
+        if cursor.spelling == spelling:
+            return cursor
 
-            break
+    return None
 
-    else:
-        assert False, "Didn't find teststruct??"
+def test_a_struct():
+    tu = get_tu(kInput)
 
+    teststruct = get_cursor(tu, 'teststruct')
+    assert teststruct is not None, "Could not find teststruct."
+    fields = list(teststruct.get_children())
+    assert all(x.kind == CursorKind.FIELD_DECL for x in fields)
+
+    assert fields[0].spelling == 'a'
+    assert not fields[0].type.is_const_qualified()
+    assert fields[0].type.kind == TypeKind.INT
+    assert fields[0].type.get_canonical().kind == TypeKind.INT
+
+    assert fields[1].spelling == 'b'
+    assert not fields[1].type.is_const_qualified()
+    assert fields[1].type.kind == TypeKind.TYPEDEF
+    assert fields[1].type.get_canonical().kind == TypeKind.INT
+    assert fields[1].type.get_declaration().spelling == 'I'
+
+    assert fields[2].spelling == 'c'
+    assert not fields[2].type.is_const_qualified()
+    assert fields[2].type.kind == TypeKind.LONG
+    assert fields[2].type.get_canonical().kind == TypeKind.LONG
+
+    assert fields[3].spelling == 'd'
+    assert not fields[3].type.is_const_qualified()
+    assert fields[3].type.kind == TypeKind.ULONG
+    assert fields[3].type.get_canonical().kind == TypeKind.ULONG
+
+    assert fields[4].spelling == 'e'
+    assert not fields[4].type.is_const_qualified()
+    assert fields[4].type.kind == TypeKind.LONG
+    assert fields[4].type.get_canonical().kind == TypeKind.LONG
+
+    assert fields[5].spelling == 'f'
+    assert fields[5].type.is_const_qualified()
+    assert fields[5].type.kind == TypeKind.INT
+    assert fields[5].type.get_canonical().kind == TypeKind.INT
+
+    assert fields[6].spelling == 'g'
+    assert not fields[6].type.is_const_qualified()
+    assert fields[6].type.kind == TypeKind.POINTER
+    assert fields[6].type.get_pointee().kind == TypeKind.INT
+
+    assert fields[7].spelling == 'h'
+    assert not fields[7].type.is_const_qualified()
+    assert fields[7].type.kind == TypeKind.POINTER
+    assert fields[7].type.get_pointee().kind == TypeKind.POINTER
+    assert fields[7].type.get_pointee().get_pointee().kind == TypeKind.POINTER
+    assert fields[7].type.get_pointee().get_pointee().get_pointee().kind == TypeKind.INT
 
 constarrayInput="""
 struct teststruct {
   void *A[2];
 };
 """
 def testConstantArray():
     tu = get_tu(constarrayInput)
 
-    for n in tu.cursor.get_children():
-        if n.spelling == 'teststruct':
-            fields = list(n.get_children())
-            assert fields[0].spelling == 'A'
-            assert fields[0].type.kind == TypeKind.CONSTANTARRAY
-            assert fields[0].type.get_array_element_type() is not None
-            assert fields[0].type.get_array_element_type().kind == TypeKind.POINTER
-            assert fields[0].type.get_array_size() == 2
-
-            break
-    else:
-        assert False, "Didn't find teststruct??"
+    teststruct = get_cursor(tu, 'teststruct')
+    assert teststruct is not None, "Didn't find teststruct??"
+    fields = list(teststruct.get_children())
+    assert fields[0].spelling == 'A'
+    assert fields[0].type.kind == TypeKind.CONSTANTARRAY
+    assert fields[0].type.get_array_element_type() is not None
+    assert fields[0].type.get_array_element_type().kind == TypeKind.POINTER
+    assert fields[0].type.get_array_size() == 2
 
 def test_equal():
     """Ensure equivalence operators work on Type."""
     source = 'int a; int b; void *v;'
     tu = get_tu(source)
 
-    a, b, v = None, None, None
-
-    for cursor in tu.cursor.get_children():
-        if cursor.spelling == 'a':
-            a = cursor
-        elif cursor.spelling == 'b':
-            b = cursor
-        elif cursor.spelling == 'v':
-            v = cursor
+    a = get_cursor(tu, 'a')
+    b = get_cursor(tu, 'b')
+    v = get_cursor(tu, 'v')
 
     assert a is not None
     assert b is not None
     assert v is not None
 
     assert a.type == b.type
     assert a.type != v.type
 
     assert a.type != None
     assert a.type != 'foo'
 
+def test_function_argument_types():
+    """Ensure that Type.argument_types() works as expected."""
+    tu = get_tu('void f(int, int);')
+    f = get_cursor(tu, 'f')
+    assert f is not None
+
+    args = f.type.argument_types()
+    assert args is not None
+    assert len(args) == 2
+
+    t0 = args[0]
+    assert t0 is not None
+    assert t0.kind == TypeKind.INT
+
+    t1 = args[1]
+    assert t1 is not None
+    assert t1.kind == TypeKind.INT
+
+    args2 = list(args)
+    assert len(args2) == 2
+    assert t0 == args2[0]
+    assert t1 == args2[1]
+
+@raises(TypeError)
+def test_argument_types_string_key():
+    """Ensure that non-int keys raise a TypeError."""
+    tu = get_tu('void f(int, int);')
+    f = get_cursor(tu, 'f')
+    assert f is not None
+
+    args = f.type.argument_types()
+    assert len(args) == 2
+
+    args['foo']
+
+@raises(IndexError)
+def test_argument_types_negative_index():
+    """Ensure that negative indexes on argument_types Raises an IndexError."""
+    tu = get_tu('void f(int, int);')
+    f = get_cursor(tu, 'f')
+    args = f.type.argument_types()
+
+    args[-1]
+
+@raises(IndexError)
+def test_argument_types_overflow_index():
+    """Ensure that indexes beyond the length of Type.argument_types() raise."""
+    tu = get_tu('void f(int, int);')
+    f = get_cursor(tu, 'f')
+    args = f.type.argument_types()
+
+    args[2]
+
+@raises(Exception)
+def test_argument_types_invalid_type():
+    """Ensure that obtaining argument_types on a Type without them raises."""
+    tu = get_tu('int i;')
+    i = get_cursor(tu, 'i')
+    assert i is not None
+
+    i.type.argument_types()
+
 def test_is_pod():
     tu = get_tu('int i; void f();')
-    i, f = None, None
-
-    for cursor in tu.cursor.get_children():
-        if cursor.spelling == 'i':
-            i = cursor
-        elif cursor.spelling == 'f':
-            f = cursor
+    i = get_cursor(tu, 'i')
+    f = get_cursor(tu, 'f')
 
     assert i is not None
     assert f is not None
 
     assert i.type.is_pod()
     assert not f.type.is_pod()
 
 def test_function_variadic():
@@ -156,65 +203,46 @@ def test_function_variadic():
     source ="""
 #include <stdarg.h>
 
 void foo(int a, ...);
 void bar(int a, int b);
 """
 
     tu = get_tu(source)
-    foo, bar = None, None
-    for cursor in tu.cursor.get_children():
-        if cursor.spelling == 'foo':
-            foo = cursor
-        elif cursor.spelling == 'bar':
-            bar = cursor
+    foo = get_cursor(tu, 'foo')
+    bar = get_cursor(tu, 'bar')
 
     assert foo is not None
     assert bar is not None
 
     assert isinstance(foo.type.is_function_variadic(), bool)
     assert foo.type.is_function_variadic()
     assert not bar.type.is_function_variadic()
 
 def test_element_type():
     tu = get_tu('int i[5];')
-    i = None
-    for cursor in tu.cursor.get_children():
-        if cursor.spelling == 'i':
-            i = cursor
-            break
-
+    i = get_cursor(tu, 'i')
     assert i is not None
 
     assert i.type.kind == TypeKind.CONSTANTARRAY
     assert i.type.element_type.kind == TypeKind.INT
 
 @raises(Exception)
 def test_invalid_element_type():
     """Ensure Type.element_type raises if type doesn't have elements."""
     tu = get_tu('int i;')
-
-    i = None
-    for cursor in tu.cursor.get_children():
-        if cursor.spelling == 'i':
-            i = cursor
-            break
-
+    i = get_cursor(tu, 'i')
     assert i is not None
     i.element_type
 
 def test_element_count():
     tu = get_tu('int i[5]; int j;')
-
-    for cursor in tu.cursor.get_children():
-        if cursor.spelling == 'i':
-            i = cursor
-        elif cursor.spelling == 'j':
-            j = cursor
+    i = get_cursor(tu, 'i')
+    j = get_cursor(tu, 'j')
 
     assert i is not None
     assert j is not None
 
     assert i.type.element_count == 5
 
     try:
         j.type.element_count
-- 
1.7.8.2

