xref: /petsc/lib/petsc/bin/maint/petsclinter/petsclinter/util/_clang.py (revision 51b144c619aff302b570817d6f78637b8418d403)
1#!/usr/bin/env python3
2"""
3# Created: Mon Jun 20 17:45:39 2022 (-0400)
4# @author: Jacob Faibussowitsch
5"""
6from __future__ import annotations
7
8from .._typing import *
9
10import enum
11import ctypes
12import clang.cindex as clx # type: ignore[import]
13
14class CXTranslationUnit(enum.IntFlag):
15  """
16  clang.cindex.TranslationUnit does not have all latest flags
17
18  see: https://clang.llvm.org/doxygen/group__CINDEX__TRANSLATION__UNIT.html
19  """
20  NONE                                 = 0x0
21  DetailedPreprocessingRecord          = 0x01
22  Incomplete                           = 0x02
23  PrecompiledPreamble                  = 0x04
24  CacheCompletionResults               = 0x08
25  ForSerialization                     = 0x10
26  SkipFunctionBodies                   = 0x40
27  IncludeBriefCommentsInCodeCompletion = 0x80
28  CreatePreambleOnFirstParse           = 0x100
29  KeepGoing                            = 0x200
30  SingleFileParse                      = 0x400
31  LimitSkipFunctionBodiesToPreamble    = 0x800
32  IncludeAttributedTypes               = 0x1000
33  VisitImplicitAttributes              = 0x2000
34  IgnoreNonErrorsFromIncludedFiles     = 0x4000
35  RetainExcludedConditionalBlocks      = 0x8000
36
37# clang options used for parsing files
38base_clang_options = (
39  CXTranslationUnit.PrecompiledPreamble |
40  #CXTranslationUnit.DetailedPreprocessingRecord |
41  CXTranslationUnit.SkipFunctionBodies          |
42  CXTranslationUnit.LimitSkipFunctionBodiesToPreamble
43)
44
45# clang options for creating the precompiled megaheader
46base_pch_clang_options = (
47  CXTranslationUnit.CreatePreambleOnFirstParse |
48  CXTranslationUnit.Incomplete                 |
49  CXTranslationUnit.ForSerialization           |
50  CXTranslationUnit.KeepGoing
51)
52
53# Cursors that may be attached to function-like usage
54clx_func_call_cursor_kinds = {
55  clx.CursorKind.FUNCTION_DECL,
56  clx.CursorKind.CALL_EXPR
57}
58
59# Cursors that may be attached to mathematical operations or types
60clx_math_cursor_kinds = {
61  clx.CursorKind.INTEGER_LITERAL,
62  clx.CursorKind.UNARY_OPERATOR,
63  clx.CursorKind.BINARY_OPERATOR
64}
65
66# Cursors that contain base literal types
67clx_literal_cursor_kinds = {
68  clx.CursorKind.INTEGER_LITERAL,
69  clx.CursorKind.STRING_LITERAL
70}
71
72# Cursors that may be attached to casting
73clx_cast_cursor_kinds = {
74  clx.CursorKind.CSTYLE_CAST_EXPR,
75  clx.CursorKind.CXX_STATIC_CAST_EXPR,
76  clx.CursorKind.CXX_DYNAMIC_CAST_EXPR,
77  clx.CursorKind.CXX_REINTERPRET_CAST_EXPR,
78  clx.CursorKind.CXX_CONST_CAST_EXPR,
79  clx.CursorKind.CXX_FUNCTIONAL_CAST_EXPR
80}
81
82# Cursors that may be attached when types are converted
83clx_conversion_cursor_kinds = clx_cast_cursor_kinds | {clx.CursorKind.UNEXPOSED_EXPR}
84
85clx_var_token_kinds = {clx.TokenKind.IDENTIFIER}
86
87clx_function_type_kinds = {clx.TypeKind.FUNCTIONPROTO, clx.TypeKind.FUNCTIONNOPROTO}
88
89# General Array types, note this doesn't contain the pointer type since that is usually handled
90# differently
91clx_array_type_kinds = {
92  clx.TypeKind.INCOMPLETEARRAY,
93  clx.TypeKind.CONSTANTARRAY,
94  clx.TypeKind.VARIABLEARRAY
95}
96
97clx_pointer_type_kinds = clx_array_type_kinds | {clx.TypeKind.POINTER}
98
99# Specific types
100clx_enum_type_kinds   = {clx.TypeKind.ENUM}
101clx_bool_type_kinds   = {clx.TypeKind.BOOL}
102clx_char_type_kinds   = {clx.TypeKind.CHAR_S, clx.TypeKind.UCHAR}
103clx_mpiint_type_kinds = {clx.TypeKind.INT}
104clx_int_type_kinds    = clx_enum_type_kinds | clx_mpiint_type_kinds | {
105  clx.TypeKind.USHORT,
106  clx.TypeKind.SHORT,
107  clx.TypeKind.UINT,
108  clx.TypeKind.LONG,
109  clx.TypeKind.ULONG,
110  clx.TypeKind.LONGLONG,
111  clx.TypeKind.ULONGLONG
112}
113
114clx_real_type_kinds = {
115  clx.TypeKind.FLOAT,
116  clx.TypeKind.DOUBLE,
117  clx.TypeKind.LONGDOUBLE,
118  clx.TypeKind.FLOAT128
119}
120
121clx_scalar_type_kinds = clx_real_type_kinds | {clx.TypeKind.COMPLEX}
122
123_T = TypeVar('_T')
124_U = TypeVar('_U', covariant=True)
125
126class CTypesCallable(Protocol[_T, _U]):
127  # work around a bug in mypy:
128  # error: "CTypesCallable[_T, _U]" has no attribute "__name__"
129  __name__: str
130
131  @property
132  def argtypes(self) -> Sequence[type[_T]]: ...
133
134  def __call__(*args: _T) -> _U: ...
135
136class ClangFunction(Generic[_T, _U]):
137  r"""A wrapper to enable safely calling a clang function from python.
138
139  Automatically check the return-type (if it is some kind of int) and raises a RuntimeError if an
140  error is detected
141  """
142  __slots__ = ('_function',)
143
144  _function: CTypesCallable[_T, _U]
145
146  def __init__(self, function: CTypesCallable[_T, _U]) -> None:
147    r"""Construct a `ClangFunction`
148
149    Parameters
150    ----------
151    function : callable
152      the underlying clang function
153    """
154    self._function = function
155    return
156
157  def __getattr__(self, attr: str) -> Any:
158    return getattr(self._function, attr)
159
160  # Unfortunately, this type-hint does not really do what we want yet... it says that
161  # every entry in *args must be of type _T. This is both good and bad:
162  #
163  # The good news is that if len(*args) == 1, or all arguments are indeed the same type,
164  # then this __call__() will be properly type checked.
165  #
166  # The bad new is if *args does take multiple different argument types, then _T
167  # will be deduced to Any in get_clang_function(), and this call will be completely
168  # unchecked. At least the type checkers won't through spurious warnings though...
169  def __call__(self, *args, check: bool = True) -> _U:
170    r"""Invoke the clang function
171
172    Parameters
173    ----------
174    *args :
175      arguments to pass to the clang function
176    check : optional
177      if the return type is ctype.c_uint, check that it is 0
178
179    Returns
180    -------
181    ret :
182      the return value of the clang function
183
184    Raises
185    ------
186    ValueError
187      if the clang function is called with the wrong number of Arguments
188    TypeError
189      if the clang function was called with the wrong argument types
190    RuntimeError
191      if the clang function returned a nonzero exit code
192    """
193    if len(args) != len(self._function.argtypes):
194      mess = f'Trying to call {self._function.__name__}(). Wrong number of arguments for function, expected {len(self._function.argtypes)} got {len(args)}'
195      raise ValueError(mess)
196    for i, (arg, expected) in enumerate(zip(args, self._function.argtypes)):
197      if type(arg) != expected:
198        mess = f'Trying to call {self._function.__name__}(). Argument type for argument #{i} does not match. Expected {expected}, got {type(arg)}'
199        raise TypeError(mess)
200    ret = self._function(*args)
201    if check and isinstance(ret, int) and ret != 0:
202      raise RuntimeError(f'{self._function.__name__}() returned nonzero exit code {ret}')
203    return ret
204
205@overload
206def get_clang_function(name: str, arg_types: Sequence[type[_T]]) -> ClangFunction[_T, ctypes.c_uint]:
207  ...
208
209@overload
210def get_clang_function(name: str, arg_types: Sequence[type[_T]], ret_type: type[_U]) -> ClangFunction[_T, _U]:
211  ...
212
213def get_clang_function(name: str, arg_types: Sequence[type[_T]], ret_type: Optional[type[_U]] = None) -> ClangFunction[_T, _U]:
214  r"""Get (or register) the clang function RET_TYPE (NAME *)(ARG_TYPES...)
215
216  A useful helper routine to reduce verbiage when retrieving a clang function which may or may not
217  already be exposed by clang.cindex
218
219  Parameters
220  ----------
221  name :
222    the name of the clang function
223  arg_types :
224    the argument types of the clang function
225  ret_type : optional
226    the return type of the clang function, or ctypes.c_uint if None
227
228  Returns
229  -------
230  clang_func :
231    the callable clang function
232  """
233  if ret_type is None:
234    # cast needed, otherwise
235    #
236    # error: Incompatible types in assignment (expression has type "Type[c_uint]",
237    # variable has type "Optional[Type[_U]]")
238    ret_type = TYPE_CAST(type[_U], ctypes.c_uint)
239
240  clxlib = clx.conf.lib
241  try:
242    func = getattr(clxlib, name)
243    if (func.argtypes is None) and (func.errcheck is None):
244      # if this hasn't been registered before these will be none
245      raise AttributeError
246  except AttributeError:
247    # have to do the book-keeping ourselves since it may not be properly hooked up
248    clx.register_function(clxlib, (name, arg_types, ret_type), False)
249    func = getattr(clxlib, name)
250  return ClangFunction(TYPE_CAST(CTypesCallable[_T, _U], func))
251