From b882dd3ebf3e716001b1e9138f9975c77adf84b1 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Thu, 13 May 2021 03:12:16 +0200 Subject: [PATCH 1/4] Add `__attrs_attrs__` and `fields` to attrs --- mypy/plugins/attrs.py | 76 ++++++++++++++++++++++++++++++++++++++--- mypy/plugins/default.py | 4 ++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index ed32f218d004..2f2b02a32998 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -1,5 +1,6 @@ """Plugin for supporting the attrs library (http://www.attrs.org)""" +from mypy.lookup import lookup_fully_qualified from mypy.ordered_dict import OrderedDict from typing import Optional, Dict, List, cast, Tuple, Iterable @@ -9,19 +10,19 @@ from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.fixup import lookup_qualified_stnode from mypy.nodes import ( - Context, Argument, Var, ARG_OPT, ARG_POS, TypeInfo, AssignmentStmt, - TupleExpr, ListExpr, NameExpr, CallExpr, RefExpr, FuncDef, + Block, ClassDef, Context, Argument, SymbolTable, Var, ARG_OPT, ARG_POS, TypeInfo, + AssignmentStmt, TupleExpr, ListExpr, NameExpr, CallExpr, RefExpr, FuncDef, is_class_var, TempNode, Decorator, MemberExpr, Expression, SymbolTableNode, MDEF, JsonDict, OverloadedFuncDef, ARG_NAMED_OPT, ARG_NAMED, TypeVarExpr, PlaceholderNode ) -from mypy.plugin import SemanticAnalyzerPluginInterface +from mypy.plugin import FunctionContext, SemanticAnalyzerPluginInterface from mypy.plugins.common import ( _get_argument, _get_bool_argument, _get_decorator_bool_argument, add_method, deserialize_and_fixup_type ) from mypy.types import ( - Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarDef, TypeVarType, + Instance, Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarDef, TypeVarType, Overloaded, UnionType, FunctionLike, get_proper_type ) from mypy.typeops import make_simplified_union, map_type_from_supertype @@ -257,6 +258,14 @@ def _get_decorator_optional_bool_argument( return default +def _make_var(n: str, i: Type, fullname: Optional[str] = None, is_classvar: bool = False) -> Var: + res = Var(n, i) + res.info = i.type + res.is_classvar = is_classvar + res._fullname = fullname + return res + + def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', auto_attribs_default: Optional[bool] = False, frozen_default: bool = False) -> None: @@ -319,6 +328,51 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', if frozen: _make_frozen(ctx, attributes) + # Set up the `__attrs_attrs__` class variable. + attr_type: TypeInfo = lookup_fully_qualified("attr.Attribute", + ctx.api.modules, + raise_on_missing=True).node + tuple_type: TypeInfo = lookup_fully_qualified("builtins.tuple", + ctx.api.modules, + raise_on_missing=True).node + + sym_table = SymbolTable( + { + a.name: SymbolTableNode( + MDEF, + _make_var( + a.name, + Instance(attr_type, + # We want to support future attrs versions with a 2-param Attribute + [Instance(ctx.cls.info, []), a.argument(ctx).type_annotation] + if len(attr_type.type_vars) == 2 else + [a.argument(ctx).type_annotation]), + ), + ) + for a in attributes + }, + ) + cd = ClassDef(f"{info.name}Attributes", Block([])) + cd.fullname = f"{info.name}Attributes" + ti = TypeInfo( + sym_table, + cd, + "attr", + ) + ti.is_named_tuple = True + ti.mro = [ti, tuple_type] + ti.type_vars = [] + attributes_type = Instance( + ti, + [], + ) + fn = ctx.cls.fullname + ".__attrs_attrs__" + ctx.cls.info.names["__attrs_attrs__"] = SymbolTableNode( + MDEF, + _make_var("__attrs_attrs__", attributes_type, fullname=fn, is_classvar=True), + plugin_generated=True + ) + def _get_frozen(ctx: 'mypy.plugin.ClassDefContext', frozen_default: bool) -> bool: """Return whether this class is frozen.""" @@ -732,3 +786,17 @@ def add_method(self, """ self_type = self_type if self_type is not None else self.self_type add_method(self.ctx, method_name, args, ret_type, self_type, tvd) + + +def adjust_fields(fc: FunctionContext) -> Type: + # We fish out the attrs class out of the return type. + try: + attrs_class: TypeInfo = fc.arg_types[0][0].ret_type.type + except Exception: + fc.api.fail("Argument is not an attrs class", fc.context) + return AnyType(TypeOfAny.from_error) + + stn = attrs_class.get("__attrs_attrs__") + if stn is not None: + return stn.type + return None diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 552a52c5c860..2d8d6e554e4b 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -22,7 +22,7 @@ class DefaultPlugin(Plugin): def get_function_hook(self, fullname: str ) -> Optional[Callable[[FunctionContext], Type]]: - from mypy.plugins import ctypes + from mypy.plugins import attrs, ctypes if fullname == 'contextlib.contextmanager': return contextmanager_callback @@ -30,6 +30,8 @@ def get_function_hook(self, fullname: str return open_callback elif fullname == 'ctypes.Array': return ctypes.array_constructor_callback + elif fullname == 'attr.fields': + return attrs.adjust_fields return None def get_method_signature_hook(self, fullname: str From 5fd5098bb54e6e457bbbc32e380f8c88ede140a3 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sun, 16 May 2021 02:41:26 +0200 Subject: [PATCH 2/4] Some corner cases --- mypy/plugins/attrs.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 2f2b02a32998..448d7a4f808c 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -329,9 +329,17 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', _make_frozen(ctx, attributes) # Set up the `__attrs_attrs__` class variable. - attr_type: TypeInfo = lookup_fully_qualified("attr.Attribute", - ctx.api.modules, - raise_on_missing=True).node + attr_symbol_tn = lookup_fully_qualified("attr.Attribute", + ctx.api.modules) + + if attr_symbol_tn is None or attr_symbol_tn.node is None: + # Not expected to happen since the absence of attrs is handled elsewhere, + # but we check just in case. Without this we cannot generate + # the `__attrs_attrs__` variable, so we don't. + return + + attr_type = attr_symbol_tn.node + tuple_type: TypeInfo = lookup_fully_qualified("builtins.tuple", ctx.api.modules, raise_on_missing=True).node @@ -789,6 +797,7 @@ def add_method(self, def adjust_fields(fc: FunctionContext) -> Type: + """Generate the proper return value for an `attr.fields` invocation.""" # We fish out the attrs class out of the return type. try: attrs_class: TypeInfo = fc.arg_types[0][0].ret_type.type @@ -796,7 +805,10 @@ def adjust_fields(fc: FunctionContext) -> Type: fc.api.fail("Argument is not an attrs class", fc.context) return AnyType(TypeOfAny.from_error) + # We've placed the appropriate instance on the type already, when we analyzed it. stn = attrs_class.get("__attrs_attrs__") if stn is not None: return stn.type - return None + + # Should never happen. + return AnyType(TypeOfAny.from_error) From 0a0c4a00aa8d03294580ecbec4b53a3d2dc57650 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Wed, 19 May 2021 23:14:01 +0200 Subject: [PATCH 3/4] Test fixes --- mypy/plugins/attrs.py | 22 ++-- test-data/unit/check-attr.test | 145 +++++++++++----------- test-data/unit/check-flags.test | 3 + test-data/unit/check-incremental.test | 23 ++-- test-data/unit/fixtures/attr.pyi | 11 +- test-data/unit/lib-stub/attr/__init__.pyi | 37 +++++- 6 files changed, 151 insertions(+), 90 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 448d7a4f808c..94c15fcfb82d 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -258,7 +258,8 @@ def _get_decorator_optional_bool_argument( return default -def _make_var(n: str, i: Type, fullname: Optional[str] = None, is_classvar: bool = False) -> Var: +def _make_var(n: str, i: Instance, fullname: Optional[str] = None, is_classvar: bool = False) -> Var: + """Create a `Var` for a symbol table.""" res = Var(n, i) res.info = i.type res.is_classvar = is_classvar @@ -330,19 +331,26 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', # Set up the `__attrs_attrs__` class variable. attr_symbol_tn = lookup_fully_qualified("attr.Attribute", - ctx.api.modules) + ctx.api.modules, raise_on_missing=False) if attr_symbol_tn is None or attr_symbol_tn.node is None: # Not expected to happen since the absence of attrs is handled elsewhere, # but we check just in case. Without this we cannot generate # the `__attrs_attrs__` variable, so we don't. return - + attr_type = attr_symbol_tn.node tuple_type: TypeInfo = lookup_fully_qualified("builtins.tuple", ctx.api.modules, raise_on_missing=True).node + + # The `__attrs_attrs__` field is a special namedtuple, unique for each + # class, containing all gathered attributes. + # It's a namedtuple so attributes can be accessed by name: + # `X.__attrs_attrs__.a` points to the attribute for field `X.a` + # Both the namedtuple class and instance of it are generated at runtime + # by attrs, so we replicate that behavior here. sym_table = SymbolTable( { @@ -352,20 +360,20 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', a.name, Instance(attr_type, # We want to support future attrs versions with a 2-param Attribute - [Instance(ctx.cls.info, []), a.argument(ctx).type_annotation] + [Instance(ctx.cls.info, []), info[a.name].type] if len(attr_type.type_vars) == 2 else - [a.argument(ctx).type_annotation]), + [info[a.name].type]), ), ) for a in attributes }, ) cd = ClassDef(f"{info.name}Attributes", Block([])) - cd.fullname = f"{info.name}Attributes" + cd.fullname = f"builtins.{info.name}Attributes" # To match the runtime semantics ti = TypeInfo( sym_table, cd, - "attr", + "builtins", ) ti.is_named_tuple = True ti.mro = [ti, tuple_type] diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index 1475d2cf6142..98ae21c07a1f 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -15,7 +15,8 @@ A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) A(1, [2], '3', 4, 5) # E: Too many arguments for "A" -[builtins fixtures/list.pyi] +reveal_type(A.__attrs_attrs__) # N: Revealed type is "builtins.AAttributes" +[builtins fixtures/attr.pyi] [case testAttrsAnnotated] import attr @@ -33,7 +34,7 @@ A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" A(1, [2], '3', 4, 5) # E: Too many arguments for "A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsPython2Annotations] import attr @@ -51,7 +52,7 @@ A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" A(1, [2], '3', 4, 5) # E: Too many arguments for "A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsAutoAttribs] import attr @@ -69,7 +70,7 @@ A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" A(1, [2], '3', 4, 5) # E: Too many arguments for "A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUntypedNoUntypedDefs] # flags: --disallow-untyped-defs @@ -81,7 +82,7 @@ class A: c = attr.ib(18) # E: Need type annotation for "c" _d = attr.ib(validator=None, default=18) # E: Need type annotation for "_d" E = 18 -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsWrongReturnValue] import attr @@ -105,7 +106,7 @@ class D: x = attr.ib(8, type=int) def foo(self) -> str: return self.x # E: Incompatible return value type (got "int", expected "str") -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsSeriousNames] from attr import attrib, attrs @@ -122,7 +123,7 @@ A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" A(1, [2], '3', 4, 5) # E: Too many arguments for "A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsDefaultErrors] import attr @@ -146,7 +147,7 @@ class D: @x.default def foo(self): return 17 -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsNotBooleans] import attr @@ -154,7 +155,7 @@ x = True @attr.s(cmp=x) # E: "cmp" argument must be True or False. class A: a = attr.ib(init=x) # E: "init" argument must be True or False. -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsInitFalse] from attr import attrib, attrs @@ -168,7 +169,7 @@ reveal_type(A) # N: Revealed type is "def () -> __main__.A" A() A(1, [2]) # E: Too many arguments for "A" A(1, [2], '3', 4) # E: Too many arguments for "A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsInitAttribFalse] from attr import attrib, attrs @@ -177,7 +178,7 @@ class A: a = attrib(init=False) b = attrib() reveal_type(A) # N: Revealed type is "def (b: Any) -> __main__.A" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsCmpTrue] from attr import attrib, attrs @@ -304,7 +305,7 @@ class B: class C(A, B): c: bool = attr.ib() reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.str, c: builtins.bool) -> __main__.C" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsNestedInClasses] import attr @@ -316,7 +317,7 @@ class C: x: int = attr.ib() reveal_type(C) # N: Revealed type is "def (y: Any) -> __main__.C" reveal_type(C.D) # N: Revealed type is "def (x: builtins.int) -> __main__.C.D" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsInheritanceOverride] import attr @@ -339,7 +340,7 @@ class C(B): reveal_type(A) # N: Revealed type is "def (a: builtins.int, x: builtins.int) -> __main__.A" reveal_type(B) # N: Revealed type is "def (a: builtins.int, b: builtins.str, x: builtins.int =) -> __main__.B" reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.str, c: builtins.bool, x: builtins.int) -> __main__.C" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsTypeEquals] import attr @@ -349,7 +350,7 @@ class A: a = attr.ib(type=int) b = attr.ib(18, type=int) reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.int =) -> __main__.A" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsFrozen] import attr @@ -360,7 +361,7 @@ class A: a = A(5) a.a = 16 # E: Property "a" defined in "A" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsNextGenFrozen] from attr import frozen, field @@ -370,7 +371,7 @@ class A: a = A(5) a.a = 16 # E: Property "a" defined in "A" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsNextGenDetect] from attr import define, field @@ -398,9 +399,7 @@ reveal_type(B) # N: Revealed type is "def (a: builtins.int) -> __main__.B" reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: Any) -> __main__.C" reveal_type(D) # N: Revealed type is "def (b: Any) -> __main__.D" -[builtins fixtures/bool.pyi] - - +[builtins fixtures/attr.pyi] [case testAttrsDataClass] import attr @@ -415,7 +414,7 @@ class A: F: ClassVar[int] = 22 reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.str], c: builtins.str =, d: builtins.int =) -> __main__.A" A(1, ['2']) -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsTypeAlias] from typing import List @@ -427,7 +426,7 @@ class A: x: Alias y: Alias2 = attr.ib() reveal_type(A) # N: Revealed type is "def (x: builtins.list[builtins.int], y: builtins.list[builtins.str]) -> __main__.A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsGeneric] from typing import TypeVar, Generic, List @@ -452,7 +451,7 @@ reveal_type(a.y) # N: Revealed type is "builtins.int*" A(['str'], 7) # E: Cannot infer type argument 1 of "A" A([1], '2') # E: Cannot infer type argument 1 of "A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUntypedGenericInheritance] @@ -473,7 +472,7 @@ sub = Sub(attr=1) reveal_type(sub) # N: Revealed type is "__main__.Sub" reveal_type(sub.attr) # N: Revealed type is "Any" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsGenericInheritance] @@ -499,7 +498,7 @@ sub_str = Sub[str](attr='ok') reveal_type(sub_str) # N: Revealed type is "__main__.Sub[builtins.str*]" reveal_type(sub_str.attr) # N: Revealed type is "builtins.str*" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsGenericInheritance] @@ -526,7 +525,7 @@ reveal_type(sub.one) # N: Revealed type is "builtins.int*" reveal_type(sub.two) # N: Revealed type is "builtins.str*" reveal_type(sub.three) # N: Revealed type is "builtins.float*" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsMultiGenericInheritance] @@ -554,7 +553,7 @@ reveal_type(sub) # N: Revealed type is "__main__.Sub" reveal_type(sub.base_attr) # N: Revealed type is "builtins.int*" reveal_type(sub.middle_attr) # N: Revealed type is "builtins.str*" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsGenericClassmethod] @@ -568,7 +567,7 @@ class A(Generic[T]): def clsmeth(cls) -> None: reveal_type(cls) # N: Revealed type is "Type[__main__.A[T`1]]" -[builtins fixtures/classmethod.pyi] +[builtins fixtures/attr.pyi] [case testAttrsForwardReference] import attr @@ -583,7 +582,7 @@ class B: reveal_type(A) # N: Revealed type is "def (parent: __main__.B) -> __main__.A" reveal_type(B) # N: Revealed type is "def (parent: __main__.A) -> __main__.B" A(B(None)) -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsForwardReferenceInClass] import attr @@ -598,7 +597,7 @@ class A: reveal_type(A) # N: Revealed type is "def (parent: __main__.A.B) -> __main__.A" reveal_type(A.B) # N: Revealed type is "def (parent: __main__.A) -> __main__.A.B" A(A.B(None)) -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsImporting] from helper import A @@ -609,7 +608,7 @@ import attr class A: a: int b: str = attr.ib() -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsOtherMethods] import attr @@ -629,7 +628,7 @@ class A: reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> __main__.A" a = A.new() reveal_type(a.foo) # N: Revealed type is "def () -> builtins.int" -[builtins fixtures/classmethod.pyi] +[builtins fixtures/attr.pyi] [case testAttrsOtherOverloads] import attr @@ -661,7 +660,7 @@ class A: reveal_type(A.foo(3)) # N: Revealed type is "builtins.int" reveal_type(A.foo("foo")) # N: Revealed type is "builtins.str" -[builtins fixtures/classmethod.pyi] +[builtins fixtures/attr.pyi] [case testAttrsDefaultDecorator] import attr @@ -673,7 +672,7 @@ class C(object): def name_does_not_matter(self): return self.x + 1 C() -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsValidatorDecorator] import attr @@ -686,7 +685,7 @@ class C(object): raise ValueError("x must be smaller or equal to 42") C(42) C(43) -[builtins fixtures/exception.pyi] +[builtins fixtures/attr.pyi] [case testAttrsLocalVariablesInClassMethod] import attr @@ -699,7 +698,8 @@ class A: a = foo b = a return cls(a, b) -[builtins fixtures/classmethod.pyi] + +[builtins fixtures/attr.pyi] [case testAttrsUnionForward] import attr @@ -718,7 +718,8 @@ reveal_type(A) # N: Revealed type is "def (frob: builtins.list[Union[__main__.A reveal_type(B) # N: Revealed type is "def () -> __main__.B" A([B()]) -[builtins fixtures/list.pyi] + +[builtins fixtures/attr.pyi] [case testAttrsUsingConvert] import attr @@ -733,7 +734,7 @@ class C: # Because of the convert the __init__ takes an int, but the variable is a str. reveal_type(C) # N: Revealed type is "def (x: builtins.int) -> __main__.C" reveal_type(C(15).x) # N: Revealed type is "builtins.str" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUsingConverter] import attr @@ -753,7 +754,7 @@ reveal_type(C(15, 16).x) # N: Revealed type is "builtins.str" [file helper.py] def converter(s:int) -> str: return 'hello' -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUsingConvertAndConverter] import attr @@ -765,7 +766,7 @@ def converter(s:int) -> str: class C: x: str = attr.ib(converter=converter, convert=converter) # E: Can't pass both `convert` and `converter`. -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUsingBadConverter] # flags: --no-strict-optional @@ -792,7 +793,7 @@ main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; main:17: error: Cannot determine __init__ type from converter main:17: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], int]" main:18: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> __main__.A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUsingBadConverterReprocess] # flags: --no-strict-optional @@ -820,7 +821,7 @@ main:17: error: Argument "converter" has incompatible type "Callable[[], str]"; main:18: error: Cannot determine __init__ type from converter main:18: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], int]" main:19: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> __main__.A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUsingUnsupportedConverter] import attr @@ -836,7 +837,7 @@ class C: y: str = attr.ib(converter=lambda x: x) # E: Unsupported converter, only named functions and types are currently supported z: str = attr.ib(converter=factory(8)) # E: Unsupported converter, only named functions and types are currently supported reveal_type(C) # N: Revealed type is "def (x: Any, y: Any, z: Any) -> __main__.C" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUsingConverterAndSubclass] import attr @@ -855,7 +856,7 @@ class A(C): # Because of the convert the __init__ takes an int, but the variable is a str. reveal_type(A) # N: Revealed type is "def (x: builtins.int) -> __main__.A" reveal_type(A(15).x) # N: Revealed type is "builtins.str" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsUsingConverterWithTypes] from typing import overload @@ -910,7 +911,7 @@ B() <= 1 # E: Unsupported operand types for <= ("B" and "int") C() <= 1 # E: Unsupported operand types for <= ("C" and "int") D() <= 1 # E: Unsupported operand types for <= ("D" and "int") -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsComplexSuperclass] import attr @@ -926,7 +927,7 @@ class A(C): z: int = attr.ib(default=18) reveal_type(C) # N: Revealed type is "def (x: builtins.int =, y: builtins.int =) -> __main__.C" reveal_type(A) # N: Revealed type is "def (x: builtins.int =, y: builtins.int =, z: builtins.int =) -> __main__.A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsMultiAssign] import attr @@ -934,14 +935,14 @@ import attr class A: x, y, z = attr.ib(), attr.ib(type=int), attr.ib(default=17) reveal_type(A) # N: Revealed type is "def (x: Any, y: builtins.int, z: Any =) -> __main__.A" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsMultiAssign2] import attr @attr.s class A: x = y = z = attr.ib() # E: Too many names for one attribute -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsPrivateInit] import attr @@ -950,7 +951,7 @@ class C(object): _x = attr.ib(init=False, default=42) C() C(_x=42) # E: Unexpected keyword argument "_x" for "C" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsAutoMustBeAll] import attr @@ -962,7 +963,7 @@ class A: c = attr.ib() # E: Need type annotation for "c" d, e = attr.ib(), attr.ib() # E: Need type annotation for "d" # E: Need type annotation for "e" f = g = attr.ib() # E: Need type annotation for "f" # E: Need type annotation for "g" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsRepeatedName] import attr @@ -984,7 +985,7 @@ class C: b: int a: int = attr.ib() # E: Name "a" already defined on line 16 reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.int) -> __main__.C" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsNewStyleClassPy2] # flags: --py2 @@ -995,7 +996,7 @@ class Good(object): @attr.s class Bad: # E: attrs only works with new-style classes pass -[builtins_py2 fixtures/bool.pyi] +[builtins_py2 fixtures/attr.pyi] [case testAttrsAutoAttribsPy2] # flags: --py2 @@ -1003,7 +1004,8 @@ import attr @attr.s(auto_attribs=True) # E: auto_attribs is not supported in Python 2 class A(object): x = attr.ib() -[builtins_py2 fixtures/bool.pyi] + +[builtins_py2 fixtures/attr.pyi] [case testAttrsFrozenSubclass] import attr @@ -1046,7 +1048,8 @@ c = NonFrozenFrozen(1, 2) c.a = 17 # E: Property "a" defined in "NonFrozenFrozen" is read-only c.b = 17 # E: Property "b" defined in "NonFrozenFrozen" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] + [case testAttrsCallableAttributes] from typing import Callable import attr @@ -1070,6 +1073,7 @@ class FFrozen(F): def bar(self) -> bool: return self._cb(5, 6) [builtins fixtures/callable.pyi] +[builtins fixtures/dict.pyi] [case testAttrsWithFactory] from typing import List @@ -1081,14 +1085,14 @@ class A: x: List[int] = attr.ib(factory=list) y: int = attr.ib(factory=my_factory) A() -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsFactoryAndDefault] import attr @attr.s class A: x: int = attr.ib(factory=int, default=7) # E: Can't pass both `default` and `factory`. -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsFactoryBadReturn] import attr @@ -1098,7 +1102,7 @@ def my_factory() -> int: class A: x: int = attr.ib(factory=list) # E: Incompatible types in assignment (expression has type "List[T]", variable has type "int") y: str = attr.ib(factory=my_factory) # E: Incompatible types in assignment (expression has type "int", variable has type "str") -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsDefaultAndInit] import attr @@ -1111,7 +1115,7 @@ class C: d = attr.ib(init=False) # Ok because this attribute is init=False e = attr.ib() # E: Non-default attributes not allowed after default attributes. -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsOptionalConverter] # flags: --strict-optional @@ -1153,6 +1157,7 @@ class A: A() # E: Missing named argument "a" for "A" A(15) # E: Too many positional arguments for "A" A(a=15) + [builtins fixtures/attr.pyi] [case testAttrsKwOnlyClass] @@ -1194,7 +1199,6 @@ class D: D(b=17) [builtins fixtures/attr.pyi] - [case testAttrsKwOnlySubclass] import attr @attr.s @@ -1230,7 +1234,7 @@ class A(object): @attr.s class B(object): x = attr.ib(kw_only=True) # E: kw_only is not supported in Python 2 -[builtins_py2 fixtures/bool.pyi] +[builtins_py2 fixtures/attr.pyi] [case testAttrsDisallowUntypedWorksForward] # flags: --disallow-untyped-defs @@ -1245,7 +1249,7 @@ class C(List[C]): pass reveal_type(B) # N: Revealed type is "def (x: __main__.C) -> __main__.B" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testDisallowUntypedWorksForwardBad] # flags: --disallow-untyped-defs @@ -1256,7 +1260,7 @@ class B: x = attr.ib() # E: Need type annotation for "x" reveal_type(B) # N: Revealed type is "def (x: Any) -> __main__.B" -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsDefaultDecoratorDeferred] defer: Yes @@ -1271,7 +1275,7 @@ class C(object): return self.x + 1 class Yes: ... -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsValidatorDecoratorDeferred] defer: Yes @@ -1289,6 +1293,7 @@ C(43) class Yes: ... [builtins fixtures/exception.pyi] +[builtins fixtures/attr.pyi] [case testTypeInAttrUndefined] import attr @@ -1296,7 +1301,7 @@ import attr @attr.s class C: total = attr.ib(type=Bad) # E: Name "Bad" is not defined -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testTypeInAttrForwardInRuntime] import attr @@ -1308,7 +1313,7 @@ class C: reveal_type(C.total) # N: Revealed type is "__main__.Forward" C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "Forward" class Forward: ... -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testDefaultInAttrForward] import attr @@ -1322,7 +1327,7 @@ def func() -> int: ... C() C(1) C(1, 2) # E: Too many arguments for "C" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testTypeInAttrUndefinedFrozen] import attr @@ -1332,7 +1337,7 @@ class C: total = attr.ib(type=Bad) # E: Name "Bad" is not defined C(0).total = 1 # E: Property "total" defined in "C" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testTypeInAttrDeferredStar] import lib @@ -1350,7 +1355,7 @@ C() # E: Missing positional argument "total" in call to "C" C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" [file other.py] import lib -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] [case testAttrsDefaultsMroOtherFile] import a @@ -1374,7 +1379,7 @@ class A1: class A2: b: int = attr.ib() -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [case testAttrsInheritanceNoAnnotation] import attr @@ -1389,4 +1394,4 @@ class B(A): foo = x reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" -[builtins fixtures/bool.pyi] +[builtins fixtures/attr.pyi] diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 3bead7aed36f..b6d4a0c8670d 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -1193,6 +1193,7 @@ import attr class Unannotated: foo = attr.ib() +[builtins fixtures/attr.pyi] [case testDisallowIncompleteDefsAttrsWithAnnotations] # flags: --disallow-incomplete-defs import attr @@ -1201,6 +1202,7 @@ import attr class Annotated: bar: int = attr.ib() +[builtins fixtures/attr.pyi] [case testDisallowIncompleteDefsAttrsPartialAnnotations] # flags: --disallow-incomplete-defs import attr @@ -1210,6 +1212,7 @@ class PartiallyAnnotated: # E: Function is missing a type annotation for one or bar: int = attr.ib() baz = attr.ib() +[builtins fixtures/attr.pyi] [case testAlwaysTrueAlwaysFalseFlags] # flags: --always-true=YOLO --always-true=YOLO1 --always-false=BLAH1 --always-false BLAH --ignore-missing-imports from somewhere import YOLO, BLAH diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index ae39599a18c6..45ed00738354 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -2943,7 +2943,7 @@ class A: E = 7 F: ClassVar[int] = 22 -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] [out2] @@ -2964,7 +2964,8 @@ import attr class A: x: str = attr.ib(converter=converter) -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] + [out1] main:6: note: Revealed type is "def (x: builtins.int) -> __main__.B" @@ -2985,7 +2986,7 @@ import attr class A: x = attr.ib(type=int) -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] main:6: note: Revealed type is "def (x: builtins.int) -> __main__.B" [out2] @@ -3020,7 +3021,7 @@ class NoInit: class NoCmp: x: int = attr.ib() -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [rechecked] [stale] [out1] @@ -3114,7 +3115,7 @@ from a import A class B(A): y: int -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] [out2] main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int" @@ -3143,7 +3144,7 @@ from a import A class B(A): y: str -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int" @@ -3174,7 +3175,7 @@ import attr class C(A, B): c: bool = attr.ib() -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] [out2] @@ -3192,7 +3193,7 @@ import attr class A: x: int = attr.ib(converter=converter) -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] main:2: note: Revealed type is "def (x: Union[builtins.int, None]) -> a.a.A" [out2] @@ -3331,7 +3332,7 @@ class SubBB(SubBase): xx: int = attr.ib(converter=maybe_int) yy: str = attr.ib(converter=maybe_str) zz: bool = attr.ib(converter=bar.maybe_bool) -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] [out2] tmp/a.py:3: error: Argument 2 to "Base" has incompatible type "int"; expected "Optional[str]" @@ -3352,7 +3353,7 @@ def foo() -> None: class A: x: int = attr.ib(converter=foo) reveal_type(A) -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] main:8: note: Revealed type is "def (x: builtins.str) -> __main__.A@6" [out2] @@ -3443,7 +3444,7 @@ import attr class A: a: int = 6 -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out1] [out2] main:2: error: Argument 1 to "A" has incompatible type "int"; expected "str" diff --git a/test-data/unit/fixtures/attr.pyi b/test-data/unit/fixtures/attr.pyi index deb1906d931e..9224bf2970ad 100644 --- a/test-data/unit/fixtures/attr.pyi +++ b/test-data/unit/fixtures/attr.pyi @@ -1,5 +1,7 @@ # Builtins stub used to support @attr.s tests. -from typing import Union, overload +from typing import Union, overload, Mapping, Sequence, Generic, TypeVar + +T = TypeVar('T') class object: def __init__(self) -> None: pass @@ -25,3 +27,10 @@ class complex: class str: pass class unicode: pass class ellipsis: pass + +class dict(Mapping): pass +class tuple(Sequence[T], Generic): pass +class list(Sequence[T]): pass +class classmethod: pass +class BaseException: + def __init__(self, *args: object) -> None: ... diff --git a/test-data/unit/lib-stub/attr/__init__.pyi b/test-data/unit/lib-stub/attr/__init__.pyi index 475cfb7571a5..a4bd9fb6fcd2 100644 --- a/test-data/unit/lib-stub/attr/__init__.pyi +++ b/test-data/unit/lib-stub/attr/__init__.pyi @@ -1,4 +1,4 @@ -from typing import TypeVar, overload, Callable, Any, Type, Optional, Union, Sequence, Mapping +from typing import Tuple, TypeVar, overload, Callable, Any, Type, Optional, Union, Sequence, Mapping, Generic, List, Dict _T = TypeVar('_T') _C = TypeVar('_C', bound=type) @@ -8,6 +8,15 @@ _ConverterType = Callable[[Any], _T] _FilterType = Callable[[Any, Any], bool] _ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] +_EqOrderType = Union[bool, Callable[[Any], Any]] +_ReprType = Callable[[Any], str] +_ReprArgType = Union[bool, _ReprType] +_OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any] +_OnSetAttrArgType = Union[ + _OnSetAttrType, List[_OnSetAttrType] +] +_FieldTransformer = Callable[[type, List[Attribute[Any]]], List[Attribute[Any]]] + # This form catches explicit None or no default but with no other arguments returns Any. @overload def attrib(default: None = ..., @@ -240,3 +249,29 @@ def field( order: Optional[bool] = ..., on_setattr: Optional[object] = ..., ) -> Any: ... + + +class Attribute(Generic[_T]): + name: str + default: Optional[_T] + validator: Optional[_ValidatorType[_T]] + repr: _ReprArgType + cmp: _EqOrderType + eq: _EqOrderType + order: _EqOrderType + hash: Optional[bool] + init: bool + converter: Optional[_ConverterType] + metadata: Dict[Any, Any] + type: Optional[Type[_T]] + kw_only: bool + on_setattr: _OnSetAttrType + + def evolve(self, **changes: Any) -> "Attribute[Any]": ... + + +class _Fields(Tuple[Attribute[Any], ...]): + def __getattr__(self, name: str) -> Attribute[Any]: ... + + +def fields(cls: type) -> _Fields: ... From 606578a2a79017acc0c2f852be8555aa4b92bfd2 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Thu, 20 May 2021 02:05:39 +0200 Subject: [PATCH 4/4] Fix some more attrs tests --- test-data/unit/fine-grained.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 2c5559a0688d..218b41254bf7 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -932,7 +932,7 @@ import attr class A: a = attr.ib() # type: int other = attr.ib() # type: int -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out] == b.py:7: error: Missing positional argument "b" in call to "B" @@ -958,7 +958,7 @@ import attr class A: a = attr.ib() # type: int other = attr.ib() # type: int -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out] == main:2: error: Missing positional argument "b" in call to "B" @@ -991,7 +991,7 @@ import attr @attr.s(auto_attribs=True, init=False) class A: a: int -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out] == @@ -1018,7 +1018,7 @@ import attr @attr.s(eq=False, init=False) class A: a = attr.ib() # type: int -[builtins fixtures/list.pyi] +[builtins fixtures/attr.pyi] [out] == main:2: error: Unsupported left operand type for < ("B")