From f4d80e0e9f0c8d8e85d04456ff96cc46ea759759 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Fri, 27 Mar 2026 14:34:39 +0000 Subject: [PATCH 1/3] Implement _CursorMirror and _TypeMirror classes to enhance libclang AST object mapping --- src/xyz/_libclang_mirrors.py | 329 ++++++++++++++++++++++++++++++++ src/xyz/cppmodel.py | 78 ++++++-- tests/test_libclang_mappings.py | 88 +++++++++ 3 files changed, 474 insertions(+), 21 deletions(-) create mode 100644 src/xyz/_libclang_mirrors.py create mode 100644 tests/test_libclang_mappings.py diff --git a/src/xyz/_libclang_mirrors.py b/src/xyz/_libclang_mirrors.py new file mode 100644 index 0000000..b4dde12 --- /dev/null +++ b/src/xyz/_libclang_mirrors.py @@ -0,0 +1,329 @@ +""" +Base classes that mirror libclang AST objects to provide self-documenting APIs. + +This module contains `_CursorMirror` and `_TypeMirror`, which explicitly map +all relevant properties and methods from `clang.cindex.Cursor` and +`clang.cindex.Type`. + +By inheriting from these classes, `cppmodel` wrappers natively expose the full +power of `libclang` while remaining discoverable by IDEs and type-checkers. +""" + +from typing import Any + +from clang.cindex import Cursor +from clang.cindex import Type as _ClangType + + +class _CursorMirror: + _cursor: Cursor + + @property + def access_specifier(self) -> Any: + return self._cursor.access_specifier + + @property + def availability(self) -> Any: + return self._cursor.availability + + @property + def brief_comment(self) -> Any: + return self._cursor.brief_comment + + @property + def canonical(self) -> Any: + return self._cursor.canonical + + @property + def displayname(self) -> Any: + return self._cursor.displayname + + @property + def location(self) -> Any: + return self._cursor.location + + @property + def type(self) -> Any: + return self._cursor.type + + @property + def kind(self) -> Any: + return self._cursor.kind + + def enum_type(self) -> Any: + return self._cursor.enum_type + + @property + def enum_value(self) -> Any: + return self._cursor.enum_value + + @property + def exception_specification_kind(self) -> Any: + return self._cursor.exception_specification_kind + + @property + def extent(self) -> Any: + return self._cursor.extent + + @property + def hash(self) -> Any: + return self._cursor.hash + + @property + def lexical_parent(self) -> Any: + return self._cursor.lexical_parent + + @property + def linkage(self) -> Any: + return self._cursor.linkage + + @property + def mangled_name(self) -> Any: + return self._cursor.mangled_name + + @property + def objc_type_encoding(self) -> Any: + return self._cursor.objc_type_encoding + + @property + def raw_comment(self) -> Any: + return self._cursor.raw_comment + + @property + def referenced(self) -> Any: + return self._cursor.referenced + + @property + def result_type(self) -> Any: + return self._cursor.result_type + + @property + def semantic_parent(self) -> Any: + return self._cursor.semantic_parent + + @property + def spelling(self) -> Any: + return self._cursor.spelling + + @property + def storage_class(self) -> Any: + return self._cursor.storage_class + + @property + def tls_kind(self) -> Any: + return self._cursor.tls_kind + + @property + def translation_unit(self) -> Any: + return self._cursor.translation_unit + + @property + def underlying_typedef_type(self) -> Any: + return self._cursor.underlying_typedef_type + + def from_cursor_result(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.from_cursor_result(*args, **kwargs) + + def from_location(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.from_location(*args, **kwargs) + + def from_result(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.from_result(*args, **kwargs) + + def get_arguments(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_arguments(*args, **kwargs) + + def get_bitfield_width(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_bitfield_width(*args, **kwargs) + + def get_children(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_children(*args, **kwargs) + + def get_definition(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_definition(*args, **kwargs) + + def get_field_offsetof(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_field_offsetof(*args, **kwargs) + + def get_included_file(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_included_file(*args, **kwargs) + + def get_num_template_arguments(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_num_template_arguments(*args, **kwargs) + + def get_template_argument_kind(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_template_argument_kind(*args, **kwargs) + + def get_template_argument_type(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_template_argument_type(*args, **kwargs) + + def get_template_argument_unsigned_value(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_template_argument_unsigned_value(*args, **kwargs) + + def get_template_argument_value(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_template_argument_value(*args, **kwargs) + + def get_tokens(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_tokens(*args, **kwargs) + + def get_usr(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.get_usr(*args, **kwargs) + + def is_abstract_record(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_abstract_record(*args, **kwargs) + + def is_anonymous(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_anonymous(*args, **kwargs) + + def is_bitfield(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_bitfield(*args, **kwargs) + + def is_const_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_const_method(*args, **kwargs) + + def is_converting_constructor(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_converting_constructor(*args, **kwargs) + + def is_copy_assignment_operator_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_copy_assignment_operator_method(*args, **kwargs) + + def is_copy_constructor(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_copy_constructor(*args, **kwargs) + + def is_default_constructor(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_default_constructor(*args, **kwargs) + + def is_default_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_default_method(*args, **kwargs) + + def is_definition(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_definition(*args, **kwargs) + + def is_deleted_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_deleted_method(*args, **kwargs) + + def is_explicit_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_explicit_method(*args, **kwargs) + + def is_move_assignment_operator_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_move_assignment_operator_method(*args, **kwargs) + + def is_move_constructor(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_move_constructor(*args, **kwargs) + + def is_mutable_field(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_mutable_field(*args, **kwargs) + + def is_pure_virtual_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_pure_virtual_method(*args, **kwargs) + + def is_scoped_enum(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_scoped_enum(*args, **kwargs) + + def is_static_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_static_method(*args, **kwargs) + + def is_virtual_method(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.is_virtual_method(*args, **kwargs) + + def walk_preorder(self, *args: Any, **kwargs: Any) -> Any: + return self._cursor.walk_preorder(*args, **kwargs) + + +class _TypeMirror: + _type: _ClangType + + @property + def kind(self) -> Any: + return self._type.kind + + @property + def element_count(self) -> Any: + return self._type.element_count + + @property + def element_type(self) -> Any: + return self._type.element_type + + @property + def spelling(self) -> Any: + return self._type.spelling + + @property + def translation_unit(self) -> Any: + return self._type.translation_unit + + def argument_types(self, *args: Any, **kwargs: Any) -> Any: + return self._type.argument_types(*args, **kwargs) + + def from_result(self, *args: Any, **kwargs: Any) -> Any: + return self._type.from_result(*args, **kwargs) + + def get_address_space(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_address_space(*args, **kwargs) + + def get_align(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_align(*args, **kwargs) + + def get_array_element_type(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_array_element_type(*args, **kwargs) + + def get_array_size(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_array_size(*args, **kwargs) + + def get_canonical(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_canonical(*args, **kwargs) + + def get_class_type(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_class_type(*args, **kwargs) + + def get_declaration(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_declaration(*args, **kwargs) + + def get_exception_specification_kind(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_exception_specification_kind(*args, **kwargs) + + def get_fields(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_fields(*args, **kwargs) + + def get_named_type(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_named_type(*args, **kwargs) + + def get_num_template_arguments(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_num_template_arguments(*args, **kwargs) + + def get_offset(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_offset(*args, **kwargs) + + def get_pointee(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_pointee(*args, **kwargs) + + def get_ref_qualifier(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_ref_qualifier(*args, **kwargs) + + def get_result(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_result(*args, **kwargs) + + def get_size(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_size(*args, **kwargs) + + def get_template_argument_type(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_template_argument_type(*args, **kwargs) + + def get_typedef_name(self, *args: Any, **kwargs: Any) -> Any: + return self._type.get_typedef_name(*args, **kwargs) + + def is_const_qualified(self, *args: Any, **kwargs: Any) -> Any: + return self._type.is_const_qualified(*args, **kwargs) + + def is_function_variadic(self, *args: Any, **kwargs: Any) -> Any: + return self._type.is_function_variadic(*args, **kwargs) + + def is_pod(self, *args: Any, **kwargs: Any) -> Any: + return self._type.is_pod(*args, **kwargs) + + def is_restrict_qualified(self, *args: Any, **kwargs: Any) -> Any: + return self._type.is_restrict_qualified(*args, **kwargs) + + def is_volatile_qualified(self, *args: Any, **kwargs: Any) -> Any: + return self._type.is_volatile_qualified(*args, **kwargs) diff --git a/src/xyz/cppmodel.py b/src/xyz/cppmodel.py index f10102f..05266fa 100644 --- a/src/xyz/cppmodel.py +++ b/src/xyz/cppmodel.py @@ -7,11 +7,14 @@ from clang.cindex import CursorKind as _CursorKind from clang.cindex import Diagnostic from clang.cindex import ExceptionSpecificationKind as _ExceptionSpecificationKind -from clang.cindex import SourceLocation from clang.cindex import TranslationUnit +from clang.cindex import Type as _ClangType from clang.cindex import TypeKind as _TypeKind # Suppress type checking warnings for clang.cindex kinds. +from xyz._libclang_mirrors import _CursorMirror +from xyz._libclang_mirrors import _TypeMirror + AccessSpecifier: Any = _AccessSpecifier CursorKind: Any = _CursorKind ExceptionSpecificationKind: Any = _ExceptionSpecificationKind @@ -22,36 +25,47 @@ def _get_annotations(cursor: Cursor) -> List[str]: return [c.displayname for c in cursor.get_children() if c.kind == CursorKind.ANNOTATE_ATTR] -class Unmodelled: +class Unmodelled(_CursorMirror): def __init__(self, cursor: Cursor): - self.location: SourceLocation = cursor.location + self._cursor = cursor self.name: str = cursor.displayname def __repr__(self) -> str: return "".format(self.name, self.location) -class Type: - def __init__(self, cindex_type): - self.kind = cindex_type.kind +class Type(_TypeMirror): + def __init__(self, cindex_type: _ClangType): + self._type = cindex_type self.name = cindex_type.spelling self.is_pointer: bool = self.kind == TypeKind.POINTER self.is_reference: bool = self.kind == TypeKind.LVALUEREFERENCE - self.is_const: bool = cindex_type.is_const_qualified() if self.is_pointer or self.is_reference: self.pointee: Optional[Type] = Type(cindex_type.get_pointee()) else: self.pointee = None + @property + def is_const(self) -> bool: + import warnings + + warnings.warn("is_const is deprecated, use is_const_qualified() instead", DeprecationWarning, stacklevel=2) + return self.is_const_qualified() + def __repr__(self) -> str: return "".format(self.name) -class Member: +class Member(_CursorMirror): def __init__(self, cursor: Cursor): - self.type: Type = Type(cursor.type) + self._cursor = cursor + self._wrapped_type: Type = Type(cursor.type) self.name: str = cursor.spelling + @property + def type(self) -> Type: + return self._wrapped_type + def __repr__(self) -> str: return "".format(self.type, self.name) @@ -67,8 +81,9 @@ def __repr__(self) -> str: return "".format(self.type, self.name) -class _Function: - def __init__(self, cursor): +class _Function(_CursorMirror): + def __init__(self, cursor: Cursor): + self._cursor = cursor self.name: str = cursor.spelling arguments: List[Optional[str]] = [str(x.spelling) or None for x in cursor.get_arguments()] argument_types: List[Type] = [Type(x) for x in cursor.type.argument_types()] @@ -98,7 +113,7 @@ def __repr__(self) -> str: class Function(_Function): - def __init__(self, cursor, namespaces: list[str] | None = None): + def __init__(self, cursor: Cursor, namespaces: list[str] | None = None): namespaces = namespaces or [] _Function.__init__(self, cursor) self.namespace: str = "::".join(namespaces) @@ -113,7 +128,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return self.__str__() - def __eq__(self, f) -> bool: + def __eq__(self, f: Any) -> bool: if self.name != f.name: return False if self.namespace != f.namespace: @@ -127,20 +142,40 @@ def __eq__(self, f) -> bool: class Method(_Function): - def __init__(self, cursor): + def __init__(self, cursor: Cursor): _Function.__init__(self, cursor) - self.is_const: bool = cursor.is_const_method() - self.is_virtual: bool = cursor.is_virtual_method() - self.is_pure_virtual: bool = cursor.is_pure_virtual_method() self.is_public: bool = cursor.access_specifier == AccessSpecifier.PUBLIC + @property + def is_const(self) -> bool: + import warnings + + warnings.warn("is_const is deprecated, use is_const_method() instead", DeprecationWarning, stacklevel=2) + return self.is_const_method() + + @property + def is_virtual(self) -> bool: + import warnings + + warnings.warn("is_virtual is deprecated, use is_virtual_method() instead", DeprecationWarning, stacklevel=2) + return self.is_virtual_method() + + @property + def is_pure_virtual(self) -> bool: + import warnings + + warnings.warn( + "is_pure_virtual is deprecated, use is_pure_virtual_method() instead", DeprecationWarning, stacklevel=2 + ) + return self.is_pure_virtual_method() + def __str__(self) -> str: s = _Function.__str__(self) - if self.is_const: + if self.is_const_method(): s = "{} const".format(s) - if self.is_pure_virtual: + if self.is_pure_virtual_method(): s = "virtual {} = 0".format(s) - elif self.is_virtual: + elif self.is_virtual_method(): s = "virtual {}".format(s) return "".format(s) @@ -148,8 +183,9 @@ def __repr__(self) -> str: return self.__str__() -class Class: +class Class(_CursorMirror): def __init__(self, cursor: Cursor, namespaces: List[str]): + self._cursor = cursor self.name: str = cursor.spelling self.namespace: str = "::".join(namespaces) self.qualified_name: str = self.name diff --git a/tests/test_libclang_mappings.py b/tests/test_libclang_mappings.py new file mode 100644 index 0000000..8cc8fc3 --- /dev/null +++ b/tests/test_libclang_mappings.py @@ -0,0 +1,88 @@ +""" +Tests to ensure all relevant libclang AST attributes are explicitly mapped in cppmodel. +""" + +import inspect + +import pytest +from clang.cindex import Cursor +from clang.cindex import TranslationUnit +from clang.cindex import Type as ClangType + +import xyz.cppmodel + +COMPILER_ARGS = [ + "-x", + "c++", + "-std=c++20", +] + +SOURCE = """\ +int z = 0; + +class A { + int a; + void foo(); +}; +""" + + +@pytest.fixture +def model(): + tu = TranslationUnit.from_source( + "sample.cc", + COMPILER_ARGS, + unsaved_files=[("sample.cc", SOURCE)], + ) + return xyz.cppmodel.Model(tu) + + +def get_public_attributes(cls): + """Returns a list of public attribute names for a given class.""" + return [name for name, _ in inspect.getmembers(cls) if not name.startswith("_")] + + +LIBCLANG_CURSOR_ATTRS = [attr for attr in get_public_attributes(Cursor) if attr not in ("data", "xdata")] + +LIBCLANG_TYPE_ATTRS = [attr for attr in get_public_attributes(ClangType) if attr not in ("data", "xdata")] + + +def is_mapped(wrapper, attr): + """Check if an attribute is accessible, handling exceptions from property evaluation.""" + if attr in dir(wrapper): + return True + try: + getattr(wrapper, attr) + return True + except AttributeError: + return False + except BaseException: + # Any other exception (AssertionError, Exception) means the property exists + # but evaluation failed for this specific instance's state. + return True + + +@pytest.mark.parametrize("attr", LIBCLANG_CURSOR_ATTRS) +def test_cursor_mappings(model, attr): + """Ensure all public attributes of libclang's Cursor are accessible on cppmodel wrappers.""" + # Get different wrappers that mirror Cursor + wrappers = [ + model.classes[0], # Class + model.classes[0].methods[0], # Method + model.classes[0].members[0], # Member + model.unmodelled_nodes[0], # Unmodelled + ] + + for wrapper in wrappers: + assert is_mapped(wrapper, attr), f"Missing Cursor attribute '{attr}' on {type(wrapper).__name__}" + + +@pytest.mark.parametrize("attr", LIBCLANG_TYPE_ATTRS) +def test_type_mappings(model, attr): + """Ensure all public attributes of libclang's Type are accessible on cppmodel's Type wrapper.""" + # Get a wrapper that mirrors Type + type_wrapper = model.classes[0].members[0].type + + assert is_mapped(wrapper=type_wrapper, attr=attr), ( + f"Missing Type attribute '{attr}' on {type(type_wrapper).__name__}" + ) From 4b2467102cf4ccaf6e7a47da331206c6a4c89f23 Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Fri, 27 Mar 2026 14:51:04 +0000 Subject: [PATCH 2/3] Address review comments --- src/xyz/_libclang_mirrors.py | 1 + tests/test_libclang_mappings.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/xyz/_libclang_mirrors.py b/src/xyz/_libclang_mirrors.py index b4dde12..02d27b6 100644 --- a/src/xyz/_libclang_mirrors.py +++ b/src/xyz/_libclang_mirrors.py @@ -50,6 +50,7 @@ def type(self) -> Any: def kind(self) -> Any: return self._cursor.kind + @property def enum_type(self) -> Any: return self._cursor.enum_type diff --git a/tests/test_libclang_mappings.py b/tests/test_libclang_mappings.py index 8cc8fc3..ca590f5 100644 --- a/tests/test_libclang_mappings.py +++ b/tests/test_libclang_mappings.py @@ -56,12 +56,34 @@ def is_mapped(wrapper, attr): return True except AttributeError: return False - except BaseException: + except Exception: # Any other exception (AssertionError, Exception) means the property exists # but evaluation failed for this specific instance's state. return True +def check_mapping_type(wrapper_cls, libclang_cls, attr): + """Verifies that the shape (property vs method) of the wrapper attribute matches libclang.""" + # If the wrapper overrides a native attribute completely, skip the shape check + # (e.g. cppmodel defines 'kind' or 'location' natively). + libclang_attr = inspect.getattr_static(libclang_cls, attr) + wrapper_attr = inspect.getattr_static(wrapper_cls, attr, None) + + if wrapper_attr is None: + return True # Handled by is_mapped failure if it's completely missing + + libclang_is_prop = isinstance(libclang_attr, property) + wrapper_is_prop = isinstance(wrapper_attr, property) + + # We also consider regular attributes to be "properties" for the sake of shape + if not libclang_is_prop and not inspect.isroutine(libclang_attr): + libclang_is_prop = True + if not wrapper_is_prop and not inspect.isroutine(wrapper_attr): + wrapper_is_prop = True + + return libclang_is_prop == wrapper_is_prop + + @pytest.mark.parametrize("attr", LIBCLANG_CURSOR_ATTRS) def test_cursor_mappings(model, attr): """Ensure all public attributes of libclang's Cursor are accessible on cppmodel wrappers.""" @@ -75,6 +97,10 @@ def test_cursor_mappings(model, attr): for wrapper in wrappers: assert is_mapped(wrapper, attr), f"Missing Cursor attribute '{attr}' on {type(wrapper).__name__}" + # Validate that the API shape matches exactly (method vs property) + assert check_mapping_type(type(wrapper), Cursor, attr), ( + f"Shape mismatch for Cursor attribute '{attr}' on {type(wrapper).__name__}" + ) @pytest.mark.parametrize("attr", LIBCLANG_TYPE_ATTRS) @@ -86,3 +112,7 @@ def test_type_mappings(model, attr): assert is_mapped(wrapper=type_wrapper, attr=attr), ( f"Missing Type attribute '{attr}' on {type(type_wrapper).__name__}" ) + # Validate that the API shape matches exactly (method vs property) + assert check_mapping_type(type(type_wrapper), ClangType, attr), ( + f"Shape mismatch for Type attribute '{attr}' on {type(type_wrapper).__name__}" + ) From fd457cee9bcad2a1ea40a7475532d2690e0ec02c Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Fri, 27 Mar 2026 15:02:18 +0000 Subject: [PATCH 3/3] No local imports --- src/xyz/cppmodel.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/xyz/cppmodel.py b/src/xyz/cppmodel.py index 05266fa..4381230 100644 --- a/src/xyz/cppmodel.py +++ b/src/xyz/cppmodel.py @@ -1,3 +1,4 @@ +import warnings from typing import Any from typing import List from typing import Optional @@ -47,8 +48,6 @@ def __init__(self, cindex_type: _ClangType): @property def is_const(self) -> bool: - import warnings - warnings.warn("is_const is deprecated, use is_const_qualified() instead", DeprecationWarning, stacklevel=2) return self.is_const_qualified() @@ -148,22 +147,16 @@ def __init__(self, cursor: Cursor): @property def is_const(self) -> bool: - import warnings - warnings.warn("is_const is deprecated, use is_const_method() instead", DeprecationWarning, stacklevel=2) return self.is_const_method() @property def is_virtual(self) -> bool: - import warnings - warnings.warn("is_virtual is deprecated, use is_virtual_method() instead", DeprecationWarning, stacklevel=2) return self.is_virtual_method() @property def is_pure_virtual(self) -> bool: - import warnings - warnings.warn( "is_pure_virtual is deprecated, use is_pure_virtual_method() instead", DeprecationWarning, stacklevel=2 )