Skip to content

Commit

Permalink
Fix Stubgen's behavior for Instance Variables in C extensions (#12524)
Browse files Browse the repository at this point in the history
It is not necessary for instance variables to have the fget attrbute
(e.g. instance variable in a C class in an extension) but
inspect.isdatadescriptor return True as expected, hence we update
the 'is_c_property' check.

Since special attributes (like __dict__ etc) also passes 'is_c_property'
check, we ignore all such special attributes in
'generate_c_property_stub' while creating the contents of stub file.

Also, 'is_c_property_readonly' assumed that the property would always
have 'fset' attribute which again is not true for instance variables
in C extension. Hence make the check defensive by first checking if
'fset' attribute even exists or not.

Fixes #12150.
  • Loading branch information
shubz1998 authored Apr 11, 2022
1 parent 6a24e1d commit 74df7fb
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 10 deletions.
8 changes: 6 additions & 2 deletions mypy/stubgenc.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,11 @@ def is_c_classmethod(obj: object) -> bool:


def is_c_property(obj: object) -> bool:
return inspect.isdatadescriptor(obj) and hasattr(obj, 'fget')
return inspect.isdatadescriptor(obj) or hasattr(obj, 'fget')


def is_c_property_readonly(prop: Any) -> bool:
return prop.fset is None
return hasattr(prop, 'fset') and prop.fset is None


def is_c_type(obj: object) -> bool:
Expand Down Expand Up @@ -287,6 +287,10 @@ def infer_prop_type(docstr: Optional[str]) -> Optional[str]:
else:
return None

# Ignore special properties/attributes.
if name.startswith('__') and name.endswith('__'):
return

inferred = infer_prop_type(getattr(obj, '__doc__', None))
if not inferred:
fget = getattr(obj, 'fget', None)
Expand Down
34 changes: 30 additions & 4 deletions mypy/test/teststubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
)
from mypy.stubutil import walk_packages, remove_misplaced_type_comments, common_dir_prefix
from mypy.stubgenc import (
generate_c_type_stub, infer_method_sig, generate_c_function_stub, generate_c_property_stub
generate_c_type_stub, infer_method_sig, generate_c_function_stub, generate_c_property_stub,
is_c_property_readonly
)
from mypy.stubdoc import (
parse_signature, parse_all_signatures, build_signature, find_unique_signatures,
Expand Down Expand Up @@ -868,9 +869,34 @@ def get_attribute(self) -> None:
pass
attribute = property(get_attribute, doc="")

output: List[str] = []
generate_c_property_stub('attribute', TestClass.attribute, [], [], output, readonly=True)
assert_equal(output, ['@property', 'def attribute(self) -> str: ...'])
readwrite_properties: List[str] = []
readonly_properties: List[str] = []
generate_c_property_stub('attribute', TestClass.attribute, [],
readwrite_properties, readonly_properties,
is_c_property_readonly(TestClass.attribute))
assert_equal(readwrite_properties, [])
assert_equal(readonly_properties, ['@property', 'def attribute(self) -> str: ...'])

def test_generate_c_property_with_rw_property(self) -> None:
class TestClass:
def __init__(self) -> None:
self._attribute = 0

@property
def attribute(self) -> int:
return self._attribute

@attribute.setter
def attribute(self, value: int) -> None:
self._attribute = value

readwrite_properties: List[str] = []
readonly_properties: List[str] = []
generate_c_property_stub("attribute", type(TestClass.attribute), [],
readwrite_properties, readonly_properties,
is_c_property_readonly(TestClass.attribute))
assert_equal(readwrite_properties, ['attribute: Any'])
assert_equal(readonly_properties, [])

def test_generate_c_type_with_single_arg_generic(self) -> None:
class TestClass:
Expand Down
4 changes: 0 additions & 4 deletions test-data/stubgen/pybind11_mypy_demo/basics.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ PI: float

class Point:
class AngleUnit:
__doc__: ClassVar[str] = ... # read-only
__members__: ClassVar[dict] = ... # read-only
__entries: ClassVar[dict] = ...
degree: ClassVar[Point.AngleUnit] = ...
radian: ClassVar[Point.AngleUnit] = ...
Expand All @@ -22,8 +20,6 @@ class Point:
def name(self) -> str: ...

class LengthUnit:
__doc__: ClassVar[str] = ... # read-only
__members__: ClassVar[dict] = ... # read-only
__entries: ClassVar[dict] = ...
inch: ClassVar[Point.LengthUnit] = ...
mm: ClassVar[Point.LengthUnit] = ...
Expand Down

0 comments on commit 74df7fb

Please sign in to comment.