4343Most object information is exposed using properties, when the underlying API
4444call is efficient.
4545"""
46- from __future__ import absolute_import , division , print_function
46+ from __future__ import annotations
4747
4848# TODO
4949# ====
6666
6767import collections .abc
6868import os
69+ import sys
6970from enum import Enum
7071
72+ from typing import (
73+ Any ,
74+ Callable ,
75+ Generic ,
76+ Optional ,
77+ Type as TType ,
78+ TypeVar ,
79+ TYPE_CHECKING ,
80+ Union as TUnion ,
81+ )
82+ from typing_extensions import Protocol , TypeAlias
83+
84+ if TYPE_CHECKING :
85+ from ctypes import _Pointer
86+
87+ StrPath : TypeAlias = TUnion [str , os .PathLike [str ]]
88+ LibFunc : TypeAlias = TUnion [
89+ "tuple[str, Optional[list[Any]]]" ,
90+ "tuple[str, Optional[list[Any]], Any]" ,
91+ "tuple[str, Optional[list[Any]], Any, Callable[..., Any]]" ,
92+ ]
93+ CObjP : TypeAlias = _Pointer [Any ]
94+
95+ TSeq = TypeVar ("TSeq" , covariant = True )
96+
97+ class NoSliceSequence (Protocol [TSeq ]):
98+ def __len__ (self ) -> int : ...
99+ def __getitem__ (self , key : int ) -> TSeq : ...
100+
71101
72102# Python 3 strings are unicode, translate them to/from utf8 for C-interop.
73103class c_interop_string (c_char_p ):
74- def __init__ (self , p = None ):
104+ def __init__ (self , p : str | bytes | None = None ):
75105 if p is None :
76106 p = ""
77107 if isinstance (p , str ):
78108 p = p .encode ("utf8" )
79109 super (c_char_p , self ).__init__ (p )
80110
81- def __str__ (self ):
82- return self .value
111+ def __str__ (self ) -> str :
112+ return self .value or ""
83113
84114 @property
85- def value (self ):
86- if super (c_char_p , self ).value is None :
115+ def value (self ) -> str | None : # type: ignore [override]
116+ val = super (c_char_p , self ).value
117+ if val is None :
87118 return None
88- return super ( c_char_p , self ). value .decode ("utf8" )
119+ return val .decode ("utf8" )
89120
90121 @classmethod
91- def from_param (cls , param ) :
122+ def from_param (cls , param : str | bytes | None ) -> c_interop_string :
92123 if isinstance (param , str ):
93124 return cls (param )
94125 if isinstance (param , bytes ):
95126 return cls (param )
96127 if param is None :
97128 # Support passing null to C functions expecting char arrays
98- return None
129+ return cls ( param )
99130 raise TypeError (
100131 "Cannot convert '{}' to '{}'" .format (type (param ).__name__ , cls .__name__ )
101132 )
102133
103134 @staticmethod
104- def to_python_string (x , * args ) :
135+ def to_python_string (x : c_interop_string , * args : Any ) -> str | None :
105136 return x .value
106137
107138
108- def b (x ) :
139+ def b (x : str | bytes ) -> bytes :
109140 if isinstance (x , bytes ):
110141 return x
111142 return x .encode ("utf8" )
@@ -115,9 +146,7 @@ def b(x):
115146# object. This is a problem, because it means that from_parameter will see an
116147# integer and pass the wrong value on platforms where int != void*. Work around
117148# this by marshalling object arguments as void**.
118- c_object_p = POINTER (c_void_p )
119-
120- callbacks = {}
149+ c_object_p : TType [CObjP ] = POINTER (c_void_p )
121150
122151### Exception Classes ###
123152
@@ -169,25 +198,32 @@ def __init__(self, enumeration, message):
169198
170199### Structures and Utility Classes ###
171200
201+ TInstance = TypeVar ("TInstance" )
202+ TResult = TypeVar ("TResult" )
203+
172204
173- class CachedProperty :
205+ class CachedProperty ( Generic [ TInstance , TResult ]) :
174206 """Decorator that lazy-loads the value of a property.
175207
176208 The first time the property is accessed, the original property function is
177209 executed. The value it returns is set as the new value of that instance's
178210 property, replacing the original method.
179211 """
180212
181- def __init__ (self , wrapped ):
213+ def __init__ (self , wrapped : Callable [[ TInstance ], TResult ] ):
182214 self .wrapped = wrapped
183215 try :
184216 self .__doc__ = wrapped .__doc__
185217 except :
186218 pass
187219
188- def __get__ (self , instance , instance_type = None ):
220+ def __get__ (self , instance : TInstance , instance_type : Any = None ) -> TResult :
189221 if instance is None :
190- return self
222+ property_name = self .wrapped .__name__
223+ class_name = instance_type .__name__
224+ raise TypeError (
225+ f"'{ property_name } ' is not a static attribute of '{ class_name } '"
226+ )
191227
192228 value = self .wrapped (instance )
193229 setattr (instance , self .wrapped .__name__ , value )
@@ -200,13 +236,16 @@ class _CXString(Structure):
200236
201237 _fields_ = [("spelling" , c_char_p ), ("free" , c_int )]
202238
203- def __del__ (self ):
239+ def __del__ (self ) -> None :
204240 conf .lib .clang_disposeString (self )
205241
206242 @staticmethod
207- def from_result (res , fn = None , args = None ):
243+ def from_result (res : _CXString , fn : Any = None , args : Any = None ) -> str :
208244 assert isinstance (res , _CXString )
209- return conf .lib .clang_getCString (res )
245+ pystr : str | None = conf .lib .clang_getCString (res )
246+ if pystr is None :
247+ return ""
248+ return pystr
210249
211250
212251class SourceLocation (Structure ):
@@ -2030,8 +2069,8 @@ def visitor(child, parent, children):
20302069 children .append (child )
20312070 return 1 # continue
20322071
2033- children = []
2034- conf .lib .clang_visitChildren (self , callbacks [ "cursor_visit" ] (visitor ), children )
2072+ children : list [ Cursor ] = []
2073+ conf .lib .clang_visitChildren (self , cursor_visit_callback (visitor ), children )
20352074 return iter (children )
20362075
20372076 def walk_preorder (self ):
@@ -2543,10 +2582,8 @@ def visitor(field, children):
25432582 fields .append (field )
25442583 return 1 # continue
25452584
2546- fields = []
2547- conf .lib .clang_Type_visitFields (
2548- self , callbacks ["fields_visit" ](visitor ), fields
2549- )
2585+ fields : list [Cursor ] = []
2586+ conf .lib .clang_Type_visitFields (self , fields_visit_callback (visitor ), fields )
25502587 return iter (fields )
25512588
25522589 def get_exception_specification_kind (self ):
@@ -3058,7 +3095,7 @@ def visitor(fobj, lptr, depth, includes):
30583095 # Automatically adapt CIndex/ctype pointers to python objects
30593096 includes = []
30603097 conf .lib .clang_getInclusions (
3061- self , callbacks [ "translation_unit_includes" ] (visitor ), includes
3098+ self , translation_unit_includes_callback (visitor ), includes
30623099 )
30633100
30643101 return iter (includes )
@@ -3570,15 +3607,15 @@ def write_main_file_to_stdout(self):
35703607
35713608# Now comes the plumbing to hook up the C library.
35723609
3573- # Register callback types in common container.
3574- callbacks [ "translation_unit_includes" ] = CFUNCTYPE (
3610+ # Register callback types
3611+ translation_unit_includes_callback = CFUNCTYPE (
35753612 None , c_object_p , POINTER (SourceLocation ), c_uint , py_object
35763613)
3577- callbacks [ "cursor_visit" ] = CFUNCTYPE (c_int , Cursor , Cursor , py_object )
3578- callbacks [ "fields_visit" ] = CFUNCTYPE (c_int , Cursor , py_object )
3614+ cursor_visit_callback = CFUNCTYPE (c_int , Cursor , Cursor , py_object )
3615+ fields_visit_callback = CFUNCTYPE (c_int , Cursor , py_object )
35793616
35803617# Functions strictly alphabetical order.
3581- functionList = [
3618+ functionList : list [ LibFunc ] = [
35823619 (
35833620 "clang_annotateTokens" ,
35843621 [TranslationUnit , POINTER (Token ), c_uint , POINTER (Cursor )],
@@ -3748,7 +3785,7 @@ def write_main_file_to_stdout(self):
37483785 ("clang_getIncludedFile" , [Cursor ], c_object_p , File .from_result ),
37493786 (
37503787 "clang_getInclusions" ,
3751- [TranslationUnit , callbacks [ "translation_unit_includes" ] , py_object ],
3788+ [TranslationUnit , translation_unit_includes_callback , py_object ],
37523789 ),
37533790 (
37543791 "clang_getInstantiationLocation" ,
@@ -3833,7 +3870,7 @@ def write_main_file_to_stdout(self):
38333870 "clang_tokenize" ,
38343871 [TranslationUnit , SourceRange , POINTER (POINTER (Token )), POINTER (c_uint )],
38353872 ),
3836- ("clang_visitChildren" , [Cursor , callbacks [ "cursor_visit" ] , py_object ], c_uint ),
3873+ ("clang_visitChildren" , [Cursor , cursor_visit_callback , py_object ], c_uint ),
38373874 ("clang_Cursor_getNumArguments" , [Cursor ], c_int ),
38383875 ("clang_Cursor_getArgument" , [Cursor , c_uint ], Cursor , Cursor .from_result ),
38393876 ("clang_Cursor_getNumTemplateArguments" , [Cursor ], c_int ),
@@ -3859,19 +3896,19 @@ def write_main_file_to_stdout(self):
38593896 ("clang_Type_getSizeOf" , [Type ], c_longlong ),
38603897 ("clang_Type_getCXXRefQualifier" , [Type ], c_uint ),
38613898 ("clang_Type_getNamedType" , [Type ], Type , Type .from_result ),
3862- ("clang_Type_visitFields" , [Type , callbacks [ "fields_visit" ] , py_object ], c_uint ),
3899+ ("clang_Type_visitFields" , [Type , fields_visit_callback , py_object ], c_uint ),
38633900]
38643901
38653902
38663903class LibclangError (Exception ):
3867- def __init__ (self , message ):
3904+ def __init__ (self , message : str ):
38683905 self .m = message
38693906
3870- def __str__ (self ):
3907+ def __str__ (self ) -> str :
38713908 return self .m
38723909
38733910
3874- def register_function (lib , item , ignore_errors ) :
3911+ def register_function (lib : CDLL , item : LibFunc , ignore_errors : bool ) -> None :
38753912 # A function may not exist, if these bindings are used with an older or
38763913 # incompatible version of libclang.so.
38773914 try :
@@ -3895,28 +3932,28 @@ def register_function(lib, item, ignore_errors):
38953932 func .errcheck = item [3 ]
38963933
38973934
3898- def register_functions (lib , ignore_errors ) :
3935+ def register_functions (lib : CDLL , ignore_errors : bool ) -> None :
38993936 """Register function prototypes with a libclang library instance.
39003937
39013938 This must be called as part of library instantiation so Python knows how
39023939 to call out to the shared library.
39033940 """
39043941
3905- def register (item ) :
3906- return register_function (lib , item , ignore_errors )
3942+ def register (item : LibFunc ) -> None :
3943+ register_function (lib , item , ignore_errors )
39073944
39083945 for f in functionList :
39093946 register (f )
39103947
39113948
39123949class Config :
39133950 library_path = None
3914- library_file = None
3951+ library_file : str | None = None
39153952 compatibility_check = True
39163953 loaded = False
39173954
39183955 @staticmethod
3919- def set_library_path (path ) :
3956+ def set_library_path (path : StrPath ) -> None :
39203957 """Set the path in which to search for libclang"""
39213958 if Config .loaded :
39223959 raise Exception (
@@ -3927,7 +3964,7 @@ def set_library_path(path):
39273964 Config .library_path = os .fspath (path )
39283965
39293966 @staticmethod
3930- def set_library_file (filename ) :
3967+ def set_library_file (filename : StrPath ) -> None :
39313968 """Set the exact location of libclang"""
39323969 if Config .loaded :
39333970 raise Exception (
@@ -3938,7 +3975,7 @@ def set_library_file(filename):
39383975 Config .library_file = os .fspath (filename )
39393976
39403977 @staticmethod
3941- def set_compatibility_check (check_status ) :
3978+ def set_compatibility_check (check_status : bool ) -> None :
39423979 """Perform compatibility check when loading libclang
39433980
39443981 The python bindings are only tested and evaluated with the version of
@@ -3964,13 +4001,13 @@ def set_compatibility_check(check_status):
39644001 Config .compatibility_check = check_status
39654002
39664003 @CachedProperty
3967- def lib (self ):
4004+ def lib (self ) -> CDLL :
39684005 lib = self .get_cindex_library ()
39694006 register_functions (lib , not Config .compatibility_check )
39704007 Config .loaded = True
39714008 return lib
39724009
3973- def get_filename (self ):
4010+ def get_filename (self ) -> str :
39744011 if Config .library_file :
39754012 return Config .library_file
39764013
@@ -3990,7 +4027,7 @@ def get_filename(self):
39904027
39914028 return file
39924029
3993- def get_cindex_library (self ):
4030+ def get_cindex_library (self ) -> CDLL :
39944031 try :
39954032 library = cdll .LoadLibrary (self .get_filename ())
39964033 except OSError as e :
@@ -4003,7 +4040,7 @@ def get_cindex_library(self):
40034040
40044041 return library
40054042
4006- def function_exists (self , name ) :
4043+ def function_exists (self , name : str ) -> bool :
40074044 try :
40084045 getattr (self .lib , name )
40094046 except AttributeError :
0 commit comments