-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add typecheck tests for dataclass attributes
- Loading branch information
1 parent
9cc3ffa
commit 97e467d
Showing
3 changed files
with
129 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
"""Tests for dataclass attributes with basic type annotations. | ||
Tests for regressions from https://github.com/PyCQA/astroid/pull/1126 | ||
""" | ||
# pylint: disable=missing-docstring,too-few-public-methods,pointless-statement,redefined-builtin, fixme | ||
from dataclasses import dataclass | ||
from typing import Callable, Dict, List, Optional | ||
|
||
|
||
class Dummy: | ||
pass | ||
|
||
|
||
@dataclass | ||
class MyClass: | ||
# Attribute inference does not support Optional, so Uninferable is yielded | ||
# This should not trigger any type errors from pylint | ||
attr0: Optional[Dummy] | ||
|
||
attr1: int | ||
attr2: str | ||
attr3: Callable[[int], int] | ||
attr4: List[int] | ||
attr5: Dict[str, str] | ||
|
||
|
||
obj = MyClass(None, 1, 'hi', lambda x: x, [], {}) | ||
|
||
lst = [0, 1, 2] | ||
print(lst[obj.attr0]) | ||
print(lst[obj.attr1]) | ||
print(lst[obj.attr2]) # [invalid-sequence-index] | ||
|
||
print(lst[obj.attr0::]) | ||
print(lst[obj.attr1::]) | ||
print(lst[obj.attr2::]) # [invalid-slice-index] | ||
|
||
obj.attr0(100) | ||
obj.attr1(100) # [not-callable] | ||
obj.attr3(100) | ||
|
||
print(-obj.attr0) | ||
print(-obj.attr1) | ||
print(-obj.attr2) # [invalid-unary-operand-type] | ||
|
||
print(1 + obj.attr0) | ||
print(1 + obj.attr1) | ||
print(1 + obj.attr2) # Should be an error here once unsupported-binary-operation is enabled | ||
|
||
print(1 in obj.attr0) | ||
print(1 in obj.attr1) # [unsupported-membership-test] | ||
print(1 in obj.attr4) | ||
print('hi' in obj.attr5) | ||
|
||
print(obj.attr0[1]) | ||
print(obj.attr1[1]) # [unsubscriptable-object] | ||
print(obj.attr4[1]) | ||
print(obj.attr5['hi']) | ||
|
||
obj.attr0[1] = 1 | ||
obj.attr1[1] = 1 # [unsupported-assignment-operation] | ||
obj.attr4[1] = 1 | ||
obj.attr5['hi'] = 'bye' | ||
|
||
del obj.attr0[1] | ||
del obj.attr1[1] # [unsupported-delete-operation] | ||
del obj.attr4[1] | ||
del obj.attr5['hi'] | ||
|
||
|
||
class Manager: | ||
def __enter__(self): | ||
pass | ||
|
||
def __exit__(self, type_, value, traceback): | ||
pass | ||
|
||
|
||
@dataclass | ||
class MyClass2: | ||
attr0: Optional[Dummy] | ||
attr1: Manager | ||
attr2: str | ||
|
||
|
||
obj2 = MyClass2(None, Manager(), 'hi') | ||
with obj2.attr0: | ||
pass | ||
with obj2.attr1: | ||
pass | ||
with obj2.attr2: # [not-context-manager] | ||
pass | ||
|
||
|
||
class Test1(metaclass=obj.attr0): | ||
pass | ||
|
||
|
||
class Test2(metaclass=obj.attr1): # [invalid-metaclass] | ||
pass | ||
|
||
|
||
{}[obj.attr0] = 1 | ||
{}[obj.attr1] = 1 | ||
{}[obj.attr5] = 1 # [unhashable-dict-key] | ||
|
||
for k, v in obj.attr5: # TODO: Should be an dict-iter-missing-items error | ||
print(k, v) | ||
|
||
__name__ = obj.attr0 | ||
__name__ = obj.attr1 # TODO: Should be a non-str-assignment-to-dunder-name error | ||
__name__ = obj.attr2 | ||
|
||
print(isinstance(1, obj.attr0)) | ||
print(isinstance(1, obj.attr1)) # [isinstance-second-argument-not-valid-type] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[testoptions] | ||
min_pyver=3.7 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
invalid-sequence-index:32:6::Sequence index is not an int, slice, or instance with __index__:HIGH | ||
invalid-slice-index:36:0::Slice index is not an int, None, or instance with __index__:HIGH | ||
not-callable:39:0::obj.attr1 is not callable:HIGH | ||
invalid-unary-operand-type:44:6::"bad operand type for unary -: str":HIGH | ||
unsupported-membership-test:51:11::Value 'obj.attr1' doesn't support membership test:HIGH | ||
unsubscriptable-object:56:6::Value 'obj.attr1' is unsubscriptable:HIGH | ||
unsupported-assignment-operation:61:0::'obj.attr1' does not support item assignment:HIGH | ||
unsupported-delete-operation:66:4::'obj.attr1' does not support item deletion:HIGH | ||
not-context-manager:91:0::Context manager 'str' doesn't implement __enter__ and __exit__.:HIGH | ||
invalid-metaclass:99:0:Test2:"Invalid metaclass '\n\nclass int(object):\n """"""int([x]) -> integer\nint(x, base=10) -> integer\n\nConvert a number or string to an integer, or return 0 if no arguments\nare given. If x is a number, return x.__int__(). For floating point\nnumbers, this truncates towards zero.\n\nIf x is not a number or if base is given, then x must be a string,\nbytes, or bytearray instance representing an integer literal in the\ngiven base. The literal can be preceded by \'+\' or \'-\' and be surrounded\nby whitespace. The base defaults to 10. Valid bases are 0 and 2-36.\nBase 0 means to interpret the base from the string as an integer literal.\n>>> int(\'0b100\', base=0)\n4""""""\n \'int\'\n \'int\'\n \n def __abs__():\n """"""abs(self)""""""\n \n \n def __add__():\n """"""Return self+value.""""""\n \n \n def __and__():\n """"""Return self&value.""""""\n \n \n def __bool__():\n """"""self != 0""""""\n \n \n def __ceil__():\n """"""Ceiling of an Integral returns itself.""""""\n \n \n def __delattr__():\n """"""Implement delattr(self, name).""""""\n \n \n def __dir__():\n """"""Default dir() implementation.""""""\n \n \n def __divmod__():\n """"""Return divmod(self, value).""""""\n \n \n def __eq__():\n """"""Return self==value.""""""\n \n \n def __float__():\n """"""float(self)""""""\n \n \n def __floor__():\n """"""Flooring an Integral returns itself.""""""\n \n \n def __floordiv__():\n """"""Return self//value.""""""\n \n \n def __format__():\n \n \n def __ge__():\n """"""Return self>=value.""""""\n \n \n def __getattribute__():\n """"""Return getattr(self, name).""""""\n \n \n def __getnewargs__():\n \n \n def __gt__():\n """"""Return self>value.""""""\n \n \n def __hash__():\n """"""Return hash(self).""""""\n \n \n def __index__():\n """"""Return self converted to an integer, if self is suitable for use as an index into a list.""""""\n \n \n def __init__():\n """"""Initialize self. See help(type(self)) for accurate signature.""""""\n \n \n def __int__():\n """"""int(self)""""""\n \n \n def __invert__():\n """"""~self""""""\n \n \n def __le__():\n """"""Return self<=value.""""""\n \n \n def __lshift__():\n """"""Return self<<value.""""""\n \n \n def __lt__():\n """"""Return self<value.""""""\n \n \n def __mod__():\n """"""Return self%value.""""""\n \n \n def __mul__():\n """"""Return self*value.""""""\n \n \n def __ne__():\n """"""Return self!=value.""""""\n \n \n def __neg__():\n """"""-self""""""\n \n \n def __new__():\n """"""Create and return a new object. See help(type) for accurate signature.""""""\n \n \n def __or__():\n """"""Return self|value.""""""\n \n \n def __pos__():\n """"""+self""""""\n \n \n def __pow__():\n """"""Return pow(self, value, mod).""""""\n \n \n def __radd__():\n """"""Return value+self.""""""\n \n \n def __rand__():\n """"""Return value&self.""""""\n \n \n def __rdivmod__():\n """"""Return divmod(value, self).""""""\n \n \n def __reduce__():\n """"""Helper for pickle.""""""\n \n \n def __reduce_ex__():\n """"""Helper for pickle.""""""\n \n \n def __repr__():\n """"""Return repr(self).""""""\n \n \n def __rfloordiv__():\n """"""Return value//self.""""""\n \n \n def __rlshift__():\n """"""Return value<<self.""""""\n \n \n def __rmod__():\n """"""Return value%self.""""""\n \n \n def __rmul__():\n """"""Return value*self.""""""\n \n \n def __ror__():\n """"""Return value|self.""""""\n \n \n def __round__():\n """"""Rounding an Integral returns itself.\nRounding with an ndigits argument also returns an integer.""""""\n \n \n def __rpow__():\n """"""Return pow(value, self, mod).""""""\n \n \n def __rrshift__():\n """"""Return value>>self.""""""\n \n \n def __rshift__():\n """"""Return self>>value.""""""\n \n \n def __rsub__():\n """"""Return value-self.""""""\n \n \n def __rtruediv__():\n """"""Return value/self.""""""\n \n \n def __rxor__():\n """"""Return value^self.""""""\n \n \n def __setattr__():\n """"""Implement setattr(self, name, value).""""""\n \n \n def __sizeof__():\n """"""Returns size in memory, in bytes.""""""\n \n \n def __str__():\n """"""Return str(self).""""""\n \n \n def __sub__():\n """"""Return self-value.""""""\n \n \n def __subclasshook__():\n """"""Abstract classes can override this to customize issubclass().\n\nThis is invoked early on by abc.ABCMeta.__subclasscheck__().\nIt should return True, False or NotImplemented. If it returns\nNotImplemented, the normal algorithm is used. Otherwise, it\noverrides the normal algorithm (and the outcome is cached).\n""""""\n \n \n def __truediv__():\n """"""Return self/value.""""""\n \n \n def __trunc__():\n """"""Truncating an Integral returns itself.""""""\n \n \n def __xor__():\n """"""Return self^value.""""""\n \n \n def as_integer_ratio():\n """"""Return integer ratio.\n\nReturn a pair of integers, whose ratio is exactly equal to the original int\nand with a positive denominator.\n\n>>> (10).as_integer_ratio()\n(10, 1)\n>>> (-10).as_integer_ratio()\n(-10, 1)\n>>> (0).as_integer_ratio()\n(0, 1)""""""\n \n \n def bit_length():\n """"""Number of bits necessary to represent self in binary.\n\n>>> bin(37)\n\'0b100101\'\n>>> (37).bit_length()\n6""""""\n \n \n def conjugate():\n """"""Returns self, the complex conjugate of any int.""""""\n \n \n \n class denominator:\n """"""the denominator of a rational number in lowest terms""""""\n \'denominator\'\n \'denominator\'\n \n \n \n class imag:\n """"""the imaginary part of a complex number""""""\n \'imag\'\n \'imag\'\n \n \n \n class numerator:\n """"""the numerator of a rational number in lowest terms""""""\n \'numerator\'\n \'numerator\'\n \n \n \n class real:\n """"""the real part of a complex number""""""\n \'real\'\n \'real\'\n \n \n def to_bytes():\n """"""Return an array of bytes representing an integer.\n\n length\n Length of bytes object to use. An OverflowError is raised if the\n integer is not representable with the given number of bytes.\n byteorder\n The byte order used to represent the integer. If byteorder is \'big\',\n the most significant byte is at the beginning of the byte array. If\n byteorder is \'little\', the most significant byte is at the end of the\n byte array. To request the native byte order of the host system, use\n `sys.byteorder\' as the byte order value.\n signed\n Determines whether two\'s complement is used to represent the integer.\n If signed is False and a negative integer is given, an OverflowError\n is raised.""""""\n \n' used":HIGH | ||
unhashable-dict-key:105:0::Dict key is unhashable:HIGH | ||
isinstance-second-argument-not-valid-type:115:6::Second argument of isinstance is not a type:HIGH |