[cfe-commits] r94933 - in /cfe/trunk/bindings/python: clang/cindex.py tests/cindex/test_diagnostics.py
Daniel Dunbar
daniel at zuster.org
Sat Jan 30 15:59:02 PST 2010
Author: ddunbar
Date: Sat Jan 30 17:59:02 2010
New Revision: 94933
URL: http://llvm.org/viewvc/llvm-project?rev=94933&view=rev
Log:
cindex/Python: Add full support for Diagnostic and FixIt objects, available via TranslationUnit.diagnostics.
Several important FIXMEs remain:
- We aren't getting all the notes?
- There is still no way to get diagnostics for invalid inputs.
Added:
cfe/trunk/bindings/python/tests/cindex/test_diagnostics.py
Modified:
cfe/trunk/bindings/python/clang/cindex.py
Modified: cfe/trunk/bindings/python/clang/cindex.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/bindings/python/clang/cindex.py?rev=94933&r1=94932&r2=94933&view=diff
==============================================================================
--- cfe/trunk/bindings/python/clang/cindex.py (original)
+++ cfe/trunk/bindings/python/clang/cindex.py Sat Jan 30 17:59:02 2010
@@ -48,6 +48,10 @@
# TODO
# ====
#
+# o API support for invalid translation units. Currently we can't even get the
+# diagnostics on failure because they refer to locations in an object that
+# will have been invalidated.
+#
# o fix memory management issues (currently client must hold on to index and
# translation unit, or risk crashes).
#
@@ -145,6 +149,12 @@
("begin_int_data", c_uint),
("end_int_data", c_uint)]
+ # FIXME: Eliminate this and make normal constructor? Requires hiding ctypes
+ # object.
+ @staticmethod
+ def from_locations(start, end):
+ return SourceRange_getRange(start, end)
+
@property
def start(self):
"""
@@ -164,6 +174,44 @@
def __repr__(self):
return "<SourceRange start %r, end %r>" % (self.start, self.end)
+class Diagnostic(object):
+ """
+ A Diagnostic is a single instance of a Clang diagnostic. It includes the
+ diagnostic severity, the message, the location the diagnostic occurred, as
+ well as additional source ranges and associated fix-it hints.
+ """
+
+ Ignored = 0
+ Note = 1
+ Warning = 2
+ Error = 3
+ Fatal = 4
+
+ def __init__(self, severity, location, spelling, ranges, fixits):
+ self.severity = severity
+ self.location = location
+ self.spelling = spelling
+ self.ranges = ranges
+ self.fixits = fixits
+
+ def __repr__(self):
+ return "<Diagnostic severity %r, location %r, spelling %r>" % (
+ self.severity, self.location, self.spelling)
+
+class FixIt(object):
+ """
+ A FixIt represents a transformation to be applied to the source to
+ "fix-it". The fix-it shouldbe applied by replacing the given source range
+ with the given value.
+ """
+
+ def __init__(self, range, value):
+ self.range = range
+ self.value = value
+
+ def __repr__(self):
+ return "<FixIt range %r, value %r>" % (self.range, self.value)
+
### Cursor Kinds ###
class CursorKind(object):
@@ -455,7 +503,7 @@
children.append(child)
return 1 # continue
children = []
- Cursor_visit(self, Callback(visitor), children)
+ Cursor_visit(self, Cursor_visit_callback(visitor), children)
return iter(children)
@staticmethod
@@ -489,6 +537,109 @@
"""Helper for passing unsaved file arguments."""
_fields_ = [("name", c_char_p), ("contents", c_char_p), ('length', c_ulong)]
+## Diagnostic Conversion ##
+
+# Diagnostic objects are temporary, we must extract all the information from the
+# diagnostic object when it is passed to the callback.
+
+_clang_getDiagnosticSeverity = lib.clang_getDiagnosticSeverity
+_clang_getDiagnosticSeverity.argtypes = [c_object_p]
+_clang_getDiagnosticSeverity.restype = c_int
+
+_clang_getDiagnosticLocation = lib.clang_getDiagnosticLocation
+_clang_getDiagnosticLocation.argtypes = [c_object_p]
+_clang_getDiagnosticLocation.restype = SourceLocation
+
+_clang_getDiagnosticSpelling = lib.clang_getDiagnosticSpelling
+_clang_getDiagnosticSpelling.argtypes = [c_object_p]
+_clang_getDiagnosticSpelling.restype = _CXString
+_clang_getDiagnosticSpelling.errcheck = _CXString.from_result
+
+_clang_getDiagnosticRanges = lib.clang_getDiagnosticRanges
+_clang_getDiagnosticRanges.argtypes = [c_object_p,
+ POINTER(POINTER(SourceRange)),
+ POINTER(c_uint)]
+_clang_getDiagnosticRanges.restype = None
+
+_clang_disposeDiagnosticRanges = lib.clang_disposeDiagnosticRanges
+_clang_disposeDiagnosticRanges.argtypes = [POINTER(SourceRange), c_uint]
+_clang_disposeDiagnosticRanges.restype = None
+
+_clang_getDiagnosticNumFixIts = lib.clang_getDiagnosticNumFixIts
+_clang_getDiagnosticNumFixIts.argtypes = [c_object_p]
+_clang_getDiagnosticNumFixIts.restype = c_uint
+
+_clang_getDiagnosticFixItKind = lib.clang_getDiagnosticFixItKind
+_clang_getDiagnosticFixItKind.argtypes = [c_object_p, c_uint]
+_clang_getDiagnosticFixItKind.restype = c_int
+
+_clang_getDiagnosticFixItInsertion = lib.clang_getDiagnosticFixItInsertion
+_clang_getDiagnosticFixItInsertion.argtypes = [c_object_p, c_uint,
+ POINTER(SourceLocation)]
+_clang_getDiagnosticFixItInsertion.restype = _CXString
+_clang_getDiagnosticFixItInsertion.errcheck = _CXString.from_result
+
+_clang_getDiagnosticFixItRemoval = lib.clang_getDiagnosticFixItRemoval
+_clang_getDiagnosticFixItRemoval.argtypes = [c_object_p, c_uint,
+ POINTER(SourceLocation)]
+_clang_getDiagnosticFixItRemoval.restype = _CXString
+_clang_getDiagnosticFixItRemoval.errcheck = _CXString.from_result
+
+_clang_getDiagnosticFixItReplacement = lib.clang_getDiagnosticFixItReplacement
+_clang_getDiagnosticFixItReplacement.argtypes = [c_object_p, c_uint,
+ POINTER(SourceRange)]
+_clang_getDiagnosticFixItReplacement.restype = _CXString
+_clang_getDiagnosticFixItReplacement.errcheck = _CXString.from_result
+
+def _convert_fixit(diag_ptr, index):
+ # We normalize all the fix-its to a single representation, this is more
+ # convenient.
+ #
+ # FIXME: Push this back into API? It isn't exactly clear what the
+ # SourceRange semantics are, we should make sure we can represent an empty
+ # range.
+ kind = _clang_getDiagnosticFixItKind(diag_ptr, index)
+ range = None
+ value = None
+ if kind == 0: # insertion
+ location = SourceLocation()
+ value = _clang_getDiagnosticFixItInsertion(diag_ptr, index,
+ byref(location))
+ range = SourceRange.from_locations(location, location)
+ elif kind == 1: # removal
+ range = _clang_getDiagnosticFixItRemoval(diag_ptr, index)
+ value = ''
+ else: # replacement
+ assert kind == 2
+ range = SourceRange()
+ value = _clang_getDiagnosticFixItReplacement(diag_ptr, index,
+ byref(range))
+ return FixIt(range, value)
+
+def _convert_diag(diag_ptr, diag_list):
+ severity = _clang_getDiagnosticSeverity(diag_ptr)
+ loc = _clang_getDiagnosticLocation(diag_ptr)
+ spelling = _clang_getDiagnosticSpelling(diag_ptr)
+
+ # Diagnostic ranges.
+ #
+ # FIXME: Use getNum... based API?
+ num_ranges = c_uint()
+ ranges_array = POINTER(SourceRange)()
+ _clang_getDiagnosticRanges(diag_ptr, byref(ranges_array), byref(num_ranges))
+
+ # Copy the ranges array so we can dispose the original.
+ ranges = [SourceRange.from_buffer_copy(ranges_array[i])
+ for i in range(num_ranges.value)]
+ _clang_disposeDiagnosticRanges(ranges_array, num_ranges)
+
+ fixits = [_convert_fixit(diag_ptr, i)
+ for i in range(_clang_getDiagnosticNumFixIts(diag_ptr))]
+
+ diag_list.append(Diagnostic(severity, loc, spelling, ranges, fixits))
+
+###
+
class Index(ClangObject):
"""
The Index type provides the primary interface to the Clang CIndex library,
@@ -510,7 +661,11 @@
def read(self, path):
"""Load the translation unit from the given AST file."""
- ptr = TranslationUnit_read(self, path)
+ # FIXME: In theory, we could support streaming diagnostics. It's hard to
+ # integrate this into the API cleanly, however. Resolve.
+ diags = []
+ ptr = TranslationUnit_read(self, path,
+ Diagnostic_callback(_convert_diag), diags)
return TranslationUnit(ptr) if ptr else None
def parse(self, path, args = [], unsaved_files = []):
@@ -541,9 +696,13 @@
unsaved_files_array[i].name = name
unsaved_files_array[i].contents = value
unsaved_files_array[i].length = len(value)
+ # FIXME: In theory, we could support streaming diagnostics. It's hard to
+ # integrate this into the API cleanly, however. Resolve.
+ diags = []
ptr = TranslationUnit_parse(self, path, len(args), arg_array,
- len(unsaved_files), unsaved_files_array)
- return TranslationUnit(ptr) if ptr else None
+ len(unsaved_files), unsaved_files_array,
+ Diagnostic_callback(_convert_diag), diags)
+ return TranslationUnit(ptr, diags) if ptr else None
class TranslationUnit(ClangObject):
@@ -552,6 +711,10 @@
provides read-only access to its top-level declarations.
"""
+ def __init__(self, ptr, diagnostics):
+ ClangObject.__init__(self, ptr)
+ self.diagnostics = diagnostics
+
def __del__(self):
TranslationUnit_dispose(self)
@@ -583,9 +746,6 @@
# Additional Functions and Types
-# Wrap calls to TranslationUnit._load and Decl._load.
-Callback = CFUNCTYPE(c_int, Cursor, Cursor, py_object)
-
# String Functions
_CXString_dispose = lib.clang_disposeString
_CXString_dispose.argtypes = [_CXString]
@@ -601,6 +761,10 @@
POINTER(c_uint)]
# Source Range Functions
+SourceRange_getRange = lib.clang_getRange
+SourceRange_getRange.argtypes = [SourceLocation, SourceLocation]
+SourceRange_getRange.restype = SourceRange
+
SourceRange_start = lib.clang_getRangeStart
SourceRange_start.argtypes = [SourceRange]
SourceRange_start.restype = SourceLocation
@@ -675,8 +839,9 @@
Cursor_ref.restype = Cursor
Cursor_ref.errcheck = Cursor.from_result
+Cursor_visit_callback = CFUNCTYPE(c_int, Cursor, Cursor, py_object)
Cursor_visit = lib.clang_visitChildren
-Cursor_visit.argtypes = [Cursor, Callback, py_object]
+Cursor_visit.argtypes = [Cursor, Cursor_visit_callback, py_object]
Cursor_visit.restype = c_uint
# Index Functions
@@ -688,13 +853,17 @@
Index_dispose.argtypes = [Index]
# Translation Unit Functions
+Diagnostic_callback = CFUNCTYPE(None, c_object_p, py_object)
+
TranslationUnit_read = lib.clang_createTranslationUnit
-TranslationUnit_read.argtypes = [Index, c_char_p]
+TranslationUnit_read.argtypes = [Index, c_char_p,
+ Diagnostic_callback, py_object]
TranslationUnit_read.restype = c_object_p
TranslationUnit_parse = lib.clang_createTranslationUnitFromSourceFile
TranslationUnit_parse.argtypes = [Index, c_char_p, c_int, c_void_p,
- c_int, c_void_p]
+ c_int, c_void_p,
+ Diagnostic_callback, py_object]
TranslationUnit_parse.restype = c_object_p
TranslationUnit_cursor = lib.clang_getTranslationUnitCursor
@@ -722,4 +891,4 @@
###
__all__ = ['Index', 'TranslationUnit', 'Cursor', 'CursorKind',
- 'SourceRange', 'SourceLocation', 'File']
+ 'Diagnostic', 'FixIt', 'SourceRange', 'SourceLocation', 'File']
Added: cfe/trunk/bindings/python/tests/cindex/test_diagnostics.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/bindings/python/tests/cindex/test_diagnostics.py?rev=94933&view=auto
==============================================================================
--- cfe/trunk/bindings/python/tests/cindex/test_diagnostics.py (added)
+++ cfe/trunk/bindings/python/tests/cindex/test_diagnostics.py Sat Jan 30 17:59:02 2010
@@ -0,0 +1,48 @@
+from clang.cindex import *
+
+def tu_from_source(source):
+ index = Index.create()
+ tu = index.parse('INPUT.c', unsaved_files = [('INPUT.c', source)])
+ # FIXME: Remove the need for this.
+ tu.index = index
+ return tu
+
+# FIXME: We need support for invalid translation units to test better.
+
+def test_diagnostic_warning():
+ tu = tu_from_source("""int f0() {}\n""")
+ assert len(tu.diagnostics) == 1
+ assert tu.diagnostics[0].severity == Diagnostic.Warning
+ assert tu.diagnostics[0].location.line == 1
+ assert tu.diagnostics[0].location.column == 11
+ assert (tu.diagnostics[0].spelling ==
+ 'control reaches end of non-void function')
+
+def test_diagnostic_note():
+ # FIXME: We aren't getting notes here for some reason.
+ index = Index.create()
+ tu = tu_from_source("""#define A x\nvoid *A = 1;\n""")
+ assert len(tu.diagnostics) == 1
+ assert tu.diagnostics[0].severity == Diagnostic.Warning
+ assert tu.diagnostics[0].location.line == 2
+ assert tu.diagnostics[0].location.column == 7
+ assert 'incompatible' in tu.diagnostics[0].spelling
+# assert tu.diagnostics[1].severity == Diagnostic.Note
+# assert tu.diagnostics[1].location.line == 1
+# assert tu.diagnostics[1].location.column == 11
+# assert tu.diagnostics[1].spelling == 'instantiated from'
+
+def test_diagnostic_fixit():
+ index = Index.create()
+ tu = tu_from_source("""struct { int f0; } x = { f0 : 1 };""")
+ assert len(tu.diagnostics) == 1
+ assert tu.diagnostics[0].severity == Diagnostic.Warning
+ assert tu.diagnostics[0].location.line == 1
+ assert tu.diagnostics[0].location.column == 31
+ assert tu.diagnostics[0].spelling.startswith('use of GNU old-style')
+ assert len(tu.diagnostics[0].fixits) == 1
+ assert tu.diagnostics[0].fixits[0].range.start.line == 1
+ assert tu.diagnostics[0].fixits[0].range.start.column == 26
+ assert tu.diagnostics[0].fixits[0].range.end.line == 1
+ assert tu.diagnostics[0].fixits[0].range.end.column == 29
+ assert tu.diagnostics[0].fixits[0].value == '.f0 = '
More information about the cfe-commits
mailing list