43
43
Most object information is exposed using properties, when the underlying API
44
44
call is efficient.
45
45
"""
46
- from __future__ import absolute_import , division , print_function
46
+ from __future__ import annotations
47
47
48
48
# TODO
49
49
# ====
66
66
67
67
import collections .abc
68
68
import os
69
+ import sys
69
70
from enum import Enum
70
71
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
+
71
101
72
102
# Python 3 strings are unicode, translate them to/from utf8 for C-interop.
73
103
class c_interop_string (c_char_p ):
74
- def __init__ (self , p = None ):
104
+ def __init__ (self , p : str | bytes | None = None ):
75
105
if p is None :
76
106
p = ""
77
107
if isinstance (p , str ):
78
108
p = p .encode ("utf8" )
79
109
super (c_char_p , self ).__init__ (p )
80
110
81
- def __str__ (self ):
82
- return self .value
111
+ def __str__ (self ) -> str :
112
+ return self .value or ""
83
113
84
114
@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 :
87
118
return None
88
- return super ( c_char_p , self ). value .decode ("utf8" )
119
+ return val .decode ("utf8" )
89
120
90
121
@classmethod
91
- def from_param (cls , param ) :
122
+ def from_param (cls , param : str | bytes | None ) -> c_interop_string :
92
123
if isinstance (param , str ):
93
124
return cls (param )
94
125
if isinstance (param , bytes ):
95
126
return cls (param )
96
127
if param is None :
97
128
# Support passing null to C functions expecting char arrays
98
- return None
129
+ return cls ( param )
99
130
raise TypeError (
100
131
"Cannot convert '{}' to '{}'" .format (type (param ).__name__ , cls .__name__ )
101
132
)
102
133
103
134
@staticmethod
104
- def to_python_string (x , * args ) :
135
+ def to_python_string (x : c_interop_string , * args : Any ) -> str | None :
105
136
return x .value
106
137
107
138
108
- def b (x ) :
139
+ def b (x : str | bytes ) -> bytes :
109
140
if isinstance (x , bytes ):
110
141
return x
111
142
return x .encode ("utf8" )
@@ -115,9 +146,7 @@ def b(x):
115
146
# object. This is a problem, because it means that from_parameter will see an
116
147
# integer and pass the wrong value on platforms where int != void*. Work around
117
148
# 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 )
121
150
122
151
### Exception Classes ###
123
152
@@ -169,25 +198,32 @@ def __init__(self, enumeration, message):
169
198
170
199
### Structures and Utility Classes ###
171
200
201
+ TInstance = TypeVar ("TInstance" )
202
+ TResult = TypeVar ("TResult" )
203
+
172
204
173
- class CachedProperty :
205
+ class CachedProperty ( Generic [ TInstance , TResult ]) :
174
206
"""Decorator that lazy-loads the value of a property.
175
207
176
208
The first time the property is accessed, the original property function is
177
209
executed. The value it returns is set as the new value of that instance's
178
210
property, replacing the original method.
179
211
"""
180
212
181
- def __init__ (self , wrapped ):
213
+ def __init__ (self , wrapped : Callable [[ TInstance ], TResult ] ):
182
214
self .wrapped = wrapped
183
215
try :
184
216
self .__doc__ = wrapped .__doc__
185
217
except :
186
218
pass
187
219
188
- def __get__ (self , instance , instance_type = None ):
220
+ def __get__ (self , instance : TInstance , instance_type : Any = None ) -> TResult :
189
221
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
+ )
191
227
192
228
value = self .wrapped (instance )
193
229
setattr (instance , self .wrapped .__name__ , value )
@@ -200,13 +236,16 @@ class _CXString(Structure):
200
236
201
237
_fields_ = [("spelling" , c_char_p ), ("free" , c_int )]
202
238
203
- def __del__ (self ):
239
+ def __del__ (self ) -> None :
204
240
conf .lib .clang_disposeString (self )
205
241
206
242
@staticmethod
207
- def from_result (res , fn = None , args = None ):
243
+ def from_result (res : _CXString , fn : Any = None , args : Any = None ) -> str :
208
244
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
210
249
211
250
212
251
class SourceLocation (Structure ):
@@ -2030,8 +2069,8 @@ def visitor(child, parent, children):
2030
2069
children .append (child )
2031
2070
return 1 # continue
2032
2071
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 )
2035
2074
return iter (children )
2036
2075
2037
2076
def walk_preorder (self ):
@@ -2543,10 +2582,8 @@ def visitor(field, children):
2543
2582
fields .append (field )
2544
2583
return 1 # continue
2545
2584
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 )
2550
2587
return iter (fields )
2551
2588
2552
2589
def get_exception_specification_kind (self ):
@@ -3058,7 +3095,7 @@ def visitor(fobj, lptr, depth, includes):
3058
3095
# Automatically adapt CIndex/ctype pointers to python objects
3059
3096
includes = []
3060
3097
conf .lib .clang_getInclusions (
3061
- self , callbacks [ "translation_unit_includes" ] (visitor ), includes
3098
+ self , translation_unit_includes_callback (visitor ), includes
3062
3099
)
3063
3100
3064
3101
return iter (includes )
@@ -3570,15 +3607,15 @@ def write_main_file_to_stdout(self):
3570
3607
3571
3608
# Now comes the plumbing to hook up the C library.
3572
3609
3573
- # Register callback types in common container.
3574
- callbacks [ "translation_unit_includes" ] = CFUNCTYPE (
3610
+ # Register callback types
3611
+ translation_unit_includes_callback = CFUNCTYPE (
3575
3612
None , c_object_p , POINTER (SourceLocation ), c_uint , py_object
3576
3613
)
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 )
3579
3616
3580
3617
# Functions strictly alphabetical order.
3581
- functionList = [
3618
+ functionList : list [ LibFunc ] = [
3582
3619
(
3583
3620
"clang_annotateTokens" ,
3584
3621
[TranslationUnit , POINTER (Token ), c_uint , POINTER (Cursor )],
@@ -3748,7 +3785,7 @@ def write_main_file_to_stdout(self):
3748
3785
("clang_getIncludedFile" , [Cursor ], c_object_p , File .from_result ),
3749
3786
(
3750
3787
"clang_getInclusions" ,
3751
- [TranslationUnit , callbacks [ "translation_unit_includes" ] , py_object ],
3788
+ [TranslationUnit , translation_unit_includes_callback , py_object ],
3752
3789
),
3753
3790
(
3754
3791
"clang_getInstantiationLocation" ,
@@ -3833,7 +3870,7 @@ def write_main_file_to_stdout(self):
3833
3870
"clang_tokenize" ,
3834
3871
[TranslationUnit , SourceRange , POINTER (POINTER (Token )), POINTER (c_uint )],
3835
3872
),
3836
- ("clang_visitChildren" , [Cursor , callbacks [ "cursor_visit" ] , py_object ], c_uint ),
3873
+ ("clang_visitChildren" , [Cursor , cursor_visit_callback , py_object ], c_uint ),
3837
3874
("clang_Cursor_getNumArguments" , [Cursor ], c_int ),
3838
3875
("clang_Cursor_getArgument" , [Cursor , c_uint ], Cursor , Cursor .from_result ),
3839
3876
("clang_Cursor_getNumTemplateArguments" , [Cursor ], c_int ),
@@ -3859,19 +3896,19 @@ def write_main_file_to_stdout(self):
3859
3896
("clang_Type_getSizeOf" , [Type ], c_longlong ),
3860
3897
("clang_Type_getCXXRefQualifier" , [Type ], c_uint ),
3861
3898
("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 ),
3863
3900
]
3864
3901
3865
3902
3866
3903
class LibclangError (Exception ):
3867
- def __init__ (self , message ):
3904
+ def __init__ (self , message : str ):
3868
3905
self .m = message
3869
3906
3870
- def __str__ (self ):
3907
+ def __str__ (self ) -> str :
3871
3908
return self .m
3872
3909
3873
3910
3874
- def register_function (lib , item , ignore_errors ) :
3911
+ def register_function (lib : CDLL , item : LibFunc , ignore_errors : bool ) -> None :
3875
3912
# A function may not exist, if these bindings are used with an older or
3876
3913
# incompatible version of libclang.so.
3877
3914
try :
@@ -3895,28 +3932,28 @@ def register_function(lib, item, ignore_errors):
3895
3932
func .errcheck = item [3 ]
3896
3933
3897
3934
3898
- def register_functions (lib , ignore_errors ) :
3935
+ def register_functions (lib : CDLL , ignore_errors : bool ) -> None :
3899
3936
"""Register function prototypes with a libclang library instance.
3900
3937
3901
3938
This must be called as part of library instantiation so Python knows how
3902
3939
to call out to the shared library.
3903
3940
"""
3904
3941
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 )
3907
3944
3908
3945
for f in functionList :
3909
3946
register (f )
3910
3947
3911
3948
3912
3949
class Config :
3913
3950
library_path = None
3914
- library_file = None
3951
+ library_file : str | None = None
3915
3952
compatibility_check = True
3916
3953
loaded = False
3917
3954
3918
3955
@staticmethod
3919
- def set_library_path (path ) :
3956
+ def set_library_path (path : StrPath ) -> None :
3920
3957
"""Set the path in which to search for libclang"""
3921
3958
if Config .loaded :
3922
3959
raise Exception (
@@ -3927,7 +3964,7 @@ def set_library_path(path):
3927
3964
Config .library_path = os .fspath (path )
3928
3965
3929
3966
@staticmethod
3930
- def set_library_file (filename ) :
3967
+ def set_library_file (filename : StrPath ) -> None :
3931
3968
"""Set the exact location of libclang"""
3932
3969
if Config .loaded :
3933
3970
raise Exception (
@@ -3938,7 +3975,7 @@ def set_library_file(filename):
3938
3975
Config .library_file = os .fspath (filename )
3939
3976
3940
3977
@staticmethod
3941
- def set_compatibility_check (check_status ) :
3978
+ def set_compatibility_check (check_status : bool ) -> None :
3942
3979
"""Perform compatibility check when loading libclang
3943
3980
3944
3981
The python bindings are only tested and evaluated with the version of
@@ -3964,13 +4001,13 @@ def set_compatibility_check(check_status):
3964
4001
Config .compatibility_check = check_status
3965
4002
3966
4003
@CachedProperty
3967
- def lib (self ):
4004
+ def lib (self ) -> CDLL :
3968
4005
lib = self .get_cindex_library ()
3969
4006
register_functions (lib , not Config .compatibility_check )
3970
4007
Config .loaded = True
3971
4008
return lib
3972
4009
3973
- def get_filename (self ):
4010
+ def get_filename (self ) -> str :
3974
4011
if Config .library_file :
3975
4012
return Config .library_file
3976
4013
@@ -3990,7 +4027,7 @@ def get_filename(self):
3990
4027
3991
4028
return file
3992
4029
3993
- def get_cindex_library (self ):
4030
+ def get_cindex_library (self ) -> CDLL :
3994
4031
try :
3995
4032
library = cdll .LoadLibrary (self .get_filename ())
3996
4033
except OSError as e :
@@ -4003,7 +4040,7 @@ def get_cindex_library(self):
4003
4040
4004
4041
return library
4005
4042
4006
- def function_exists (self , name ) :
4043
+ def function_exists (self , name : str ) -> bool :
4007
4044
try :
4008
4045
getattr (self .lib , name )
4009
4046
except AttributeError :
0 commit comments