Skip to content

Commit

Permalink
Add typecheck tests for dataclass attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
david-yz-liu committed Aug 19, 2021
1 parent 9cc3ffa commit 97e467d
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 0 deletions.
115 changes: 115 additions & 0 deletions tests/functional/d/deprecated/dataclass_typecheck.py
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]
2 changes: 2 additions & 0 deletions tests/functional/d/deprecated/dataclass_typecheck.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.7
12 changes: 12 additions & 0 deletions tests/functional/d/deprecated/dataclass_typecheck.txt
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

0 comments on commit 97e467d

Please sign in to comment.