diff --git a/mypy/build.py b/mypy/build.py index a55fe4eb92d5b..45b361e6a1b08 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -4458,6 +4458,15 @@ def load_graph( if dep in graph: st.add_dependency(dep) manager.missing_modules.pop(dep, None) + # Suppressing a dependency and adding it back appends it to the end of + # the list, so a module whose dependency list order changed only due to + # that round trip would be considered stale by the order-sensitive + # freshness check (State.is_fresh), even though nothing actually + # changed. Restore the cached order when the dependency set is + # unchanged; a genuine change keeps the new order and the module is + # (correctly) rechecked. + if st.meta is not None and st.dependencies_set == set(st.meta.dependencies): + st.dependencies = list(st.meta.dependencies) # Second, in the initial loop we skip indirect dependencies, so to make indirect dependencies # behave more consistently with regular ones, we suppress them manually here (when needed). for st in graph.values(): diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 1593c73cd2bfe..0c2382956a143 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -1,1179 +1,1186 @@ -"""Plugin for supporting the attrs library (http://www.attrs.org)""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Iterable, Mapping -from functools import reduce -from typing import Final, Literal, cast - -import mypy.plugin # To avoid circular imports. -from mypy.applytype import apply_generic_arguments -from mypy.errorcodes import LITERAL_REQ -from mypy.expandtype import expand_type, expand_type_by_instance -from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type -from mypy.meet import meet_types -from mypy.messages import format_type_bare -from mypy.nodes import ( - ARG_NAMED, - ARG_NAMED_OPT, - ARG_OPT, - ARG_POS, - MDEF, - Argument, - AssignmentStmt, - CallExpr, - Context, - Decorator, - Expression, - FuncDef, - IndexExpr, - JsonDict, - LambdaExpr, - ListExpr, - MemberExpr, - NameExpr, - OverloadedFuncDef, - PlaceholderNode, - RefExpr, - SymbolTableNode, - TempNode, - TupleExpr, - TypeApplication, - TypeInfo, - TypeVarExpr, - Var, - is_class_var, -) -from mypy.plugin import SemanticAnalyzerPluginInterface -from mypy.plugins.common import ( - _get_argument, - _get_bool_argument, - _get_decorator_bool_argument, - add_attribute_to_class, - add_method_to_class, - deserialize_and_fixup_type, -) -from mypy.server.trigger import make_wildcard_trigger -from mypy.state import state -from mypy.typeops import ( - get_type_vars, - make_simplified_union, - map_type_from_supertype, - type_object_type, -) -from mypy.types import ( - AnyType, - CallableType, - FunctionLike, - Instance, - LiteralType, - NoneType, - Overloaded, - ProperType, - TupleType, - Type, - TypeOfAny, - TypeType, - TypeVarId, - TypeVarType, - UninhabitedType, - UnionType, - get_proper_type, - is_unannotated_any, -) -from mypy.typevars import fill_typevars -from mypy.util import unmangle - -# The names of the different functions that create classes or arguments. -attr_class_makers: Final = {"attr.s", "attr.attrs", "attr.attributes"} -attr_dataclass_makers: Final = {"attr.dataclass"} -attr_frozen_makers: Final = {"attr.frozen", "attrs.frozen"} -attr_define_makers: Final = {"attr.define", "attr.mutable", "attrs.define", "attrs.mutable"} -attr_attrib_makers: Final = {"attr.ib", "attr.attrib", "attr.attr", "attr.field", "attrs.field"} -attr_optional_converters: Final = {"attr.converters.optional", "attrs.converters.optional"} - -SELF_TVAR_NAME: Final = "_AT" -MAGIC_ATTR_NAME: Final = "__attrs_attrs__" -MAGIC_ATTR_CLS_NAME_TEMPLATE: Final = "__{}_AttrsAttributes__" # The tuple subclass pattern. -ATTRS_INIT_NAME: Final = "__attrs_init__" - - -class Converter: - """Holds information about a `converter=` argument""" - - def __init__(self, init_type: Type | None = None, ret_type: Type | None = None) -> None: - self.init_type = init_type - self.ret_type = ret_type - - -class Attribute: - """The value of an attr.ib() call.""" - - def __init__( - self, - name: str, - alias: str | None, - info: TypeInfo, - has_default: bool, - init: bool, - kw_only: bool, - converter: Converter | None, - context: Context, - init_type: Type | None, - ) -> None: - self.name = name - self.alias = alias - self.info = info - self.has_default = has_default - self.init = init - self.kw_only = kw_only - self.converter = converter - self.context = context - self.init_type = init_type - - def argument(self, ctx: mypy.plugin.ClassDefContext) -> Argument: - """Return this attribute as an argument to __init__.""" - assert self.init - init_type: Type | None = None - if self.converter: - if self.converter.init_type: - init_type = self.converter.init_type - if init_type and self.init_type and self.converter.ret_type: - # The converter return type should be the same type as the attribute type. - # Copy type vars from attr type to converter. - converter_vars = get_type_vars(self.converter.ret_type) - init_vars = get_type_vars(self.init_type) - if converter_vars and len(converter_vars) == len(init_vars): - variables = { - binder.id: arg for binder, arg in zip(converter_vars, init_vars) - } - init_type = expand_type(init_type, variables) - else: - ctx.api.fail("Cannot determine __init__ type from converter", self.context) - init_type = AnyType(TypeOfAny.from_error) - else: # There is no converter, the init type is the normal type. - init_type = self.init_type or self.info[self.name].type - - unannotated = False - if init_type is None: - unannotated = True - # Convert type not set to Any. - init_type = AnyType(TypeOfAny.unannotated) - else: - proper_type = get_proper_type(init_type) - if isinstance(proper_type, AnyType): - if proper_type.type_of_any == TypeOfAny.unannotated: - unannotated = True - - if unannotated and ctx.api.options.disallow_untyped_defs: - # This is a compromise. If you don't have a type here then the - # __init__ will be untyped. But since the __init__ is added it's - # pointing at the decorator. So instead we also show the error in the - # assignment, which is where you would fix the issue. - node = self.info[self.name].node - assert node is not None - ctx.api.msg.need_annotation_for_var(node, self.context) - - if self.kw_only: - arg_kind = ARG_NAMED_OPT if self.has_default else ARG_NAMED - else: - arg_kind = ARG_OPT if self.has_default else ARG_POS - - # Attrs removes leading underscores when creating the __init__ arguments. - name = self.alias or self.name.lstrip("_") - return Argument(Var(name, init_type), init_type, None, arg_kind) - - def serialize(self) -> JsonDict: - """Serialize this object so it can be saved and restored.""" - return { - "name": self.name, - "alias": self.alias, - "has_default": self.has_default, - "init": self.init, - "kw_only": self.kw_only, - "has_converter": self.converter is not None, - "converter_init_type": ( - self.converter.init_type.serialize() - if self.converter and self.converter.init_type - else None - ), - "context_line": self.context.line, - "context_column": self.context.column, - "init_type": self.init_type.serialize() if self.init_type else None, - } - - @classmethod - def deserialize( - cls, info: TypeInfo, data: JsonDict, api: SemanticAnalyzerPluginInterface - ) -> Attribute: - """Return the Attribute that was serialized.""" - raw_init_type = data["init_type"] - init_type = deserialize_and_fixup_type(raw_init_type, api) if raw_init_type else None - raw_converter_init_type = data["converter_init_type"] - converter_init_type = ( - deserialize_and_fixup_type(raw_converter_init_type, api) - if raw_converter_init_type - else None - ) - - return Attribute( - data["name"], - data["alias"], - info, - data["has_default"], - data["init"], - data["kw_only"], - Converter(converter_init_type) if data["has_converter"] else None, - Context(line=data["context_line"], column=data["context_column"]), - init_type, - ) - - def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None: - """Expands type vars in the context of a subtype when an attribute is inherited - from a generic super type.""" - if self.init_type: - self.init_type = map_type_from_supertype(self.init_type, sub_type, self.info) - else: - self.init_type = None - - -def _determine_eq_order(ctx: mypy.plugin.ClassDefContext) -> bool: - """ - Validate the combination of *cmp*, *eq*, and *order*. Derive the effective - value of order. - """ - cmp = _get_decorator_optional_bool_argument(ctx, "cmp") - eq = _get_decorator_optional_bool_argument(ctx, "eq") - order = _get_decorator_optional_bool_argument(ctx, "order") - - if cmp is not None and any((eq is not None, order is not None)): - ctx.api.fail('Don\'t mix "cmp" with "eq" and "order"', ctx.reason) - - # cmp takes precedence due to bw-compatibility. - if cmp is not None: - return cmp - - # If left None, equality is on and ordering mirrors equality. - if eq is None: - eq = True - - if order is None: - order = eq - - if eq is False and order is True: - ctx.api.fail("eq must be True if order is True", ctx.reason) - - return order - - -def _get_decorator_optional_bool_argument( - ctx: mypy.plugin.ClassDefContext, name: str, default: bool | None = None -) -> bool | None: - """Return the Optional[bool] argument for the decorator. - - This handles both @decorator(...) and @decorator. - """ - if isinstance(ctx.reason, CallExpr): - attr_value = _get_argument(ctx.reason, name) - if attr_value: - if isinstance(attr_value, NameExpr): - if attr_value.fullname == "builtins.True": - return True - if attr_value.fullname == "builtins.False": - return False - if attr_value.fullname == "builtins.None": - return None - ctx.api.fail( - f'"{name}" argument must be a True, False, or None literal', - ctx.reason, - code=LITERAL_REQ, - ) - return default - return default - else: - return default - - -def attr_tag_callback(ctx: mypy.plugin.ClassDefContext) -> None: - """Record that we have an attrs class in the main semantic analysis pass. - - The later pass implemented by attr_class_maker_callback will use this - to detect attrs classes in base classes. - """ - # The value is ignored, only the existence matters. - ctx.cls.info.metadata["attrs_tag"] = {} - - -def attr_class_maker_callback( - ctx: mypy.plugin.ClassDefContext, - auto_attribs_default: bool | None = False, - frozen_default: bool = False, - slots_default: bool = False, -) -> bool: - """Add necessary dunder methods to classes decorated with attr.s. - - attrs is a package that lets you define classes without writing dull boilerplate code. - - At a quick glance, the decorator searches the class body for assignments of `attr.ib`s (or - annotated variables if auto_attribs=True), then depending on how the decorator is called, - it will add an __init__ or all the compare methods. - For frozen=True it will turn the attrs into properties. - - Hashability will be set according to https://www.attrs.org/en/stable/hashing.html. - - See https://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works. - - If this returns False, some required metadata was not ready yet, and we need another - pass. - """ - with state.strict_optional_set(ctx.api.options.strict_optional): - # This hook is called during semantic analysis, but it uses a bunch of - # type-checking ops, so it needs the strict optional set properly. - return attr_class_maker_callback_impl( - ctx, auto_attribs_default, frozen_default, slots_default - ) - - -def attr_class_maker_callback_impl( - ctx: mypy.plugin.ClassDefContext, - auto_attribs_default: bool | None, - frozen_default: bool, - slots_default: bool, -) -> bool: - info = ctx.cls.info - - init = _get_decorator_bool_argument(ctx, "init", True) - frozen = _get_frozen(ctx, frozen_default) - order = _determine_eq_order(ctx) - slots = _get_decorator_bool_argument(ctx, "slots", slots_default) - - auto_attribs = _get_decorator_optional_bool_argument(ctx, "auto_attribs", auto_attribs_default) - kw_only = _get_decorator_bool_argument(ctx, "kw_only", False) - match_args = _get_decorator_bool_argument(ctx, "match_args", True) - - for super_info in ctx.cls.info.mro[1:-1]: - if "attrs_tag" in super_info.metadata and "attrs" not in super_info.metadata: - # Super class is not ready yet. Request another pass. - return False - - attributes = _analyze_class(ctx, auto_attribs, kw_only) - - # Check if attribute types are ready. - for attr in attributes: - node = info.get(attr.name) - if node is None: - # This name is likely blocked by some semantic analysis error that - # should have been reported already. - _add_empty_metadata(info) - return True - - _add_attrs_magic_attribute(ctx, [(attr.name, info[attr.name].type) for attr in attributes]) - if slots: - _add_slots(ctx, attributes) - if match_args: - _add_match_args(ctx, attributes) - - # Save the attributes so that subclasses can reuse them. - ctx.cls.info.metadata["attrs"] = { - "attributes": [attr.serialize() for attr in attributes], - "frozen": frozen, - } - - adder = MethodAdder(ctx) - # If __init__ is not being generated, attrs still generates it as __attrs_init__ instead. - _add_init(ctx, attributes, adder, "__init__" if init else ATTRS_INIT_NAME) - - if order: - _add_order(ctx, adder) - if frozen: - _make_frozen(ctx, attributes) - # Frozen classes are hashable by default, even if inheriting from non-frozen ones. - hashable: bool | None = _get_decorator_bool_argument( - ctx, "hash", True - ) and _get_decorator_bool_argument(ctx, "unsafe_hash", True) - else: - hashable = _get_decorator_optional_bool_argument(ctx, "unsafe_hash") - if hashable is None: # unspecified - hashable = _get_decorator_optional_bool_argument(ctx, "hash") - - eq = _get_decorator_optional_bool_argument(ctx, "eq") - has_own_hash = "__hash__" in ctx.cls.info.names - - if has_own_hash or (hashable is None and eq is False): - pass # Do nothing. - elif hashable: - # We copy the `__hash__` signature from `object` to make them hashable. - ctx.cls.info.names["__hash__"] = ctx.cls.info.mro[-1].names["__hash__"] - else: - _remove_hashability(ctx) - - return True - - -def _get_frozen(ctx: mypy.plugin.ClassDefContext, frozen_default: bool) -> bool: - """Return whether this class is frozen.""" - if _get_decorator_bool_argument(ctx, "frozen", frozen_default): - return True - # Subclasses of frozen classes are frozen so check that. - for super_info in ctx.cls.info.mro[1:-1]: - if "attrs" in super_info.metadata and super_info.metadata["attrs"]["frozen"]: - return True - return False - - -def _analyze_class( - ctx: mypy.plugin.ClassDefContext, auto_attribs: bool | None, class_kw_only: bool -) -> list[Attribute]: - """Analyze the class body of an attr maker, its parents, and return the Attributes found. - - auto_attribs=True means we'll generate attributes from type annotations also. - auto_attribs=None means we'll detect which mode to use. - class_kw_only=True means that all attributes created here will be keyword only args by - default in __init__. - """ - own_attrs: dict[str, Attribute] = {} - if auto_attribs is None: - auto_attribs = _detect_auto_attribs(ctx) - - # Walk the body looking for assignments and decorators. - for stmt in ctx.cls.defs.body: - if isinstance(stmt, AssignmentStmt): - for attr in _attributes_from_assignment(ctx, stmt, auto_attribs, class_kw_only): - # When attrs are defined twice in the same body we want to use the 2nd definition - # in the 2nd location. So remove it from the OrderedDict. - # Unless it's auto_attribs in which case we want the 2nd definition in the - # 1st location. - if not auto_attribs and attr.name in own_attrs: - del own_attrs[attr.name] - own_attrs[attr.name] = attr - elif isinstance(stmt, Decorator): - _cleanup_decorator(stmt, own_attrs) - - for attribute in own_attrs.values(): - # Even though these look like class level assignments we want them to look like - # instance level assignments. - if attribute.name in ctx.cls.info.names: - node = ctx.cls.info.names[attribute.name].node - if isinstance(node, PlaceholderNode): - # This node is not ready yet. - continue - assert isinstance(node, Var), node - node.is_initialized_in_class = False - - # Traverse the MRO and collect attributes from the parents. - taken_attr_names = set(own_attrs) - super_attrs = [] - for super_info in ctx.cls.info.mro[1:-1]: - if "attrs" in super_info.metadata: - # Each class depends on the set of attributes in its attrs ancestors. - ctx.api.add_plugin_dependency(make_wildcard_trigger(super_info.fullname)) - - for data in super_info.metadata["attrs"]["attributes"]: - # Only add an attribute if it hasn't been defined before. This - # allows for overwriting attribute definitions by subclassing. - if data["name"] not in taken_attr_names: - a = Attribute.deserialize(super_info, data, ctx.api) - a.expand_typevar_from_subtype(ctx.cls.info) - super_attrs.append(a) - taken_attr_names.add(a.name) - attributes = super_attrs + list(own_attrs.values()) - - # Check the init args for correct default-ness. Note: This has to be done after all the - # attributes for all classes have been read, because subclasses can override parents. - last_default = False - - for i, attribute in enumerate(attributes): - if not attribute.init: - continue - - if attribute.kw_only: - # Keyword-only attributes don't care whether they are default or not. - continue - - # If the issue comes from merging different classes, report it - # at the class definition point. - context = attribute.context if i >= len(super_attrs) else ctx.cls - - if not attribute.has_default and last_default: - ctx.api.fail("Non-default attributes not allowed after default attributes.", context) - last_default |= attribute.has_default - - return attributes - - -def _add_empty_metadata(info: TypeInfo) -> None: - """Add empty metadata to mark that we've finished processing this class.""" - info.metadata["attrs"] = {"attributes": [], "frozen": False} - - -def _detect_auto_attribs(ctx: mypy.plugin.ClassDefContext) -> bool: - """Return whether auto_attribs should be enabled or disabled. - - It's disabled if there are any unannotated attribs() - """ - for stmt in ctx.cls.defs.body: - if isinstance(stmt, AssignmentStmt): - for lvalue in stmt.lvalues: - lvalues, rvalues = _parse_assignments(lvalue, stmt) - - if len(lvalues) != len(rvalues): - # This means we have some assignment that isn't 1 to 1. - # It can't be an attrib. - continue - - for lhs, rvalue in zip(lvalues, rvalues): - # Check if the right hand side is a call to an attribute maker. - if ( - isinstance(rvalue, CallExpr) - and isinstance(rvalue.callee, RefExpr) - and rvalue.callee.fullname in attr_attrib_makers - and not stmt.new_syntax - ): - # This means we have an attrib without an annotation and so - # we can't do auto_attribs=True - return False - return True - - -def _attributes_from_assignment( - ctx: mypy.plugin.ClassDefContext, stmt: AssignmentStmt, auto_attribs: bool, class_kw_only: bool -) -> Iterable[Attribute]: - """Return Attribute objects that are created by this assignment. - - The assignments can look like this: - x = attr.ib() - x = y = attr.ib() - x, y = attr.ib(), attr.ib() - or if auto_attribs is enabled also like this: - x: type - x: type = default_value - x: type = attr.ib(...) - """ - for lvalue in stmt.lvalues: - lvalues, rvalues = _parse_assignments(lvalue, stmt) - - if len(lvalues) != len(rvalues): - # This means we have some assignment that isn't 1 to 1. - # It can't be an attrib. - continue - - for lhs, rvalue in zip(lvalues, rvalues): - # Check if the right hand side is a call to an attribute maker. - if ( - isinstance(rvalue, CallExpr) - and isinstance(rvalue.callee, RefExpr) - and rvalue.callee.fullname in attr_attrib_makers - ): - attr = _attribute_from_attrib_maker( - ctx, auto_attribs, class_kw_only, lhs, rvalue, stmt - ) - if attr: - yield attr - elif auto_attribs and stmt.type and stmt.new_syntax and not is_class_var(lhs): - yield _attribute_from_auto_attrib(ctx, class_kw_only, lhs, rvalue, stmt) - - -def _cleanup_decorator(stmt: Decorator, attr_map: dict[str, Attribute]) -> None: - """Handle decorators in class bodies. - - `x.default` will set a default value on x - `x.validator` and `x.default` will get removed to avoid throwing a type error. - """ - remove_me = [] - for func_decorator in stmt.decorators: - if ( - isinstance(func_decorator, MemberExpr) - and isinstance(func_decorator.expr, NameExpr) - and func_decorator.expr.name in attr_map - ): - if func_decorator.name == "default": - attr_map[func_decorator.expr.name].has_default = True - - if func_decorator.name in ("default", "validator"): - # These are decorators on the attrib object that only exist during - # class creation time. In order to not trigger a type error later we - # just remove them. This might leave us with a Decorator with no - # decorators (Emperor's new clothes?) - # TODO: It would be nice to type-check these rather than remove them. - # default should be Callable[[], T] - # validator should be Callable[[Any, 'Attribute', T], Any] - # where T is the type of the attribute. - remove_me.append(func_decorator) - for dec in remove_me: - stmt.decorators.remove(dec) - - -def _attribute_from_auto_attrib( - ctx: mypy.plugin.ClassDefContext, - class_kw_only: bool, - lhs: NameExpr, - rvalue: Expression, - stmt: AssignmentStmt, -) -> Attribute: - """Return an Attribute for a new type assignment.""" - name = unmangle(lhs.name) - # `x: int` (without equal sign) assigns rvalue to TempNode(AnyType()) - has_rhs = not isinstance(rvalue, TempNode) - sym = ctx.cls.info.names.get(name) - init_type = sym.type if sym else None - return Attribute(name, None, ctx.cls.info, has_rhs, True, class_kw_only, None, stmt, init_type) - - -def _attribute_from_attrib_maker( - ctx: mypy.plugin.ClassDefContext, - auto_attribs: bool, - class_kw_only: bool, - lhs: NameExpr, - rvalue: CallExpr, - stmt: AssignmentStmt, -) -> Attribute | None: - """Return an Attribute from the assignment or None if you can't make one.""" - if auto_attribs and not stmt.new_syntax: - # auto_attribs requires an annotation on *every* attr.ib. - assert lhs.node is not None - ctx.api.msg.need_annotation_for_var(lhs.node, stmt) - return None - - if len(stmt.lvalues) > 1: - ctx.api.fail("Too many names for one attribute", stmt) - return None - - # This is the type that belongs in the __init__ method for this attrib. - init_type = stmt.type - - # Read all the arguments from the call. - init = _get_bool_argument(ctx, rvalue, "init", True) - # The class decorator kw_only value can be overridden by the attribute value - # See https://github.com/python-attrs/attrs/pull/1457 - kw_only = _get_bool_argument(ctx, rvalue, "kw_only", class_kw_only) - - # TODO: Check for attr.NOTHING - attr_has_default = bool(_get_argument(rvalue, "default")) - attr_has_factory = bool(_get_argument(rvalue, "factory")) - - if attr_has_default and attr_has_factory: - ctx.api.fail('Can\'t pass both "default" and "factory".', rvalue) - elif attr_has_factory: - attr_has_default = True - - # If the type isn't set through annotation but is passed through `type=` use that. - type_arg = _get_argument(rvalue, "type") - if type_arg and not init_type: - try: - un_type = expr_to_unanalyzed_type(type_arg, ctx.api.options, ctx.api.is_stub_file) - except TypeTranslationError: - ctx.api.fail("Invalid argument to type", type_arg) - else: - init_type = ctx.api.anal_type(un_type) - if init_type and isinstance(lhs.node, Var) and not lhs.node.type: - # If there is no annotation, add one. - lhs.node.type = init_type - lhs.is_inferred_def = False - - # Note: convert is deprecated but works the same as converter. - converter = _get_argument(rvalue, "converter") - convert = _get_argument(rvalue, "convert") - if convert and converter: - ctx.api.fail('Can\'t pass both "convert" and "converter".', rvalue) - elif convert: - ctx.api.fail("convert is deprecated, use converter", rvalue) - converter = convert - converter_info = _parse_converter(ctx, converter) - - # Custom alias might be defined: - alias = None - alias_expr = _get_argument(rvalue, "alias") - if alias_expr: - alias = ctx.api.parse_str_literal(alias_expr) - if alias is None: - ctx.api.fail( - '"alias" argument to attrs field must be a string literal', - rvalue, - code=LITERAL_REQ, - ) - name = unmangle(lhs.name) - return Attribute( - name, alias, ctx.cls.info, attr_has_default, init, kw_only, converter_info, stmt, init_type - ) - - -def _parse_converter( - ctx: mypy.plugin.ClassDefContext, converter_expr: Expression | None -) -> Converter | None: - """Return the Converter object from an Expression.""" - # TODO: Support complex converters, e.g. lambdas, calls, etc. - if not converter_expr: - return None - converter_info = Converter() - if ( - isinstance(converter_expr, CallExpr) - and isinstance(converter_expr.callee, RefExpr) - and converter_expr.callee.fullname in attr_optional_converters - and converter_expr.args - and converter_expr.args[0] - ): - # Special handling for attr.converters.optional(type) - # We extract the type and add make the init_args Optional in Attribute.argument - converter_expr = converter_expr.args[0] - is_attr_converters_optional = True - else: - is_attr_converters_optional = False - - converter_type: Type | None = None - if isinstance(converter_expr, RefExpr) and converter_expr.node: - if isinstance(converter_expr.node, FuncDef): - if converter_expr.node.type and isinstance(converter_expr.node.type, FunctionLike): - converter_type = converter_expr.node.type - else: # The converter is an unannotated function. - converter_info.init_type = AnyType(TypeOfAny.unannotated) - return converter_info - elif isinstance(converter_expr.node, OverloadedFuncDef) and is_valid_overloaded_converter( - converter_expr.node - ): - converter_type = converter_expr.node.type - elif isinstance(converter_expr.node, TypeInfo): - converter_type = type_object_type(converter_expr.node) - elif ( - isinstance(converter_expr, IndexExpr) - and isinstance(converter_expr.analyzed, TypeApplication) - and isinstance(converter_expr.base, RefExpr) - and isinstance(converter_expr.base.node, TypeInfo) - ): - # The converter is a generic type. - converter_type = type_object_type(converter_expr.base.node) - if isinstance(converter_type, CallableType): - converter_type = apply_generic_arguments( - converter_type, - converter_expr.analyzed.types, - ctx.api.msg.incompatible_typevar_value, - converter_type, - ) - else: - converter_type = None - - if isinstance(converter_expr, LambdaExpr): - # TODO: should we send a fail if converter_expr.min_args > 1? - converter_info.init_type = AnyType(TypeOfAny.unannotated) - return converter_info - - if not converter_type: - # Signal that we have an unsupported converter. - ctx.api.fail( - "Unsupported converter, only named functions, types and lambdas are currently " - "supported", - converter_expr, - ) - converter_info.init_type = AnyType(TypeOfAny.from_error) - return converter_info - - converter_type = get_proper_type(converter_type) - if isinstance(converter_type, CallableType) and converter_type.arg_types: - converter_info.init_type = converter_type.arg_types[0] - if not is_attr_converters_optional: - converter_info.ret_type = converter_type.ret_type - elif isinstance(converter_type, Overloaded): - types: list[Type] = [] - for item in converter_type.items: - # Walk the overloads looking for methods that can accept one argument. - num_arg_types = len(item.arg_types) - if not num_arg_types: - continue - if num_arg_types > 1 and any(kind == ARG_POS for kind in item.arg_kinds[1:]): - continue - types.append(item.arg_types[0]) - # Make a union of all the valid types. - if types: - converter_info.init_type = make_simplified_union(types) - - if is_attr_converters_optional and converter_info.init_type: - # If the converter was attr.converter.optional(type) then add None to - # the allowed init_type. - converter_info.init_type = UnionType.make_union([converter_info.init_type, NoneType()]) - - return converter_info - - -def is_valid_overloaded_converter(defn: OverloadedFuncDef) -> bool: - return all( - (not isinstance(item, Decorator) or isinstance(item.func.type, FunctionLike)) - for item in defn.items - ) - - -def _parse_assignments( - lvalue: Expression, stmt: AssignmentStmt -) -> tuple[list[NameExpr], list[Expression]]: - """Convert a possibly complex assignment expression into lists of lvalues and rvalues.""" - lvalues: list[NameExpr] = [] - rvalues: list[Expression] = [] - if isinstance(lvalue, (TupleExpr, ListExpr)): - if all(isinstance(item, NameExpr) for item in lvalue.items): - lvalues = cast(list[NameExpr], lvalue.items) - if isinstance(stmt.rvalue, (TupleExpr, ListExpr)): - rvalues = stmt.rvalue.items - elif isinstance(lvalue, NameExpr): - lvalues = [lvalue] - rvalues = [stmt.rvalue] - return lvalues, rvalues - - -def _add_order(ctx: mypy.plugin.ClassDefContext, adder: MethodAdder) -> None: - """Generate all the ordering methods for this class.""" - bool_type = ctx.api.named_type("builtins.bool") - object_type = ctx.api.named_type("builtins.object") - # Make the types be: - # AT = TypeVar('AT') - # def __lt__(self: AT, other: AT) -> bool - # This way comparisons with subclasses will work correctly. - fullname = f"{ctx.cls.info.fullname}.{SELF_TVAR_NAME}" - tvd = TypeVarType( - SELF_TVAR_NAME, - fullname, - # Namespace is patched per-method below. - id=TypeVarId(-1, namespace=""), - values=[], - upper_bound=object_type, - default=AnyType(TypeOfAny.from_omitted_generics), - ) - self_tvar_expr = TypeVarExpr( - SELF_TVAR_NAME, fullname, [], object_type, AnyType(TypeOfAny.from_omitted_generics) - ) - ctx.cls.info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) - - for method in ["__lt__", "__le__", "__gt__", "__ge__"]: - namespace = f"{ctx.cls.info.fullname}.{method}" - tvd = tvd.copy_modified(id=TypeVarId(tvd.id.raw_id, namespace=namespace)) - args = [Argument(Var("other", tvd), tvd, None, ARG_POS)] - adder.add_method(method, args, bool_type, self_type=tvd, tvd=tvd) - - -def _make_frozen(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: - """Turn all the attributes into properties to simulate frozen classes.""" - for attribute in attributes: - if attribute.name in ctx.cls.info.names: - # This variable belongs to this class so we can modify it. - node = ctx.cls.info.names[attribute.name].node - if not isinstance(node, Var): - # The superclass attribute was overridden with a non-variable. - # No need to do anything here, override will be verified during - # type checking. - continue - node.is_property = True - else: - # This variable belongs to a super class so create new Var so we - # can modify it. - var = Var(attribute.name, attribute.init_type) - var.info = ctx.cls.info - var._fullname = f"{ctx.cls.info.fullname}.{var.name}" - ctx.cls.info.names[var.name] = SymbolTableNode(MDEF, var) - var.is_property = True - - -def _add_init( - ctx: mypy.plugin.ClassDefContext, - attributes: list[Attribute], - adder: MethodAdder, - method_name: Literal["__init__", "__attrs_init__"], -) -> None: - """Generate an __init__ method for the attributes and add it to the class.""" - # Convert attributes to arguments with kw_only arguments at the end of - # the argument list - pos_args = [] - kw_only_args = [] - sym_table = ctx.cls.info.names - for attribute in attributes: - if not attribute.init: - continue - if attribute.kw_only: - kw_only_args.append(attribute.argument(ctx)) - else: - pos_args.append(attribute.argument(ctx)) - - # If the attribute is Final, present in `__init__` and has - # no default, make sure it doesn't error later. - if not attribute.has_default and attribute.name in sym_table: - sym_node = sym_table[attribute.name].node - if isinstance(sym_node, Var) and sym_node.is_final: - sym_node.final_set_in_init = True - args = pos_args + kw_only_args - if all(arg.variable.type and is_unannotated_any(arg.variable.type) for arg in args): - # This workaround makes --disallow-incomplete-defs usable with attrs, - # but is definitely suboptimal as a long-term solution. - # See https://github.com/python/mypy/issues/5954 for discussion. - for a in args: - a.variable.type = AnyType(TypeOfAny.implementation_artifact) - a.type_annotation = AnyType(TypeOfAny.implementation_artifact) - adder.add_method(method_name, args, NoneType()) - - -def _add_attrs_magic_attribute( - ctx: mypy.plugin.ClassDefContext, attrs: list[tuple[str, Type | None]] -) -> None: - any_type = AnyType(TypeOfAny.explicit) - attributes_types: list[Type] = [ - ctx.api.named_type_or_none("attr.Attribute", [attr_type or any_type]) or any_type - for _, attr_type in attrs - ] - fallback_type = ctx.api.named_type( - "builtins.tuple", [ctx.api.named_type_or_none("attr.Attribute", [any_type]) or any_type] - ) - - attr_name = MAGIC_ATTR_CLS_NAME_TEMPLATE.format(ctx.cls.fullname.replace(".", "_")) - ti = ctx.api.basic_new_typeinfo(attr_name, fallback_type, 0) - for (name, _), attr_type in zip(attrs, attributes_types): - var = Var(name, attr_type) - var._fullname = name - var.is_property = True - proper_type = get_proper_type(attr_type) - if isinstance(proper_type, Instance): - var.info = proper_type.type - ti.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True) - attributes_type = Instance(ti, []) - - # We need to stash the type of the magic attribute so it can be - # loaded on cached runs. - ctx.cls.info.names[attr_name] = SymbolTableNode(MDEF, ti, plugin_generated=True) - - add_attribute_to_class( - ctx.api, - ctx.cls, - MAGIC_ATTR_NAME, - TupleType(attributes_types, fallback=attributes_type), - fullname=f"{ctx.cls.fullname}.{MAGIC_ATTR_NAME}", - override_allow_incompatible=True, - is_classvar=True, - ) - - -def _add_slots(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: - if any(p.slots is None for p in ctx.cls.info.mro[1:-1]): - # At least one type in mro (excluding `self` and `object`) - # does not have concrete `__slots__` defined. Ignoring. - return - - # Unlike `@dataclasses.dataclass`, `__slots__` is rewritten here. - ctx.cls.info.slots = {attr.name for attr in attributes} - - # Also, inject `__slots__` attribute to class namespace: - slots_type = TupleType( - [ctx.api.named_type("builtins.str") for _ in attributes], - fallback=ctx.api.named_type("builtins.tuple"), - ) - add_attribute_to_class(api=ctx.api, cls=ctx.cls, name="__slots__", typ=slots_type) - - -def _add_match_args(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: - if ( - "__match_args__" not in ctx.cls.info.names - or ctx.cls.info.names["__match_args__"].plugin_generated - ): - str_type = ctx.api.named_type("builtins.str") - match_args = TupleType( - [ - str_type.copy_modified(last_known_value=LiteralType(attr.name, fallback=str_type)) - for attr in attributes - if not attr.kw_only and attr.init - ], - fallback=ctx.api.named_type("builtins.tuple"), - ) - add_attribute_to_class(api=ctx.api, cls=ctx.cls, name="__match_args__", typ=match_args) - - -def _remove_hashability(ctx: mypy.plugin.ClassDefContext) -> None: - """Remove hashability from a class.""" - add_attribute_to_class( - ctx.api, ctx.cls, "__hash__", NoneType(), is_classvar=True, overwrite_existing=True - ) - - -class MethodAdder: - """Helper to add methods to a TypeInfo. - - ctx: The ClassDefCtx we are using on which we will add methods. - """ - - # TODO: Combine this with the code build_namedtuple_typeinfo to support both. - - def __init__(self, ctx: mypy.plugin.ClassDefContext) -> None: - self.ctx = ctx - self.self_type = fill_typevars(ctx.cls.info) - - def add_method( - self, - method_name: str, - args: list[Argument], - ret_type: Type, - self_type: Type | None = None, - tvd: TypeVarType | None = None, - ) -> None: - """Add a method: def (self, ) -> ): ... to info. - - self_type: The type to use for the self argument or None to use the inferred self type. - tvd: If the method is generic these should be the type variables. - """ - self_type = self_type if self_type is not None else self.self_type - add_method_to_class( - self.ctx.api, self.ctx.cls, method_name, args, ret_type, self_type, tvd - ) - - -def _get_attrs_init_type(typ: Instance) -> CallableType | None: - """ - If `typ` refers to an attrs class, get the type of its initializer method. - """ - magic_attr = typ.type.get(MAGIC_ATTR_NAME) - if magic_attr is None or not magic_attr.plugin_generated: - return None - init_method = typ.type.get_method("__init__") or typ.type.get_method(ATTRS_INIT_NAME) - if not isinstance(init_method, FuncDef) or not isinstance(init_method.type, CallableType): - return None - return init_method.type - - -def _fail_not_attrs_class(ctx: mypy.plugin.FunctionSigContext, t: Type, parent_t: Type) -> None: - t_name = format_type_bare(t, ctx.api.options) - if parent_t is t: - msg = ( - f'Argument 1 to "evolve" has a variable type "{t_name}" not bound to an attrs class' - if isinstance(t, TypeVarType) - else f'Argument 1 to "evolve" has incompatible type "{t_name}"; expected an attrs class' - ) - else: - pt_name = format_type_bare(parent_t, ctx.api.options) - msg = ( - f'Argument 1 to "evolve" has type "{pt_name}" whose item "{t_name}" is not bound to an attrs class' - if isinstance(t, TypeVarType) - else f'Argument 1 to "evolve" has incompatible type "{pt_name}" whose item "{t_name}" is not an attrs class' - ) - - ctx.api.fail(msg, ctx.context) - - -def _get_expanded_attr_types( - ctx: mypy.plugin.FunctionSigContext, - typ: ProperType, - display_typ: ProperType, - parent_typ: ProperType, -) -> list[Mapping[str, Type]] | None: - """ - For a given type, determine what attrs classes it can be: for each class, return the field types. - For generic classes, the field types are expanded. - If the type contains Any or a non-attrs type, returns None; in the latter case, also reports an error. - """ - if isinstance(typ, AnyType): - return None - elif isinstance(typ, UnionType): - ret: list[Mapping[str, Type]] | None = [] - for item in typ.relevant_items(): - item = get_proper_type(item) - item_types = _get_expanded_attr_types(ctx, item, item, parent_typ) - if ret is not None and item_types is not None: - ret += item_types - else: - ret = None # but keep iterating to emit all errors - return ret - elif isinstance(typ, TypeVarType): - return _get_expanded_attr_types( - ctx, get_proper_type(typ.upper_bound), display_typ, parent_typ - ) - elif isinstance(typ, Instance): - init_func = _get_attrs_init_type(typ) - if init_func is None: - _fail_not_attrs_class(ctx, display_typ, parent_typ) - return None - init_func = expand_type_by_instance(init_func, typ) - # [1:] to skip the self argument of AttrClass.__init__ - field_names = cast(list[str], init_func.arg_names[1:]) - field_types = init_func.arg_types[1:] - return [dict(zip(field_names, field_types))] - else: - _fail_not_attrs_class(ctx, display_typ, parent_typ) - return None - - -def _meet_fields(types: list[Mapping[str, Type]]) -> Mapping[str, Type]: - """ - "Meet" the fields of a list of attrs classes, i.e. for each field, its new type will be the lower bound. - """ - field_to_types = defaultdict(list) - for fields in types: - for name, typ in fields.items(): - field_to_types[name].append(typ) - - return { - name: ( - get_proper_type(reduce(meet_types, f_types)) - if len(f_types) == len(types) - else UninhabitedType() - ) - for name, f_types in field_to_types.items() - } - - -def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> CallableType: - """ - Generate a signature for the 'attr.evolve' function that's specific to the call site - and dependent on the type of the first argument. - """ - if len(ctx.args) != 2: - # Ideally the name and context should be callee's, but we don't have it in FunctionSigContext. - ctx.api.fail(f'"{ctx.default_signature.name}" has unexpected type annotation', ctx.context) - return ctx.default_signature - - if len(ctx.args[0]) != 1: - return ctx.default_signature # leave it to the type checker to complain - - inst_arg = ctx.args[0][0] - inst_type = get_proper_type(ctx.api.get_expression_type(inst_arg)) - inst_type_str = format_type_bare(inst_type, ctx.api.options) - - attr_types = _get_expanded_attr_types(ctx, inst_type, inst_type, inst_type) - if attr_types is None: - return ctx.default_signature - fields = _meet_fields(attr_types) - - return CallableType( - arg_names=["inst", *fields.keys()], - arg_kinds=[ARG_POS] + [ARG_NAMED_OPT] * len(fields), - arg_types=[inst_type, *fields.values()], - ret_type=inst_type, - fallback=ctx.default_signature.fallback, - name=f"{ctx.default_signature.name} of {inst_type_str}", - ) - - -def fields_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> CallableType: - """Provide the signature for `attrs.fields`.""" - if len(ctx.args) != 1 or len(ctx.args[0]) != 1: - return ctx.default_signature - - proper_type = get_proper_type(ctx.api.get_expression_type(ctx.args[0][0])) - - # fields(Any) -> Any, fields(type[Any]) -> Any - if ( - isinstance(proper_type, AnyType) - or isinstance(proper_type, TypeType) - and isinstance(proper_type.item, AnyType) - ): - return ctx.default_signature - - cls = None - arg_types = ctx.default_signature.arg_types - - if isinstance(proper_type, TypeVarType): - inner = get_proper_type(proper_type.upper_bound) - if isinstance(inner, Instance): - # We need to work arg_types to compensate for the attrs stubs. - arg_types = [proper_type] - cls = inner.type - elif isinstance(proper_type, CallableType): - cls = proper_type.type_object() - - if cls is not None and MAGIC_ATTR_NAME in cls.names: - # This is a proper attrs class. - ret_type = cls.names[MAGIC_ATTR_NAME].type - assert ret_type is not None - return ctx.default_signature.copy_modified(arg_types=arg_types, ret_type=ret_type) - - return ctx.default_signature +"""Plugin for supporting the attrs library (http://www.attrs.org)""" + +from __future__ import annotations + +from collections import defaultdict +from collections.abc import Iterable, Mapping +from functools import reduce +from typing import Final, Literal, cast + +import mypy.plugin # To avoid circular imports. +from mypy.applytype import apply_generic_arguments +from mypy.errorcodes import LITERAL_REQ +from mypy.expandtype import expand_type, expand_type_by_instance +from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type +from mypy.meet import meet_types +from mypy.messages import format_type_bare +from mypy.nodes import ( + ARG_NAMED, + ARG_NAMED_OPT, + ARG_OPT, + ARG_POS, + MDEF, + Argument, + AssignmentStmt, + CallExpr, + Context, + Decorator, + Expression, + FuncDef, + IndexExpr, + JsonDict, + LambdaExpr, + ListExpr, + MemberExpr, + NameExpr, + OverloadedFuncDef, + PlaceholderNode, + RefExpr, + SymbolTableNode, + TempNode, + TupleExpr, + TypeApplication, + TypeInfo, + TypeVarExpr, + Var, + is_class_var, +) +from mypy.plugin import SemanticAnalyzerPluginInterface +from mypy.plugins.common import ( + _get_argument, + _get_bool_argument, + _get_decorator_bool_argument, + add_attribute_to_class, + add_method_to_class, + deserialize_and_fixup_type, +) +from mypy.server.trigger import make_wildcard_trigger +from mypy.state import state +from mypy.typeops import ( + get_type_vars, + make_simplified_union, + map_type_from_supertype, + type_object_type, +) +from mypy.types import ( + AnyType, + CallableType, + FunctionLike, + Instance, + LiteralType, + NoneType, + Overloaded, + ProperType, + TupleType, + Type, + TypeOfAny, + TypeType, + TypeVarId, + TypeVarType, + UninhabitedType, + UnionType, + get_proper_type, + is_unannotated_any, +) +from mypy.typevars import fill_typevars +from mypy.util import unmangle + +# The names of the different functions that create classes or arguments. +attr_class_makers: Final = {"attr.s", "attr.attrs", "attr.attributes"} +attr_dataclass_makers: Final = {"attr.dataclass"} +attr_frozen_makers: Final = {"attr.frozen", "attrs.frozen"} +attr_define_makers: Final = {"attr.define", "attr.mutable", "attrs.define", "attrs.mutable"} +attr_attrib_makers: Final = {"attr.ib", "attr.attrib", "attr.attr", "attr.field", "attrs.field"} +attr_optional_converters: Final = {"attr.converters.optional", "attrs.converters.optional"} + +SELF_TVAR_NAME: Final = "_AT" +MAGIC_ATTR_NAME: Final = "__attrs_attrs__" +MAGIC_ATTR_CLS_NAME_TEMPLATE: Final = "__{}_AttrsAttributes__" # The tuple subclass pattern. +ATTRS_INIT_NAME: Final = "__attrs_init__" + + +class Converter: + """Holds information about a `converter=` argument""" + + def __init__(self, init_type: Type | None = None, ret_type: Type | None = None) -> None: + self.init_type = init_type + self.ret_type = ret_type + + +class Attribute: + """The value of an attr.ib() call.""" + + def __init__( + self, + name: str, + alias: str | None, + info: TypeInfo, + has_default: bool, + init: bool, + kw_only: bool, + converter: Converter | None, + context: Context, + init_type: Type | None, + ) -> None: + self.name = name + self.alias = alias + self.info = info + self.has_default = has_default + self.init = init + self.kw_only = kw_only + self.converter = converter + self.context = context + self.init_type = init_type + + def argument(self, ctx: mypy.plugin.ClassDefContext) -> Argument: + """Return this attribute as an argument to __init__.""" + assert self.init + init_type: Type | None = None + if self.converter: + if self.converter.init_type: + init_type = self.converter.init_type + if init_type and self.init_type and self.converter.ret_type: + # The converter return type should be the same type as the attribute type. + # Copy type vars from attr type to converter. + converter_vars = get_type_vars(self.converter.ret_type) + init_vars = get_type_vars(self.init_type) + if converter_vars and len(converter_vars) == len(init_vars): + variables = { + binder.id: arg for binder, arg in zip(converter_vars, init_vars) + } + init_type = expand_type(init_type, variables) + else: + ctx.api.fail("Cannot determine __init__ type from converter", self.context) + init_type = AnyType(TypeOfAny.from_error) + else: # There is no converter, the init type is the normal type. + init_type = self.init_type or self.info[self.name].type + + unannotated = False + if init_type is None: + unannotated = True + # Convert type not set to Any. + init_type = AnyType(TypeOfAny.unannotated) + else: + proper_type = get_proper_type(init_type) + if isinstance(proper_type, AnyType): + if proper_type.type_of_any == TypeOfAny.unannotated: + unannotated = True + + if unannotated and ctx.api.options.disallow_untyped_defs: + # This is a compromise. If you don't have a type here then the + # __init__ will be untyped. But since the __init__ is added it's + # pointing at the decorator. So instead we also show the error in the + # assignment, which is where you would fix the issue. + node = self.info[self.name].node + assert node is not None + ctx.api.msg.need_annotation_for_var(node, self.context) + + if self.kw_only: + arg_kind = ARG_NAMED_OPT if self.has_default else ARG_NAMED + else: + arg_kind = ARG_OPT if self.has_default else ARG_POS + + # Attrs removes leading underscores when creating the __init__ arguments. + name = self.alias or self.name.lstrip("_") + return Argument(Var(name, init_type), init_type, None, arg_kind) + + def serialize(self) -> JsonDict: + """Serialize this object so it can be saved and restored.""" + return { + "name": self.name, + "alias": self.alias, + "has_default": self.has_default, + "init": self.init, + "kw_only": self.kw_only, + "has_converter": self.converter is not None, + "converter_init_type": ( + self.converter.init_type.serialize() + if self.converter and self.converter.init_type + else None + ), + "context_line": self.context.line, + "context_column": self.context.column, + "init_type": self.init_type.serialize() if self.init_type else None, + } + + @classmethod + def deserialize( + cls, info: TypeInfo, data: JsonDict, api: SemanticAnalyzerPluginInterface + ) -> Attribute: + """Return the Attribute that was serialized.""" + raw_init_type = data["init_type"] + init_type = deserialize_and_fixup_type(raw_init_type, api) if raw_init_type else None + raw_converter_init_type = data["converter_init_type"] + converter_init_type = ( + deserialize_and_fixup_type(raw_converter_init_type, api) + if raw_converter_init_type + else None + ) + + return Attribute( + data["name"], + data["alias"], + info, + data["has_default"], + data["init"], + data["kw_only"], + Converter(converter_init_type) if data["has_converter"] else None, + Context(line=data["context_line"], column=data["context_column"]), + init_type, + ) + + def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None: + """Expands type vars in the context of a subtype when an attribute is inherited + from a generic super type.""" + if self.init_type: + self.init_type = map_type_from_supertype(self.init_type, sub_type, self.info) + else: + self.init_type = None + + +def _determine_eq_order(ctx: mypy.plugin.ClassDefContext) -> bool: + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + value of order. + """ + cmp = _get_decorator_optional_bool_argument(ctx, "cmp") + eq = _get_decorator_optional_bool_argument(ctx, "eq") + order = _get_decorator_optional_bool_argument(ctx, "order") + + if cmp is not None and any((eq is not None, order is not None)): + ctx.api.fail('Don\'t mix "cmp" with "eq" and "order"', ctx.reason) + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + return cmp + + # If left None, equality is on and ordering mirrors equality. + if eq is None: + eq = True + + if order is None: + order = eq + + if eq is False and order is True: + ctx.api.fail("eq must be True if order is True", ctx.reason) + + return order + + +def _get_decorator_optional_bool_argument( + ctx: mypy.plugin.ClassDefContext, name: str, default: bool | None = None +) -> bool | None: + """Return the Optional[bool] argument for the decorator. + + This handles both @decorator(...) and @decorator. + """ + if isinstance(ctx.reason, CallExpr): + attr_value = _get_argument(ctx.reason, name) + if attr_value: + if isinstance(attr_value, NameExpr): + if attr_value.fullname == "builtins.True": + return True + if attr_value.fullname == "builtins.False": + return False + if attr_value.fullname == "builtins.None": + return None + ctx.api.fail( + f'"{name}" argument must be a True, False, or None literal', + ctx.reason, + code=LITERAL_REQ, + ) + return default + return default + else: + return default + + +def attr_tag_callback(ctx: mypy.plugin.ClassDefContext) -> None: + """Record that we have an attrs class in the main semantic analysis pass. + + The later pass implemented by attr_class_maker_callback will use this + to detect attrs classes in base classes. + """ + # The value is ignored, only the existence matters. + ctx.cls.info.metadata["attrs_tag"] = {} + + +def attr_class_maker_callback( + ctx: mypy.plugin.ClassDefContext, + auto_attribs_default: bool | None = False, + frozen_default: bool = False, + slots_default: bool = False, +) -> bool: + """Add necessary dunder methods to classes decorated with attr.s. + + attrs is a package that lets you define classes without writing dull boilerplate code. + + At a quick glance, the decorator searches the class body for assignments of `attr.ib`s (or + annotated variables if auto_attribs=True), then depending on how the decorator is called, + it will add an __init__ or all the compare methods. + For frozen=True it will turn the attrs into properties. + + Hashability will be set according to https://www.attrs.org/en/stable/hashing.html. + + See https://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works. + + If this returns False, some required metadata was not ready yet, and we need another + pass. + """ + with state.strict_optional_set(ctx.api.options.strict_optional): + # This hook is called during semantic analysis, but it uses a bunch of + # type-checking ops, so it needs the strict optional set properly. + return attr_class_maker_callback_impl( + ctx, auto_attribs_default, frozen_default, slots_default + ) + + +def attr_class_maker_callback_impl( + ctx: mypy.plugin.ClassDefContext, + auto_attribs_default: bool | None, + frozen_default: bool, + slots_default: bool, +) -> bool: + info = ctx.cls.info + + init = _get_decorator_bool_argument(ctx, "init", True) + frozen = _get_frozen(ctx, frozen_default) + order = _determine_eq_order(ctx) + slots = _get_decorator_bool_argument(ctx, "slots", slots_default) + + auto_attribs = _get_decorator_optional_bool_argument(ctx, "auto_attribs", auto_attribs_default) + kw_only = _get_decorator_bool_argument(ctx, "kw_only", False) + match_args = _get_decorator_bool_argument(ctx, "match_args", True) + + for super_info in ctx.cls.info.mro[1:-1]: + if "attrs_tag" in super_info.metadata and "attrs" not in super_info.metadata: + # Super class is not ready yet. Request another pass. + return False + + attributes = _analyze_class(ctx, auto_attribs, kw_only) + + # Check if attribute types are ready. + for attr in attributes: + node = info.get(attr.name) + if node is None: + # This name is likely blocked by some semantic analysis error that + # should have been reported already. + _add_empty_metadata(info) + return True + + _add_attrs_magic_attribute(ctx, [(attr.name, info[attr.name].type) for attr in attributes]) + if slots: + _add_slots(ctx, attributes) + if match_args: + _add_match_args(ctx, attributes) + + # Save the attributes so that subclasses can reuse them. + ctx.cls.info.metadata["attrs"] = { + "attributes": [attr.serialize() for attr in attributes], + "frozen": frozen, + } + + adder = MethodAdder(ctx) + # If __init__ is not being generated, attrs still generates it as __attrs_init__ instead. + _add_init(ctx, attributes, adder, "__init__" if init else ATTRS_INIT_NAME) + + if order: + _add_order(ctx, adder) + if frozen: + _make_frozen(ctx, attributes) + # Frozen classes are hashable by default, even if inheriting from non-frozen ones. + hashable: bool | None = _get_decorator_bool_argument( + ctx, "hash", True + ) and _get_decorator_bool_argument(ctx, "unsafe_hash", True) + else: + hashable = _get_decorator_optional_bool_argument(ctx, "unsafe_hash") + if hashable is None: # unspecified + hashable = _get_decorator_optional_bool_argument(ctx, "hash") + + eq = _get_decorator_optional_bool_argument(ctx, "eq") + has_own_hash = "__hash__" in ctx.cls.info.names + + if has_own_hash or (hashable is None and eq is False): + pass # Do nothing. + elif hashable: + # We copy the `__hash__` signature from `object` to make them hashable. + ctx.cls.info.names["__hash__"] = ctx.cls.info.mro[-1].names["__hash__"] + else: + _remove_hashability(ctx) + + return True + + +def _get_frozen(ctx: mypy.plugin.ClassDefContext, frozen_default: bool) -> bool: + """Return whether this class is frozen.""" + if _get_decorator_bool_argument(ctx, "frozen", frozen_default): + return True + # Subclasses of frozen classes are frozen so check that. + for super_info in ctx.cls.info.mro[1:-1]: + if "attrs" in super_info.metadata and super_info.metadata["attrs"]["frozen"]: + return True + return False + + +def _analyze_class( + ctx: mypy.plugin.ClassDefContext, auto_attribs: bool | None, class_kw_only: bool +) -> list[Attribute]: + """Analyze the class body of an attr maker, its parents, and return the Attributes found. + + auto_attribs=True means we'll generate attributes from type annotations also. + auto_attribs=None means we'll detect which mode to use. + class_kw_only=True means that all attributes created here will be keyword only args by + default in __init__. + """ + own_attrs: dict[str, Attribute] = {} + if auto_attribs is None: + auto_attribs = _detect_auto_attribs(ctx) + + # Walk the body looking for assignments and decorators. + for stmt in ctx.cls.defs.body: + if isinstance(stmt, AssignmentStmt): + for attr in _attributes_from_assignment(ctx, stmt, auto_attribs, class_kw_only): + # When attrs are defined twice in the same body we want to use the 2nd definition + # in the 2nd location. So remove it from the OrderedDict. + # Unless it's auto_attribs in which case we want the 2nd definition in the + # 1st location. + if not auto_attribs and attr.name in own_attrs: + del own_attrs[attr.name] + own_attrs[attr.name] = attr + elif isinstance(stmt, Decorator): + _cleanup_decorator(stmt, own_attrs) + + for attribute in own_attrs.values(): + # Even though these look like class level assignments we want them to look like + # instance level assignments. + if attribute.name in ctx.cls.info.names: + node = ctx.cls.info.names[attribute.name].node + if isinstance(node, PlaceholderNode): + # This node is not ready yet. + continue + # The plugin hooks run once per applied decorator, so a class with + # stacked decorators (e.g. @attrs.define on @attrs.frozen) is + # analyzed twice. On the second run a synthesized ordering method + # from the first run still holds the name, and a class body + # attribute with the same name must not trip the assertion below. + if isinstance(node, FuncDef): + continue + assert isinstance(node, Var), node + node.is_initialized_in_class = False + + # Traverse the MRO and collect attributes from the parents. + taken_attr_names = set(own_attrs) + super_attrs = [] + for super_info in ctx.cls.info.mro[1:-1]: + if "attrs" in super_info.metadata: + # Each class depends on the set of attributes in its attrs ancestors. + ctx.api.add_plugin_dependency(make_wildcard_trigger(super_info.fullname)) + + for data in super_info.metadata["attrs"]["attributes"]: + # Only add an attribute if it hasn't been defined before. This + # allows for overwriting attribute definitions by subclassing. + if data["name"] not in taken_attr_names: + a = Attribute.deserialize(super_info, data, ctx.api) + a.expand_typevar_from_subtype(ctx.cls.info) + super_attrs.append(a) + taken_attr_names.add(a.name) + attributes = super_attrs + list(own_attrs.values()) + + # Check the init args for correct default-ness. Note: This has to be done after all the + # attributes for all classes have been read, because subclasses can override parents. + last_default = False + + for i, attribute in enumerate(attributes): + if not attribute.init: + continue + + if attribute.kw_only: + # Keyword-only attributes don't care whether they are default or not. + continue + + # If the issue comes from merging different classes, report it + # at the class definition point. + context = attribute.context if i >= len(super_attrs) else ctx.cls + + if not attribute.has_default and last_default: + ctx.api.fail("Non-default attributes not allowed after default attributes.", context) + last_default |= attribute.has_default + + return attributes + + +def _add_empty_metadata(info: TypeInfo) -> None: + """Add empty metadata to mark that we've finished processing this class.""" + info.metadata["attrs"] = {"attributes": [], "frozen": False} + + +def _detect_auto_attribs(ctx: mypy.plugin.ClassDefContext) -> bool: + """Return whether auto_attribs should be enabled or disabled. + + It's disabled if there are any unannotated attribs() + """ + for stmt in ctx.cls.defs.body: + if isinstance(stmt, AssignmentStmt): + for lvalue in stmt.lvalues: + lvalues, rvalues = _parse_assignments(lvalue, stmt) + + if len(lvalues) != len(rvalues): + # This means we have some assignment that isn't 1 to 1. + # It can't be an attrib. + continue + + for lhs, rvalue in zip(lvalues, rvalues): + # Check if the right hand side is a call to an attribute maker. + if ( + isinstance(rvalue, CallExpr) + and isinstance(rvalue.callee, RefExpr) + and rvalue.callee.fullname in attr_attrib_makers + and not stmt.new_syntax + ): + # This means we have an attrib without an annotation and so + # we can't do auto_attribs=True + return False + return True + + +def _attributes_from_assignment( + ctx: mypy.plugin.ClassDefContext, stmt: AssignmentStmt, auto_attribs: bool, class_kw_only: bool +) -> Iterable[Attribute]: + """Return Attribute objects that are created by this assignment. + + The assignments can look like this: + x = attr.ib() + x = y = attr.ib() + x, y = attr.ib(), attr.ib() + or if auto_attribs is enabled also like this: + x: type + x: type = default_value + x: type = attr.ib(...) + """ + for lvalue in stmt.lvalues: + lvalues, rvalues = _parse_assignments(lvalue, stmt) + + if len(lvalues) != len(rvalues): + # This means we have some assignment that isn't 1 to 1. + # It can't be an attrib. + continue + + for lhs, rvalue in zip(lvalues, rvalues): + # Check if the right hand side is a call to an attribute maker. + if ( + isinstance(rvalue, CallExpr) + and isinstance(rvalue.callee, RefExpr) + and rvalue.callee.fullname in attr_attrib_makers + ): + attr = _attribute_from_attrib_maker( + ctx, auto_attribs, class_kw_only, lhs, rvalue, stmt + ) + if attr: + yield attr + elif auto_attribs and stmt.type and stmt.new_syntax and not is_class_var(lhs): + yield _attribute_from_auto_attrib(ctx, class_kw_only, lhs, rvalue, stmt) + + +def _cleanup_decorator(stmt: Decorator, attr_map: dict[str, Attribute]) -> None: + """Handle decorators in class bodies. + + `x.default` will set a default value on x + `x.validator` and `x.default` will get removed to avoid throwing a type error. + """ + remove_me = [] + for func_decorator in stmt.decorators: + if ( + isinstance(func_decorator, MemberExpr) + and isinstance(func_decorator.expr, NameExpr) + and func_decorator.expr.name in attr_map + ): + if func_decorator.name == "default": + attr_map[func_decorator.expr.name].has_default = True + + if func_decorator.name in ("default", "validator"): + # These are decorators on the attrib object that only exist during + # class creation time. In order to not trigger a type error later we + # just remove them. This might leave us with a Decorator with no + # decorators (Emperor's new clothes?) + # TODO: It would be nice to type-check these rather than remove them. + # default should be Callable[[], T] + # validator should be Callable[[Any, 'Attribute', T], Any] + # where T is the type of the attribute. + remove_me.append(func_decorator) + for dec in remove_me: + stmt.decorators.remove(dec) + + +def _attribute_from_auto_attrib( + ctx: mypy.plugin.ClassDefContext, + class_kw_only: bool, + lhs: NameExpr, + rvalue: Expression, + stmt: AssignmentStmt, +) -> Attribute: + """Return an Attribute for a new type assignment.""" + name = unmangle(lhs.name) + # `x: int` (without equal sign) assigns rvalue to TempNode(AnyType()) + has_rhs = not isinstance(rvalue, TempNode) + sym = ctx.cls.info.names.get(name) + init_type = sym.type if sym else None + return Attribute(name, None, ctx.cls.info, has_rhs, True, class_kw_only, None, stmt, init_type) + + +def _attribute_from_attrib_maker( + ctx: mypy.plugin.ClassDefContext, + auto_attribs: bool, + class_kw_only: bool, + lhs: NameExpr, + rvalue: CallExpr, + stmt: AssignmentStmt, +) -> Attribute | None: + """Return an Attribute from the assignment or None if you can't make one.""" + if auto_attribs and not stmt.new_syntax: + # auto_attribs requires an annotation on *every* attr.ib. + assert lhs.node is not None + ctx.api.msg.need_annotation_for_var(lhs.node, stmt) + return None + + if len(stmt.lvalues) > 1: + ctx.api.fail("Too many names for one attribute", stmt) + return None + + # This is the type that belongs in the __init__ method for this attrib. + init_type = stmt.type + + # Read all the arguments from the call. + init = _get_bool_argument(ctx, rvalue, "init", True) + # The class decorator kw_only value can be overridden by the attribute value + # See https://github.com/python-attrs/attrs/pull/1457 + kw_only = _get_bool_argument(ctx, rvalue, "kw_only", class_kw_only) + + # TODO: Check for attr.NOTHING + attr_has_default = bool(_get_argument(rvalue, "default")) + attr_has_factory = bool(_get_argument(rvalue, "factory")) + + if attr_has_default and attr_has_factory: + ctx.api.fail('Can\'t pass both "default" and "factory".', rvalue) + elif attr_has_factory: + attr_has_default = True + + # If the type isn't set through annotation but is passed through `type=` use that. + type_arg = _get_argument(rvalue, "type") + if type_arg and not init_type: + try: + un_type = expr_to_unanalyzed_type(type_arg, ctx.api.options, ctx.api.is_stub_file) + except TypeTranslationError: + ctx.api.fail("Invalid argument to type", type_arg) + else: + init_type = ctx.api.anal_type(un_type) + if init_type and isinstance(lhs.node, Var) and not lhs.node.type: + # If there is no annotation, add one. + lhs.node.type = init_type + lhs.is_inferred_def = False + + # Note: convert is deprecated but works the same as converter. + converter = _get_argument(rvalue, "converter") + convert = _get_argument(rvalue, "convert") + if convert and converter: + ctx.api.fail('Can\'t pass both "convert" and "converter".', rvalue) + elif convert: + ctx.api.fail("convert is deprecated, use converter", rvalue) + converter = convert + converter_info = _parse_converter(ctx, converter) + + # Custom alias might be defined: + alias = None + alias_expr = _get_argument(rvalue, "alias") + if alias_expr: + alias = ctx.api.parse_str_literal(alias_expr) + if alias is None: + ctx.api.fail( + '"alias" argument to attrs field must be a string literal', + rvalue, + code=LITERAL_REQ, + ) + name = unmangle(lhs.name) + return Attribute( + name, alias, ctx.cls.info, attr_has_default, init, kw_only, converter_info, stmt, init_type + ) + + +def _parse_converter( + ctx: mypy.plugin.ClassDefContext, converter_expr: Expression | None +) -> Converter | None: + """Return the Converter object from an Expression.""" + # TODO: Support complex converters, e.g. lambdas, calls, etc. + if not converter_expr: + return None + converter_info = Converter() + if ( + isinstance(converter_expr, CallExpr) + and isinstance(converter_expr.callee, RefExpr) + and converter_expr.callee.fullname in attr_optional_converters + and converter_expr.args + and converter_expr.args[0] + ): + # Special handling for attr.converters.optional(type) + # We extract the type and add make the init_args Optional in Attribute.argument + converter_expr = converter_expr.args[0] + is_attr_converters_optional = True + else: + is_attr_converters_optional = False + + converter_type: Type | None = None + if isinstance(converter_expr, RefExpr) and converter_expr.node: + if isinstance(converter_expr.node, FuncDef): + if converter_expr.node.type and isinstance(converter_expr.node.type, FunctionLike): + converter_type = converter_expr.node.type + else: # The converter is an unannotated function. + converter_info.init_type = AnyType(TypeOfAny.unannotated) + return converter_info + elif isinstance(converter_expr.node, OverloadedFuncDef) and is_valid_overloaded_converter( + converter_expr.node + ): + converter_type = converter_expr.node.type + elif isinstance(converter_expr.node, TypeInfo): + converter_type = type_object_type(converter_expr.node) + elif ( + isinstance(converter_expr, IndexExpr) + and isinstance(converter_expr.analyzed, TypeApplication) + and isinstance(converter_expr.base, RefExpr) + and isinstance(converter_expr.base.node, TypeInfo) + ): + # The converter is a generic type. + converter_type = type_object_type(converter_expr.base.node) + if isinstance(converter_type, CallableType): + converter_type = apply_generic_arguments( + converter_type, + converter_expr.analyzed.types, + ctx.api.msg.incompatible_typevar_value, + converter_type, + ) + else: + converter_type = None + + if isinstance(converter_expr, LambdaExpr): + # TODO: should we send a fail if converter_expr.min_args > 1? + converter_info.init_type = AnyType(TypeOfAny.unannotated) + return converter_info + + if not converter_type: + # Signal that we have an unsupported converter. + ctx.api.fail( + "Unsupported converter, only named functions, types and lambdas are currently " + "supported", + converter_expr, + ) + converter_info.init_type = AnyType(TypeOfAny.from_error) + return converter_info + + converter_type = get_proper_type(converter_type) + if isinstance(converter_type, CallableType) and converter_type.arg_types: + converter_info.init_type = converter_type.arg_types[0] + if not is_attr_converters_optional: + converter_info.ret_type = converter_type.ret_type + elif isinstance(converter_type, Overloaded): + types: list[Type] = [] + for item in converter_type.items: + # Walk the overloads looking for methods that can accept one argument. + num_arg_types = len(item.arg_types) + if not num_arg_types: + continue + if num_arg_types > 1 and any(kind == ARG_POS for kind in item.arg_kinds[1:]): + continue + types.append(item.arg_types[0]) + # Make a union of all the valid types. + if types: + converter_info.init_type = make_simplified_union(types) + + if is_attr_converters_optional and converter_info.init_type: + # If the converter was attr.converter.optional(type) then add None to + # the allowed init_type. + converter_info.init_type = UnionType.make_union([converter_info.init_type, NoneType()]) + + return converter_info + + +def is_valid_overloaded_converter(defn: OverloadedFuncDef) -> bool: + return all( + (not isinstance(item, Decorator) or isinstance(item.func.type, FunctionLike)) + for item in defn.items + ) + + +def _parse_assignments( + lvalue: Expression, stmt: AssignmentStmt +) -> tuple[list[NameExpr], list[Expression]]: + """Convert a possibly complex assignment expression into lists of lvalues and rvalues.""" + lvalues: list[NameExpr] = [] + rvalues: list[Expression] = [] + if isinstance(lvalue, (TupleExpr, ListExpr)): + if all(isinstance(item, NameExpr) for item in lvalue.items): + lvalues = cast(list[NameExpr], lvalue.items) + if isinstance(stmt.rvalue, (TupleExpr, ListExpr)): + rvalues = stmt.rvalue.items + elif isinstance(lvalue, NameExpr): + lvalues = [lvalue] + rvalues = [stmt.rvalue] + return lvalues, rvalues + + +def _add_order(ctx: mypy.plugin.ClassDefContext, adder: MethodAdder) -> None: + """Generate all the ordering methods for this class.""" + bool_type = ctx.api.named_type("builtins.bool") + object_type = ctx.api.named_type("builtins.object") + # Make the types be: + # AT = TypeVar('AT') + # def __lt__(self: AT, other: AT) -> bool + # This way comparisons with subclasses will work correctly. + fullname = f"{ctx.cls.info.fullname}.{SELF_TVAR_NAME}" + tvd = TypeVarType( + SELF_TVAR_NAME, + fullname, + # Namespace is patched per-method below. + id=TypeVarId(-1, namespace=""), + values=[], + upper_bound=object_type, + default=AnyType(TypeOfAny.from_omitted_generics), + ) + self_tvar_expr = TypeVarExpr( + SELF_TVAR_NAME, fullname, [], object_type, AnyType(TypeOfAny.from_omitted_generics) + ) + ctx.cls.info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr) + + for method in ["__lt__", "__le__", "__gt__", "__ge__"]: + namespace = f"{ctx.cls.info.fullname}.{method}" + tvd = tvd.copy_modified(id=TypeVarId(tvd.id.raw_id, namespace=namespace)) + args = [Argument(Var("other", tvd), tvd, None, ARG_POS)] + adder.add_method(method, args, bool_type, self_type=tvd, tvd=tvd) + + +def _make_frozen(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: + """Turn all the attributes into properties to simulate frozen classes.""" + for attribute in attributes: + if attribute.name in ctx.cls.info.names: + # This variable belongs to this class so we can modify it. + node = ctx.cls.info.names[attribute.name].node + if not isinstance(node, Var): + # The superclass attribute was overridden with a non-variable. + # No need to do anything here, override will be verified during + # type checking. + continue + node.is_property = True + else: + # This variable belongs to a super class so create new Var so we + # can modify it. + var = Var(attribute.name, attribute.init_type) + var.info = ctx.cls.info + var._fullname = f"{ctx.cls.info.fullname}.{var.name}" + ctx.cls.info.names[var.name] = SymbolTableNode(MDEF, var) + var.is_property = True + + +def _add_init( + ctx: mypy.plugin.ClassDefContext, + attributes: list[Attribute], + adder: MethodAdder, + method_name: Literal["__init__", "__attrs_init__"], +) -> None: + """Generate an __init__ method for the attributes and add it to the class.""" + # Convert attributes to arguments with kw_only arguments at the end of + # the argument list + pos_args = [] + kw_only_args = [] + sym_table = ctx.cls.info.names + for attribute in attributes: + if not attribute.init: + continue + if attribute.kw_only: + kw_only_args.append(attribute.argument(ctx)) + else: + pos_args.append(attribute.argument(ctx)) + + # If the attribute is Final, present in `__init__` and has + # no default, make sure it doesn't error later. + if not attribute.has_default and attribute.name in sym_table: + sym_node = sym_table[attribute.name].node + if isinstance(sym_node, Var) and sym_node.is_final: + sym_node.final_set_in_init = True + args = pos_args + kw_only_args + if all(arg.variable.type and is_unannotated_any(arg.variable.type) for arg in args): + # This workaround makes --disallow-incomplete-defs usable with attrs, + # but is definitely suboptimal as a long-term solution. + # See https://github.com/python/mypy/issues/5954 for discussion. + for a in args: + a.variable.type = AnyType(TypeOfAny.implementation_artifact) + a.type_annotation = AnyType(TypeOfAny.implementation_artifact) + adder.add_method(method_name, args, NoneType()) + + +def _add_attrs_magic_attribute( + ctx: mypy.plugin.ClassDefContext, attrs: list[tuple[str, Type | None]] +) -> None: + any_type = AnyType(TypeOfAny.explicit) + attributes_types: list[Type] = [ + ctx.api.named_type_or_none("attr.Attribute", [attr_type or any_type]) or any_type + for _, attr_type in attrs + ] + fallback_type = ctx.api.named_type( + "builtins.tuple", [ctx.api.named_type_or_none("attr.Attribute", [any_type]) or any_type] + ) + + attr_name = MAGIC_ATTR_CLS_NAME_TEMPLATE.format(ctx.cls.fullname.replace(".", "_")) + ti = ctx.api.basic_new_typeinfo(attr_name, fallback_type, 0) + for (name, _), attr_type in zip(attrs, attributes_types): + var = Var(name, attr_type) + var._fullname = name + var.is_property = True + proper_type = get_proper_type(attr_type) + if isinstance(proper_type, Instance): + var.info = proper_type.type + ti.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True) + attributes_type = Instance(ti, []) + + # We need to stash the type of the magic attribute so it can be + # loaded on cached runs. + ctx.cls.info.names[attr_name] = SymbolTableNode(MDEF, ti, plugin_generated=True) + + add_attribute_to_class( + ctx.api, + ctx.cls, + MAGIC_ATTR_NAME, + TupleType(attributes_types, fallback=attributes_type), + fullname=f"{ctx.cls.fullname}.{MAGIC_ATTR_NAME}", + override_allow_incompatible=True, + is_classvar=True, + ) + + +def _add_slots(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: + if any(p.slots is None for p in ctx.cls.info.mro[1:-1]): + # At least one type in mro (excluding `self` and `object`) + # does not have concrete `__slots__` defined. Ignoring. + return + + # Unlike `@dataclasses.dataclass`, `__slots__` is rewritten here. + ctx.cls.info.slots = {attr.name for attr in attributes} + + # Also, inject `__slots__` attribute to class namespace: + slots_type = TupleType( + [ctx.api.named_type("builtins.str") for _ in attributes], + fallback=ctx.api.named_type("builtins.tuple"), + ) + add_attribute_to_class(api=ctx.api, cls=ctx.cls, name="__slots__", typ=slots_type) + + +def _add_match_args(ctx: mypy.plugin.ClassDefContext, attributes: list[Attribute]) -> None: + if ( + "__match_args__" not in ctx.cls.info.names + or ctx.cls.info.names["__match_args__"].plugin_generated + ): + str_type = ctx.api.named_type("builtins.str") + match_args = TupleType( + [ + str_type.copy_modified(last_known_value=LiteralType(attr.name, fallback=str_type)) + for attr in attributes + if not attr.kw_only and attr.init + ], + fallback=ctx.api.named_type("builtins.tuple"), + ) + add_attribute_to_class(api=ctx.api, cls=ctx.cls, name="__match_args__", typ=match_args) + + +def _remove_hashability(ctx: mypy.plugin.ClassDefContext) -> None: + """Remove hashability from a class.""" + add_attribute_to_class( + ctx.api, ctx.cls, "__hash__", NoneType(), is_classvar=True, overwrite_existing=True + ) + + +class MethodAdder: + """Helper to add methods to a TypeInfo. + + ctx: The ClassDefCtx we are using on which we will add methods. + """ + + # TODO: Combine this with the code build_namedtuple_typeinfo to support both. + + def __init__(self, ctx: mypy.plugin.ClassDefContext) -> None: + self.ctx = ctx + self.self_type = fill_typevars(ctx.cls.info) + + def add_method( + self, + method_name: str, + args: list[Argument], + ret_type: Type, + self_type: Type | None = None, + tvd: TypeVarType | None = None, + ) -> None: + """Add a method: def (self, ) -> ): ... to info. + + self_type: The type to use for the self argument or None to use the inferred self type. + tvd: If the method is generic these should be the type variables. + """ + self_type = self_type if self_type is not None else self.self_type + add_method_to_class( + self.ctx.api, self.ctx.cls, method_name, args, ret_type, self_type, tvd + ) + + +def _get_attrs_init_type(typ: Instance) -> CallableType | None: + """ + If `typ` refers to an attrs class, get the type of its initializer method. + """ + magic_attr = typ.type.get(MAGIC_ATTR_NAME) + if magic_attr is None or not magic_attr.plugin_generated: + return None + init_method = typ.type.get_method("__init__") or typ.type.get_method(ATTRS_INIT_NAME) + if not isinstance(init_method, FuncDef) or not isinstance(init_method.type, CallableType): + return None + return init_method.type + + +def _fail_not_attrs_class(ctx: mypy.plugin.FunctionSigContext, t: Type, parent_t: Type) -> None: + t_name = format_type_bare(t, ctx.api.options) + if parent_t is t: + msg = ( + f'Argument 1 to "evolve" has a variable type "{t_name}" not bound to an attrs class' + if isinstance(t, TypeVarType) + else f'Argument 1 to "evolve" has incompatible type "{t_name}"; expected an attrs class' + ) + else: + pt_name = format_type_bare(parent_t, ctx.api.options) + msg = ( + f'Argument 1 to "evolve" has type "{pt_name}" whose item "{t_name}" is not bound to an attrs class' + if isinstance(t, TypeVarType) + else f'Argument 1 to "evolve" has incompatible type "{pt_name}" whose item "{t_name}" is not an attrs class' + ) + + ctx.api.fail(msg, ctx.context) + + +def _get_expanded_attr_types( + ctx: mypy.plugin.FunctionSigContext, + typ: ProperType, + display_typ: ProperType, + parent_typ: ProperType, +) -> list[Mapping[str, Type]] | None: + """ + For a given type, determine what attrs classes it can be: for each class, return the field types. + For generic classes, the field types are expanded. + If the type contains Any or a non-attrs type, returns None; in the latter case, also reports an error. + """ + if isinstance(typ, AnyType): + return None + elif isinstance(typ, UnionType): + ret: list[Mapping[str, Type]] | None = [] + for item in typ.relevant_items(): + item = get_proper_type(item) + item_types = _get_expanded_attr_types(ctx, item, item, parent_typ) + if ret is not None and item_types is not None: + ret += item_types + else: + ret = None # but keep iterating to emit all errors + return ret + elif isinstance(typ, TypeVarType): + return _get_expanded_attr_types( + ctx, get_proper_type(typ.upper_bound), display_typ, parent_typ + ) + elif isinstance(typ, Instance): + init_func = _get_attrs_init_type(typ) + if init_func is None: + _fail_not_attrs_class(ctx, display_typ, parent_typ) + return None + init_func = expand_type_by_instance(init_func, typ) + # [1:] to skip the self argument of AttrClass.__init__ + field_names = cast(list[str], init_func.arg_names[1:]) + field_types = init_func.arg_types[1:] + return [dict(zip(field_names, field_types))] + else: + _fail_not_attrs_class(ctx, display_typ, parent_typ) + return None + + +def _meet_fields(types: list[Mapping[str, Type]]) -> Mapping[str, Type]: + """ + "Meet" the fields of a list of attrs classes, i.e. for each field, its new type will be the lower bound. + """ + field_to_types = defaultdict(list) + for fields in types: + for name, typ in fields.items(): + field_to_types[name].append(typ) + + return { + name: ( + get_proper_type(reduce(meet_types, f_types)) + if len(f_types) == len(types) + else UninhabitedType() + ) + for name, f_types in field_to_types.items() + } + + +def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> CallableType: + """ + Generate a signature for the 'attr.evolve' function that's specific to the call site + and dependent on the type of the first argument. + """ + if len(ctx.args) != 2: + # Ideally the name and context should be callee's, but we don't have it in FunctionSigContext. + ctx.api.fail(f'"{ctx.default_signature.name}" has unexpected type annotation', ctx.context) + return ctx.default_signature + + if len(ctx.args[0]) != 1: + return ctx.default_signature # leave it to the type checker to complain + + inst_arg = ctx.args[0][0] + inst_type = get_proper_type(ctx.api.get_expression_type(inst_arg)) + inst_type_str = format_type_bare(inst_type, ctx.api.options) + + attr_types = _get_expanded_attr_types(ctx, inst_type, inst_type, inst_type) + if attr_types is None: + return ctx.default_signature + fields = _meet_fields(attr_types) + + return CallableType( + arg_names=["inst", *fields.keys()], + arg_kinds=[ARG_POS] + [ARG_NAMED_OPT] * len(fields), + arg_types=[inst_type, *fields.values()], + ret_type=inst_type, + fallback=ctx.default_signature.fallback, + name=f"{ctx.default_signature.name} of {inst_type_str}", + ) + + +def fields_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> CallableType: + """Provide the signature for `attrs.fields`.""" + if len(ctx.args) != 1 or len(ctx.args[0]) != 1: + return ctx.default_signature + + proper_type = get_proper_type(ctx.api.get_expression_type(ctx.args[0][0])) + + # fields(Any) -> Any, fields(type[Any]) -> Any + if ( + isinstance(proper_type, AnyType) + or isinstance(proper_type, TypeType) + and isinstance(proper_type.item, AnyType) + ): + return ctx.default_signature + + cls = None + arg_types = ctx.default_signature.arg_types + + if isinstance(proper_type, TypeVarType): + inner = get_proper_type(proper_type.upper_bound) + if isinstance(inner, Instance): + # We need to work arg_types to compensate for the attrs stubs. + arg_types = [proper_type] + cls = inner.type + elif isinstance(proper_type, CallableType): + cls = proper_type.type_object() + + if cls is not None and MAGIC_ATTR_NAME in cls.names: + # This is a proper attrs class. + ret_type = cls.names[MAGIC_ATTR_NAME].type + assert ret_type is not None + return ctx.default_signature.copy_modified(arg_types=arg_types, ret_type=ret_type) + + return ctx.default_signature diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index 7d453dcf84d01..cab50159a1601 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -1,2514 +1,2528 @@ -[case testAttrsSimple_no_empty] -import attr -@attr.s -class A: - a = attr.ib() - _b = attr.ib() - c = attr.ib(18) - _d = attr.ib(validator=None, default=18) - E = 18 - - def foo(self): - return self.a -reveal_type(A) # N: Revealed type is "def (a: Any, b: Any, c: Any =, d: Any =) -> __main__.A" -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] - -[case testAttrsAnnotated] -import attr -from typing import List, ClassVar -@attr.s -class A: - a: int = attr.ib() - _b: List[int] = attr.ib() - c: str = attr.ib('18') - _d: int = attr.ib(validator=None, default=18) - E = 7 - F: ClassVar[int] = 22 -reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> __main__.A" -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] - -[case testAttrsTypeComments] -import attr -from typing import List, ClassVar -@attr.s -class A: - a = attr.ib() # type: int - _b = attr.ib() # type: List[int] - c = attr.ib('18') # type: str - _d = attr.ib(validator=None, default=18) # type: int - E = 7 - F: ClassVar[int] = 22 -reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> __main__.A" -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] - -[case testAttrsAutoAttribs] -import attr -from typing import List, ClassVar -@attr.s(auto_attribs=True) -class A: - a: int - _b: List[int] - c: str = '18' - _d: int = attr.ib(validator=None, default=18) - E = 7 - F: ClassVar[int] = 22 -reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> __main__.A" -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] - -[case testAttrsUntypedNoUntypedDefs] -# flags: --disallow-untyped-defs -import attr -@attr.s -class A: - a = attr.ib() # E: Need type annotation for "a" - _b = attr.ib() # E: Need type annotation for "_b" - 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] - -[case testAttrsWrongReturnValue] -import attr -@attr.s -class A: - x: int = attr.ib(8) - def foo(self) -> str: - return self.x # E: Incompatible return value type (got "int", expected "str") -@attr.s -class B: - x = attr.ib(8) # type: int - def foo(self) -> str: - return self.x # E: Incompatible return value type (got "int", expected "str") -@attr.dataclass -class C: - x: int = 8 - def foo(self) -> str: - return self.x # E: Incompatible return value type (got "int", expected "str") -@attr.s -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] - -[case testAttrsSeriousNames] -from attr import attrib, attrs -from typing import List -@attrs(init=True) -class A: - a = attrib() - _b: List[int] = attrib() - c = attrib(18) - _d = attrib(validator=None, default=18) - CLASS_VAR = 18 -reveal_type(A) # N: Revealed type is "def (a: Any, b: builtins.list[builtins.int], c: Any =, d: Any =) -> __main__.A" -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] - -[case testAttrsDefaultErrors] -import attr -@attr.s -class A: - x = attr.ib(default=17) - y = attr.ib() # E: Non-default attributes not allowed after default attributes. -@attr.s(auto_attribs=True) -class B: - x: int = 17 - y: int # E: Non-default attributes not allowed after default attributes. -@attr.s(auto_attribs=True) -class C: - x: int = attr.ib(default=17) - y: int # E: Non-default attributes not allowed after default attributes. -@attr.s -class D: - x = attr.ib() - y = attr.ib() # E: Non-default attributes not allowed after default attributes. - - @x.default - def foo(self): - return 17 -[builtins fixtures/bool.pyi] - -[case testAttrsNotBooleans] -import attr -x = True -@attr.s(cmp=x) # E: "cmp" argument must be a True, False, or None literal -class A: - a = attr.ib(init=x) # E: "init" argument must be a True or False literal -[builtins fixtures/bool.pyi] - -[case testAttrsInitFalse] -from attr import attrib, attrs -@attrs(auto_attribs=True, init=False) -class A: - a: int - _b: int - c: int = 18 - _d: int = attrib(validator=None, default=18) -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] - -[case testAttrsInitAttribFalse] -from attr import attrib, attrs -@attrs -class A: - a = attrib(init=False) - b = attrib() -reveal_type(A) # N: Revealed type is "def (b: Any) -> __main__.A" -[builtins fixtures/bool.pyi] - -[case testAttrsCmpTrue] -from attr import attrib, attrs -@attrs(auto_attribs=True) -class A: - a: int -reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" -reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`3, other: _AT`3) -> builtins.bool" -reveal_type(A.__le__) # N: Revealed type is "def [_AT] (self: _AT`4, other: _AT`4) -> builtins.bool" -reveal_type(A.__gt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool" -reveal_type(A.__ge__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool" - -A(1) < A(2) -A(1) <= A(2) -A(1) > A(2) -A(1) >= A(2) -A(1) == A(2) -A(1) != A(2) - -A(1) < 1 # E: Unsupported operand types for < ("A" and "int") -A(1) <= 1 # E: Unsupported operand types for <= ("A" and "int") -A(1) > 1 # E: Unsupported operand types for > ("A" and "int") -A(1) >= 1 # E: Unsupported operand types for >= ("A" and "int") -A(1) == 1 -A(1) != 1 - -1 < A(1) # E: Unsupported operand types for > ("A" and "int") -1 <= A(1) # E: Unsupported operand types for >= ("A" and "int") -1 > A(1) # E: Unsupported operand types for < ("A" and "int") -1 >= A(1) # E: Unsupported operand types for <= ("A" and "int") -1 == A(1) -1 != A(1) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsEqFalse] -from attr import attrib, attrs -@attrs(auto_attribs=True, eq=False) -class A: - a: int -reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" -reveal_type(A.__eq__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" -reveal_type(A.__ne__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" - -A(1) < A(2) # E: Unsupported left operand type for < ("A") -A(1) <= A(2) # E: Unsupported left operand type for <= ("A") -A(1) > A(2) # E: Unsupported left operand type for > ("A") -A(1) >= A(2) # E: Unsupported left operand type for >= ("A") -A(1) == A(2) -A(1) != A(2) - -A(1) < 1 # E: Unsupported left operand type for < ("A") -A(1) <= 1 # E: Unsupported left operand type for <= ("A") -A(1) > 1 # E: Unsupported left operand type for > ("A") -A(1) >= 1 # E: Unsupported left operand type for >= ("A") -A(1) == 1 -A(1) != 1 - -1 < A(1) # E: Unsupported left operand type for < ("int") -1 <= A(1) # E: Unsupported left operand type for <= ("int") -1 > A(1) # E: Unsupported left operand type for > ("int") -1 >= A(1) # E: Unsupported left operand type for >= ("int") -1 == A(1) -1 != A(1) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsOrderFalse] -from attr import attrib, attrs -@attrs(auto_attribs=True, order=False) -class A: - a: int -reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" - -A(1) < A(2) # E: Unsupported left operand type for < ("A") -A(1) <= A(2) # E: Unsupported left operand type for <= ("A") -A(1) > A(2) # E: Unsupported left operand type for > ("A") -A(1) >= A(2) # E: Unsupported left operand type for >= ("A") -A(1) == A(2) -A(1) != A(2) - -A(1) < 1 # E: Unsupported left operand type for < ("A") -A(1) <= 1 # E: Unsupported left operand type for <= ("A") -A(1) > 1 # E: Unsupported left operand type for > ("A") -A(1) >= 1 # E: Unsupported left operand type for >= ("A") -A(1) == 1 -A(1) != 1 - -1 < A(1) # E: Unsupported left operand type for < ("int") -1 <= A(1) # E: Unsupported left operand type for <= ("int") -1 > A(1) # E: Unsupported left operand type for > ("int") -1 >= A(1) # E: Unsupported left operand type for >= ("int") -1 == A(1) -1 != A(1) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsCmpEqOrderValues] -from attr import attrib, attrs -@attrs(cmp=True) -class DeprecatedTrue: - ... - -@attrs(cmp=False) -class DeprecatedFalse: - ... - -@attrs(cmp=False, eq=True) # E: Don't mix "cmp" with "eq" and "order" -class Mixed: - ... - -@attrs(order=True, eq=False) # E: eq must be True if order is True -class Confused: - ... -[builtins fixtures/plugin_attrs.pyi] - - -[case testAttrsInheritance] -import attr -@attr.s -class A: - a: int = attr.ib() -@attr.s -class B: - b: str = attr.ib() -@attr.s -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] - -[case testAttrsNestedInClasses] -import attr -@attr.s -class C: - y = attr.ib() - @attr.s - class D: - 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] - -[case testAttrsInheritanceOverride] -import attr - -@attr.s -class A: - a: int = attr.ib() - x: int = attr.ib() - -@attr.s -class B(A): - b: str = attr.ib() - x: int = attr.ib(default=22) - -@attr.s -class C(B): - c: bool = attr.ib() # No error here because the x below overwrites the x above. - x: int = attr.ib() - -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] - -[case testAttrsTypeEquals] -import attr - -@attr.s -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] - -[case testAttrsFrozen] -import attr - -@attr.s(frozen=True) -class A: - a = attr.ib() - -a = A(5) -a.a = 16 # E: Property "a" defined in "A" is read-only -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsNextGenFrozen] -from attr import frozen, field - -@frozen -class A: - a = field() - -a = A(5) -a.a = 16 # E: Property "a" defined in "A" is read-only -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsNextGenDetect] -from attr import define, field - -@define -class A: - a = field() - -@define -class B: - a: int - -@define -class C: - a: int = field() - b = field() - -@define -class D: - a: int - b = field() - -reveal_type(A) # N: Revealed type is "def (a: Any) -> __main__.A" -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] - -[case testAttrsOldPackage] -import attr -@attr.s(auto_attribs=True) -class A: - a: int = attr.ib() - b: bool - -@attr.s(auto_attribs=True, frozen=True) -class B: - a: bool - b: int - -@attr.s -class C: - a = attr.ib(type=int) - -reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.bool) -> __main__.A" -reveal_type(B) # N: Revealed type is "def (a: builtins.bool, b: builtins.int) -> __main__.B" -reveal_type(C) # N: Revealed type is "def (a: builtins.int) -> __main__.C" - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsDataClass] -import attr -from typing import List, ClassVar -@attr.dataclass -class A: - a: int - _b: List[str] - c: str = '18' - _d: int = attr.ib(validator=None, default=18) - E = 7 - 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] - -[case testAttrsTypeAlias] -from typing import List -import attr -Alias = List[int] -@attr.s(auto_attribs=True) -class A: - Alias2 = List[str] - 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] - -[case testAttrsGeneric] -from typing import TypeVar, Generic, List -import attr -T = TypeVar('T') -@attr.s(auto_attribs=True) -class A(Generic[T]): - x: List[T] - y: T = attr.ib() - def foo(self) -> List[T]: - return [self.y] - def bar(self) -> T: - return self.x[0] - def problem(self) -> T: - return self.x # E: Incompatible return value type (got "list[T]", expected "T") -reveal_type(A) # N: Revealed type is "def [T] (x: builtins.list[T`1], y: T`1) -> __main__.A[T`1]" -a = A([1], 2) -reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]" -reveal_type(a.x) # N: Revealed type is "builtins.list[builtins.int]" -reveal_type(a.y) # N: Revealed type is "builtins.int" - -A(['str'], 7) # E: Cannot infer value of type parameter "T" of "A" -A([1], '2') # E: Cannot infer value of type parameter "T" of "A" - -[builtins fixtures/list.pyi] - -[case testAttrsGenericWithConverter] -from typing import TypeVar, Generic, List, Iterable, Iterator, Callable -import attr -T = TypeVar('T') - -def int_gen() -> Iterator[int]: - yield 1 - -def list_converter(x: Iterable[T]) -> List[T]: - return list(x) - -@attr.s(auto_attribs=True) -class A(Generic[T]): - x: List[T] = attr.ib(converter=list_converter) - y: T = attr.ib() - def foo(self) -> List[T]: - return [self.y] - def bar(self) -> T: - return self.x[0] - def problem(self) -> T: - return self.x # E: Incompatible return value type (got "list[T]", expected "T") -reveal_type(A) # N: Revealed type is "def [T] (x: typing.Iterable[T`1], y: T`1) -> __main__.A[T`1]" -a1 = A([1], 2) -reveal_type(a1) # N: Revealed type is "__main__.A[builtins.int]" -reveal_type(a1.x) # N: Revealed type is "builtins.list[builtins.int]" -reveal_type(a1.y) # N: Revealed type is "builtins.int" - -a2 = A(int_gen(), 2) -reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" -reveal_type(a2.x) # N: Revealed type is "builtins.list[builtins.int]" -reveal_type(a2.y) # N: Revealed type is "builtins.int" - - -def get_int() -> int: - return 1 - -class Other(Generic[T]): - def __init__(self, x: T) -> None: - pass - -@attr.s(auto_attribs=True) -class B(Generic[T]): - x: Other[Callable[..., T]] = attr.ib(converter=Other[Callable[..., T]]) - -b1 = B(get_int) -reveal_type(b1) # N: Revealed type is "__main__.B[builtins.int]" -reveal_type(b1.x) # N: Revealed type is "__main__.Other[def (*Any, **Any) -> builtins.int]" - -[builtins fixtures/list.pyi] - - -[case testAttrsUntypedGenericInheritance] -from typing import Generic, TypeVar -import attr - -T = TypeVar("T") - -@attr.s(auto_attribs=True) -class Base(Generic[T]): - attr: T - -@attr.s(auto_attribs=True) -class Sub(Base): - pass - -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] - - -[case testAttrsGenericInheritance] -from typing import Generic, TypeVar -import attr - -S = TypeVar("S") -T = TypeVar("T") - -@attr.s(auto_attribs=True) -class Base(Generic[T]): - attr: T - -@attr.s(auto_attribs=True) -class Sub(Base[S]): - pass - -sub_int = Sub[int](attr=1) -reveal_type(sub_int) # N: Revealed type is "__main__.Sub[builtins.int]" -reveal_type(sub_int.attr) # N: Revealed type is "builtins.int" - -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] - - -[case testAttrsGenericInheritance2] -from typing import Generic, TypeVar -import attr - -T1 = TypeVar("T1") -T2 = TypeVar("T2") -T3 = TypeVar("T3") - -@attr.s(auto_attribs=True) -class Base(Generic[T1, T2, T3]): - one: T1 - two: T2 - three: T3 - -@attr.s(auto_attribs=True) -class Sub(Base[int, str, float]): - pass - -sub = Sub(one=1, two='ok', three=3.14) -reveal_type(sub) # N: Revealed type is "__main__.Sub" -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] - - -[case testAttrsGenericInheritance3] -import attr -from typing import Any, Callable, Generic, TypeVar, List - -T = TypeVar("T") -S = TypeVar("S") - -@attr.s(auto_attribs=True) -class Parent(Generic[T]): - f: Callable[[T], Any] - -@attr.s(auto_attribs=True) -class Child(Parent[T]): ... - -class A: ... -def func(obj: A) -> bool: ... - -reveal_type(Child[A](func).f) # N: Revealed type is "def (__main__.A) -> Any" - -@attr.s(auto_attribs=True) -class Parent2(Generic[T]): - a: List[T] - -@attr.s(auto_attribs=True) -class Child2(Generic[T, S], Parent2[S]): - b: List[T] - -reveal_type(Child2([A()], [1]).a) # N: Revealed type is "builtins.list[__main__.A]" -reveal_type(Child2[int, A]([A()], [1]).b) # N: Revealed type is "builtins.list[builtins.int]" -[builtins fixtures/list.pyi] - -[case testAttrsMultiGenericInheritance] -from typing import Generic, TypeVar -import attr - -T = TypeVar("T") - -@attr.s(auto_attribs=True, eq=False) -class Base(Generic[T]): - base_attr: T - -S = TypeVar("S") - -@attr.s(auto_attribs=True, eq=False) -class Middle(Base[int], Generic[S]): - middle_attr: S - -@attr.s(auto_attribs=True, eq=False) -class Sub(Middle[str]): - pass - -sub = Sub(base_attr=1, middle_attr='ok') -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] - - -[case testAttrsGenericClassmethod] -from typing import TypeVar, Generic, Optional -import attr -T = TypeVar('T') -@attr.s(auto_attribs=True) -class A(Generic[T]): - x: Optional[T] - @classmethod - def clsmeth(cls) -> None: - reveal_type(cls) # N: Revealed type is "type[__main__.A[T`1]]" - -[builtins fixtures/classmethod.pyi] - -[case testAttrsForwardReference] -# flags: --no-strict-optional -import attr -@attr.s(auto_attribs=True) -class A: - parent: 'B' - -@attr.s(auto_attribs=True) -class B: - parent: A - -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] - -[case testAttrsForwardReferenceInClass] -# flags: --no-strict-optional -import attr -@attr.s(auto_attribs=True) -class A: - parent: A.B - - @attr.s(auto_attribs=True) - class B: - parent: 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] - -[case testAttrsImporting] -from helper import A -reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> helper.A" -[file helper.py] -import attr -@attr.s(auto_attribs=True) -class A: - a: int - b: str = attr.ib() -[builtins fixtures/list.pyi] - -[case testAttrsOtherMethods] -import attr -@attr.s(auto_attribs=True) -class A: - a: int - b: str = attr.ib() - @classmethod - def new(cls) -> A: - reveal_type(cls) # N: Revealed type is "type[__main__.A]" - return cls(6, 'hello') - @classmethod - def bad(cls) -> A: - return cls(17) # E: Missing positional argument "b" in call to "A" - def foo(self) -> int: - return self.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] - -[case testAttrsOtherOverloads] -import attr -from typing import overload, Union - -@attr.s -class A: - a = attr.ib() - b = attr.ib(default=3) - - @classmethod - def other(cls) -> str: - return "..." - - @overload - @classmethod - def foo(cls, x: int) -> int: ... - - @overload - @classmethod - def foo(cls, x: str) -> str: ... - - @classmethod - def foo(cls, x: Union[int, str]) -> Union[int, str]: - reveal_type(cls) # N: Revealed type is "type[__main__.A]" - reveal_type(cls.other()) # N: Revealed type is "builtins.str" - return x - -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] - -[case testAttrsDefaultDecorator] -import attr -@attr.s -class C(object): - x: int = attr.ib(default=1) - y: int = attr.ib() - @y.default - def name_does_not_matter(self): - return self.x + 1 -C() -[builtins fixtures/list.pyi] - -[case testAttrsValidatorDecorator] -import attr -@attr.s -class C(object): - x = attr.ib() - @x.validator - def check(self, attribute, value): - if value > 42: - raise ValueError("x must be smaller or equal to 42") -C(42) -C(43) -[builtins fixtures/exception.pyi] - -[case testAttrsLocalVariablesInClassMethod] -import attr -@attr.s(auto_attribs=True) -class A: - a: int - b: int = attr.ib() - @classmethod - def new(cls, foo: int) -> A: - a = foo - b = a - return cls(a, b) -[builtins fixtures/classmethod.pyi] - -[case testAttrsUnionForward] -import attr -from typing import Union, List - -@attr.s(auto_attribs=True) -class A: - frob: List['AOrB'] - -class B: - pass - -AOrB = Union[A, B] - -reveal_type(A) # N: Revealed type is "def (frob: builtins.list[__main__.A | __main__.B]) -> __main__.A" -reveal_type(B) # N: Revealed type is "def () -> __main__.B" - -A([B()]) -[builtins fixtures/list.pyi] - -[case testAttrsUsingConvert] -import attr - -def convert(s:int) -> str: - return 'hello' - -@attr.s -class C: - x: str = attr.ib(convert=convert) # E: convert is deprecated, use converter - -# 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] - -[case testAttrsUsingConverter] -import attr -import helper - -def converter2(s:int) -> str: - return 'hello' - -@attr.s -class C: - x: str = attr.ib(converter=helper.converter) - y: str = attr.ib(converter=converter2) - -# Because of the converter the __init__ takes an int, but the variable is a str. -reveal_type(C) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> __main__.C" -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] - -[case testAttrsUsingConvertAndConverter] -import attr - -def converter(s:int) -> str: - return 'hello' - -@attr.s -class C: - x: str = attr.ib(converter=converter, convert=converter) # E: Can't pass both "convert" and "converter". - -[builtins fixtures/list.pyi] - -[case testAttrsUsingBadConverter] -# flags: --no-strict-optional -import attr -from typing import overload -@overload -def bad_overloaded_converter(x: int, y: int) -> int: - ... -@overload -def bad_overloaded_converter(x: str, y: str) -> str: - ... -def bad_overloaded_converter(x, y=7): - return x -def bad_converter() -> str: - return '' -@attr.dataclass -class A: - bad: str = attr.ib(converter=bad_converter) - bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) -reveal_type(A) -[out] -main:16: error: Cannot determine __init__ type from converter -main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], 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] - -[case testAttrsUsingBadConverterReprocess] -# flags: --no-strict-optional -import attr -from typing import overload -forward: 'A' -@overload -def bad_overloaded_converter(x: int, y: int) -> int: - ... -@overload -def bad_overloaded_converter(x: str, y: str) -> str: - ... -def bad_overloaded_converter(x, y=7): - return x -def bad_converter() -> str: - return '' -@attr.dataclass -class A: - bad: str = attr.ib(converter=bad_converter) - bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) -reveal_type(A) -[out] -main:17: error: Cannot determine __init__ type from converter -main:17: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], 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] - -[case testAttrsUsingUnsupportedConverter] -import attr -class Thing: - def do_it(self, int) -> str: - ... -thing = Thing() -def factory(default: int): - ... -@attr.s -class C: - x: str = attr.ib(converter=thing.do_it) # E: Unsupported converter, only named functions, types and lambdas are currently supported - y: str = attr.ib(converter=lambda x: x) - z: str = attr.ib(converter=factory(8)) # E: Unsupported converter, only named functions, types and lambdas are currently supported -reveal_type(C) # N: Revealed type is "def (x: Any, y: Any, z: Any) -> __main__.C" -[builtins fixtures/list.pyi] - -[case testAttrsUsingConverterAndSubclass] -import attr - -def converter(s:int) -> str: - return 'hello' - -@attr.s -class C: - x: str = attr.ib(converter=converter) - -@attr.s -class A(C): - pass - -# 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] - -[case testAttrsUsingConverterWithTypes] -from typing import overload -import attr - -@attr.dataclass -class A: - x: str - -@attr.s -class C: - x: complex = attr.ib(converter=complex) - y: int = attr.ib(converter=int) - z: A = attr.ib(converter=A) - -o = C("1", "2", "3") -o = C(1, 2, "3") -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsCmpWithSubclasses] -import attr -@attr.s -class A: pass -@attr.s -class B: pass -@attr.s -class C(A, B): pass -@attr.s -class D(A): pass - -reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool" -reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool" -reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`7, other: _AT`7) -> builtins.bool" -reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`8, other: _AT`8) -> builtins.bool" - -A() < A() -B() < B() -A() < B() # E: Unsupported operand types for < ("A" and "B") - -C() > A() -C() > B() -C() > C() -C() > D() # E: Unsupported operand types for > ("C" and "D") - -D() >= A() -D() >= B() # E: Unsupported operand types for >= ("D" and "B") -D() >= C() # E: Unsupported operand types for >= ("D" and "C") -D() >= D() - -A() <= 1 # E: Unsupported operand types for <= ("A" and "int") -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] - -[case testAttrsComplexSuperclass] -import attr -@attr.s -class C: - x: int = attr.ib(default=1) - y: int = attr.ib() - @y.default - def name_does_not_matter(self): - return self.x + 1 -@attr.s -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] - -[case testAttrsMultiAssign] -import attr -@attr.s -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] - -[case testAttrsMultiAssign2] -import attr -@attr.s -class A: - x = y = z = attr.ib() # E: Too many names for one attribute -[builtins fixtures/list.pyi] - -[case testAttrsPrivateInit] -import attr -@attr.s -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] - -[case testAttrsAliasForInit] -from attrs import define, field - -@define -class C1: - _x: int = field(alias="x1") - -c1 = C1(x1=42) -reveal_type(c1._x) # N: Revealed type is "builtins.int" -c1.x1 # E: "C1" has no attribute "x1" -C1(_x=42) # E: Unexpected keyword argument "_x" for "C1" - -alias = "x2" -@define -class C2: - _x: int = field(alias=alias) # E: "alias" argument to attrs field must be a string literal - -@define -class C3: - _x: int = field(alias="_x") - -c3 = C3(_x=1) -reveal_type(c3._x) # N: Revealed type is "builtins.int" -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsAutoMustBeAll] -import attr -@attr.s(auto_attribs=True) -class A: - a: int - b = 17 - # The following forms are not allowed with auto_attribs=True - 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] - -[case testAttrsRepeatedName] -import attr -@attr.s -class A: - a = attr.ib(default=8) - b = attr.ib() - a = attr.ib() -reveal_type(A) # N: Revealed type is "def (b: Any, a: Any) -> __main__.A" -@attr.s -class B: - a: int = attr.ib(default=8) - b: int = attr.ib() - a: int = attr.ib() # E: Name "a" already defined on line 10 -reveal_type(B) # N: Revealed type is "def (b: builtins.int, a: builtins.int) -> __main__.B" -@attr.s(auto_attribs=True) -class C: - a: int = 8 - 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] - -[case testAttrsFrozenSubclass] -import attr - -@attr.dataclass -class NonFrozenBase: - a: int - -@attr.dataclass(frozen=True) -class FrozenBase: - a: int - -@attr.dataclass(frozen=True) -class FrozenNonFrozen(NonFrozenBase): - b: int - -@attr.dataclass(frozen=True) -class FrozenFrozen(FrozenBase): - b: int - -@attr.dataclass -class NonFrozenFrozen(FrozenBase): - b: int - -# Make sure these are untouched -non_frozen_base = NonFrozenBase(1) -non_frozen_base.a = 17 -frozen_base = FrozenBase(1) -frozen_base.a = 17 # E: Property "a" defined in "FrozenBase" is read-only - -a = FrozenNonFrozen(1, 2) -a.a = 17 # E: Property "a" defined in "FrozenNonFrozen" is read-only -a.b = 17 # E: Property "b" defined in "FrozenNonFrozen" is read-only - -b = FrozenFrozen(1, 2) -b.a = 17 # E: Property "a" defined in "FrozenFrozen" is read-only -b.b = 17 # E: Property "b" defined in "FrozenFrozen" is read-only - -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/plugin_attrs.pyi] -[case testAttrsCallableAttributes] -from typing import Callable -import attr -def blah(a: int, b: int) -> bool: - return True - -@attr.s(auto_attribs=True) -class F: - _cb: Callable[[int, int], bool] = blah - def foo(self) -> bool: - return self._cb(5, 6) - -@attr.s -class G: - _cb: Callable[[int, int], bool] = attr.ib(blah) - def foo(self) -> bool: - return self._cb(5, 6) - -@attr.s(auto_attribs=True, frozen=True) -class FFrozen(F): - def bar(self) -> bool: - return self._cb(5, 6) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsWithFactory] -from typing import List -import attr -def my_factory() -> int: - return 7 -@attr.s -class A: - x: List[int] = attr.ib(factory=list) - y: int = attr.ib(factory=my_factory) -A() -[builtins fixtures/list.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] - -[case testAttrsFactoryBadReturn] -import attr -def my_factory() -> int: - return 7 -@attr.s -class A: - x: int = attr.ib(factory=list) # E: Incompatible types in assignment (expression has type "list[Never]", 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] - -[case testAttrsDefaultAndInit] -import attr - -@attr.s -class C: - a = attr.ib(init=False, default=42) - b = attr.ib() # Ok because previous attribute is init=False - c = attr.ib(default=44) - 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] - -[case testAttrsOptionalConverter] -import attr -from attr.converters import optional -from typing import Optional - -def converter(s:int) -> str: - return 'hello' - - -@attr.s -class A: - y: Optional[int] = attr.ib(converter=optional(int)) - z: Optional[str] = attr.ib(converter=optional(converter)) - - -A(None, None) - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsOptionalConverterNewPackage] -import attrs -from attrs.converters import optional -from typing import Optional - -def converter(s:int) -> str: - return 'hello' - - -@attrs.define -class A: - y: Optional[int] = attrs.field(converter=optional(int)) - z: Optional[str] = attrs.field(converter=optional(converter)) - - -A(None, None) - -[builtins fixtures/plugin_attrs.pyi] - - -[case testAttrsTypeVarNoCollision] -from typing import TypeVar, Generic -import attr - -T = TypeVar("T", bytes, str) - -# Make sure the generated __le__ (and friends) don't use T for their arguments. -@attr.s(auto_attribs=True) -class A(Generic[T]): - v: T -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsKwOnlyAttrib] -import attr -@attr.s -class A: - a = attr.ib(kw_only=True) -A() # E: Missing named argument "a" for "A" -A(15) # E: Too many positional arguments for "A" -A(a=15) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsKwOnlyClass] -import attr -@attr.s(kw_only=True, auto_attribs=True) -class A: - a: int - b: bool -A() # E: Missing named argument "a" for "A" # E: Missing named argument "b" for "A" -A(b=True, a=15) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsKwOnlyClassNoInit] -import attr -@attr.s(kw_only=True) -class B: - a = attr.ib(init=False) - b = attr.ib() -B(b=True) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsKwOnlyWithDefault] -import attr -@attr.s -class C: - a = attr.ib(0) - b = attr.ib(kw_only=True) - c = attr.ib(16, kw_only=True) -C(b=17) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsKwOnlyClassWithMixedDefaults] -import attr -@attr.s(kw_only=True) -class D: - a = attr.ib(10) - b = attr.ib() - c = attr.ib(15) -D(b=17) -[builtins fixtures/plugin_attrs.pyi] - - -[case testAttrsKwOnlyClassAttributeFalse] -from attrs import define, field -@define(kw_only=True) -class A: - a: int = field(kw_only=False) - b: int = 1 -A(0) -A(1, b=3) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsKwOnlyClassAttributeFalseSubclass] -from attrs import define, field -@define(kw_only=True) -class A: - a: int = field(kw_only=False) - b: int = 1 -@define(kw_only=True) -class B(A): - c: int = field(kw_only=False) - d: int = 3 - -B(0, 1) -B(0, 1, b=3, d=4) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsKwOnlyClassAttributeFalseSubclassOnly] -from attrs import define, field -@define -class A: - a: int - b: int = field(kw_only=True, default=1) -@define(kw_only=True) -class B(A): - c: int = field(kw_only=False) - d: int = 3 - -B(0, 1) -B(0, 1, b=3, d=4) -[builtins fixtures/plugin_attrs.pyi] - - -[case testAttrsKwOnlySubclass] -import attr -@attr.s -class A2: - a = attr.ib(default=0) -@attr.s -class B2(A2): - b = attr.ib(kw_only=True) -B2(b=1) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsNonKwOnlyAfterKwOnly] -import attr -@attr.s(kw_only=True) -class A: - a = attr.ib(default=0) -@attr.s -class B(A): - b = attr.ib() -@attr.s -class C: - a = attr.ib(kw_only=True) - b = attr.ib(15) - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsDisallowUntypedWorksForward] -# flags: --disallow-untyped-defs -import attr -from typing import List - -@attr.s -class B: - x: C = attr.ib() - -class C(List[C]): - pass - -reveal_type(B) # N: Revealed type is "def (x: __main__.C) -> __main__.B" -[builtins fixtures/list.pyi] - -[case testDisallowUntypedWorksForwardBad] -# flags: --disallow-untyped-defs -import attr - -@attr.s -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] - -[case testAttrsDefaultDecoratorDeferred] -defer: Yes - -import attr -@attr.s -class C(object): - x: int = attr.ib(default=1) - y: int = attr.ib() - @y.default - def inc(self): - return self.x + 1 - -class Yes: ... -[builtins fixtures/list.pyi] - -[case testAttrsValidatorDecoratorDeferred] -defer: Yes - -import attr -@attr.s -class C(object): - x = attr.ib() - @x.validator - def check(self, attribute, value): - if value > 42: - raise ValueError("x must be smaller or equal to 42") -C(42) -C(43) - -class Yes: ... -[builtins fixtures/exception.pyi] - -[case testTypeInAttrUndefined] -import attr - -@attr.s -class C: - total = attr.ib(type=Bad) # E: Name "Bad" is not defined -[builtins fixtures/bool.pyi] - -[case testTypeInAttrForwardInRuntime] -import attr - -@attr.s -class C: - total = attr.ib(type=Forward) - -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] - -[case testDefaultInAttrForward] -import attr - -@attr.s -class C: - total = attr.ib(default=func()) - -def func() -> int: ... - -C() -C(1) -C(1, 2) # E: Too many arguments for "C" -[builtins fixtures/bool.pyi] - -[case testTypeInAttrUndefinedFrozen] -import attr - -@attr.s(frozen=True) -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/plugin_attrs.pyi] - -[case testTypeInAttrDeferredStar] -import lib -[file lib.py] -import attr -MYPY = False -if MYPY: # Force deferral - from other import * - -@attr.s -class C: - total = attr.ib(type=int) - -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] - -[case testAttrsDefaultsMroOtherFile] -import a - -[file a.py] -import attr -from b import A1, A2 - -@attr.s -class Asdf(A1, A2): # E: Non-default attributes not allowed after default attributes. - pass - -[file b.py] -import attr - -@attr.s -class A1: - a: str = attr.ib('test') - -@attr.s -class A2: - b: int = attr.ib() - -[builtins fixtures/list.pyi] - -[case testAttrsInheritanceNoAnnotation] -import attr - -@attr.s -class A: - foo = attr.ib() # type: int - -x = 0 -@attr.s -class B(A): - foo = x - -reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" -[builtins fixtures/bool.pyi] - -[case testAttrsClassHasMagicAttribute] -import attr - -@attr.s -class A: - b: int = attr.ib() - c: str = attr.ib() - -reveal_type(A.__attrs_attrs__) # N: Revealed type is "tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" -reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtins.int]" -reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]" -A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsBareClassHasMagicAttribute] -import attr - -@attr.s -class A: - b = attr.ib() - c = attr.ib() - -reveal_type(A.__attrs_attrs__) # N: Revealed type is "tuple[attr.Attribute[Any], attr.Attribute[Any], fallback=__main__.A.____main___A_AttrsAttributes__]" -reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[Any]" -reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[Any]" -A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsNGClassHasMagicAttribute] -import attr - -@attr.define -class A: - b: int - c: str - -reveal_type(A.__attrs_attrs__) # N: Revealed type is "tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" -reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtins.int]" -reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]" -A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsMagicAttributeProtocol] -import attr -from typing import Any, Protocol, Type, ClassVar - -class AttrsInstance(Protocol): - __attrs_attrs__: ClassVar[Any] - -@attr.define -class A: - b: int - c: str - -def takes_attrs_cls(cls: Type[AttrsInstance]) -> None: - pass - -def takes_attrs_instance(inst: AttrsInstance) -> None: - pass - -takes_attrs_cls(A) -takes_attrs_instance(A(1, "")) - -takes_attrs_cls(A(1, "")) # E: Argument 1 to "takes_attrs_cls" has incompatible type "A"; expected "type[AttrsInstance]" -takes_attrs_instance(A) # E: Argument 1 to "takes_attrs_instance" has incompatible type "type[A]"; expected "AttrsInstance" # N: ClassVar protocol member AttrsInstance.__attrs_attrs__ can never be matched by a class object -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsFields] -import attr -from attrs import fields as f # Common usage. - -@attr.define -class A: - b: int - c: str - -reveal_type(f(A)) # N: Revealed type is "tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" -reveal_type(f(A)[0]) # N: Revealed type is "attr.Attribute[builtins.int]" -reveal_type(f(A).b) # N: Revealed type is "attr.Attribute[builtins.int]" -f(A).x # E: "____main___A_AttrsAttributes__" has no attribute "x" - -for ff in f(A): - reveal_type(ff) # N: Revealed type is "attr.Attribute[Any]" - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsGenericFields] -from typing import TypeVar - -import attr -from attrs import fields - -@attr.define -class A: - b: int - c: str - -TA = TypeVar('TA', bound=A) - -def f(t: TA) -> None: - reveal_type(fields(t)) # N: Revealed type is "tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" - reveal_type(fields(t)[0]) # N: Revealed type is "attr.Attribute[builtins.int]" - reveal_type(fields(t).b) # N: Revealed type is "attr.Attribute[builtins.int]" - fields(t).x # E: "____main___A_AttrsAttributes__" has no attribute "x" - - -[builtins fixtures/plugin_attrs.pyi] - -[case testNonattrsFields] -from typing import Any, cast, Type -from attrs import fields, has - -class A: - b: int - c: str - -if has(A): - fields(A) -else: - fields(A) # E: Argument 1 to "fields" has incompatible type "type[A]"; expected "type[AttrsInstance]" -fields(None) # E: Argument 1 to "fields" has incompatible type "None"; expected "type[AttrsInstance]" -fields(cast(Any, 42)) -fields(cast(Type[Any], 43)) - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsInitMethodAlwaysGenerates] -from typing import Tuple -import attr - -@attr.define(init=False) -class A: - b: int - c: str - def __init__(self, bc: Tuple[int, str]) -> None: - b, c = bc - self.__attrs_init__(b, c) - -reveal_type(A) # N: Revealed type is "def (bc: tuple[builtins.int, builtins.str]) -> __main__.A" -reveal_type(A.__init__) # N: Revealed type is "def (self: __main__.A, bc: tuple[builtins.int, builtins.str])" -reveal_type(A.__attrs_init__) # N: Revealed type is "def (self: __main__.A, b: builtins.int, c: builtins.str)" - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsClassWithSlots] -import attr - -@attr.define -class Define: - b: int = attr.ib() - - def __attrs_post_init__(self) -> None: - self.b = 1 - self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.Define" - - -@attr.define(slots=False) -class DefineSlotsFalse: - b: int = attr.ib() - - def __attrs_post_init__(self) -> None: - self.b = 1 - self.c = 2 - - -@attr.s(slots=True) -class A: - b: int = attr.ib() - - def __attrs_post_init__(self) -> None: - self.b = 1 - self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.A" - -@attr.dataclass(slots=True) -class B: - __slots__ = () # would be replaced - b: int - - def __attrs_post_init__(self) -> None: - self.b = 1 - self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.B" - -@attr.dataclass(slots=False) -class C: - __slots__ = () # would not be replaced - b: int - - def __attrs_post_init__(self) -> None: - self.b = 1 # E: Trying to assign name "b" that is not in "__slots__" of type "__main__.C" - self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.C" -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsClassWithSlotsDerivedFromNonSlots] -import attrs - -class A: - pass - -@attrs.define(slots=True) -class B(A): - x: int - - def __attrs_post_init__(self) -> None: - self.y = 42 - -[builtins fixtures/plugin_attrs.pyi] - -[case testRuntimeSlotsAttr] -from attr import dataclass - -@dataclass(slots=True) -class Some: - x: int - y: str - z: bool - -reveal_type(Some.__slots__) # N: Revealed type is "tuple[builtins.str, builtins.str, builtins.str]" - -@dataclass(slots=True) -class Other: - x: int - y: str - -reveal_type(Other.__slots__) # N: Revealed type is "tuple[builtins.str, builtins.str]" - - -@dataclass -class NoSlots: - x: int - y: str - -NoSlots.__slots__ # E: "type[NoSlots]" has no attribute "__slots__" -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsWithMatchArgs] -import attr - -@attr.s(match_args=True, auto_attribs=True) -class ToMatch: - x: int - y: int - # Not included: - z: int = attr.field(kw_only=True) - i: int = attr.field(init=False) - -reveal_type(ToMatch(x=1, y=2, z=3).__match_args__) # N: Revealed type is "tuple[Literal['x']?, Literal['y']?]" -reveal_type(ToMatch(1, 2, z=3).__match_args__) # N: Revealed type is "tuple[Literal['x']?, Literal['y']?]" -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsWithMatchArgsDefaultCase] -import attr - -@attr.s(auto_attribs=True) -class ToMatch1: - x: int - y: int - -t1: ToMatch1 -reveal_type(t1.__match_args__) # N: Revealed type is "tuple[Literal['x']?, Literal['y']?]" - -@attr.define -class ToMatch2: - x: int - y: int - -t2: ToMatch2 -reveal_type(t2.__match_args__) # N: Revealed type is "tuple[Literal['x']?, Literal['y']?]" -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsWithMatchArgsOverrideExisting] -import attr -from typing import Final - -@attr.s(match_args=True, auto_attribs=True) -class ToMatch: - __match_args__: Final = ('a', 'b') - x: int - y: int - -# It works the same way runtime does: -reveal_type(ToMatch(x=1, y=2).__match_args__) # N: Revealed type is "tuple[Literal['a']?, Literal['b']?]" - -@attr.s(auto_attribs=True) -class WithoutMatch: - __match_args__: Final = ('a', 'b') - x: int - y: int - -reveal_type(WithoutMatch(x=1, y=2).__match_args__) # N: Revealed type is "tuple[Literal['a']?, Literal['b']?]" -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsMultipleInheritance] -import attr - -@attr.s -class A: - x = attr.ib(type=int) - -@attr.s -class B: - y = attr.ib(type=int) - -class AB(A, B): - pass -[builtins fixtures/plugin_attrs.pyi] -[typing fixtures/typing-full.pyi] - -[case testAttrsForwardReferenceInTypeVarBound] -from typing import TypeVar, Generic -import attr - -T = TypeVar("T", bound="C") - -@attr.define -class D(Generic[T]): - x: int - -class C: - pass -[builtins fixtures/plugin_attrs.pyi] - -[case testComplexTypeInAttrIb] -import a - -[file a.py] -import attr -import b -from typing import Callable - -@attr.s -class C: - a = attr.ib(type=Lst[int]) # E: Name "Lst" is not defined - # Note that for this test, the 'Value of type "int" is not indexable' errors are silly, - # and a consequence of Callable etc. being set to an int in the test stub. - b = attr.ib(type=Callable[[], C]) # E: Value of type "int" is not indexable -[file b.py] -import attr -import a -from typing import List as Lst, Optional - -@attr.s -class D: - a = attr.ib(type=Lst[int]) - b = attr.ib(type=Optional[int]) # E: Value of type "int" is not indexable -[builtins fixtures/list.pyi] - -[case testAttrsGenericInheritanceSpecialCase1] -import attr -from typing import Generic, TypeVar, List - -T = TypeVar("T") - -@attr.define -class Parent(Generic[T]): - x: List[T] - -@attr.define -class Child1(Parent["Child2"]): ... - -@attr.define -class Child2(Parent["Child1"]): ... - -def f(c: Child2) -> None: - reveal_type(Child1([c]).x) # N: Revealed type is "builtins.list[__main__.Child2]" - -def g(c: Child1) -> None: - reveal_type(Child2([c]).x) # N: Revealed type is "builtins.list[__main__.Child1]" -[builtins fixtures/list.pyi] - -[case testAttrsGenericInheritanceSpecialCase2] -import attr -from typing import Generic, TypeVar - -T = TypeVar("T") - -# A subclass might be analyzed before base in import cycles. They are -# defined here in reversed order to simulate this. - -@attr.define -class Child1(Parent["Child2"]): - x: int - -@attr.define -class Child2(Parent["Child1"]): - y: int - -@attr.define -class Parent(Generic[T]): - key: str - -Child1(x=1, key='') -Child2(y=1, key='') -[builtins fixtures/list.pyi] - -[case testAttrsUnsupportedConverterWithDisallowUntypedDefs] -# flags: --disallow-untyped-defs -import attr -from typing import Mapping, Any, Union - -def default_if_none(factory: Any) -> Any: pass - -@attr.s(slots=True, frozen=True) -class C: - name: Union[str, None] = attr.ib(default=None) - options: Mapping[str, Mapping[str, Any]] = attr.ib( - default=None, converter=default_if_none(factory=dict) \ - # E: Unsupported converter, only named functions, types and lambdas are currently supported - ) -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsUnannotatedConverter] -import attr - -def foo(value): - return value.split() - -@attr.s -class Bar: - field = attr.ib(default=None, converter=foo) - -reveal_type(Bar) # N: Revealed type is "def (field: Any =) -> __main__.Bar" -bar = Bar("Hello") -reveal_type(bar.field) # N: Revealed type is "Any" - -[builtins fixtures/tuple.pyi] - -[case testAttrsLambdaConverter] -import attr - -@attr.s -class Bar: - name: str = attr.ib(converter=lambda s: s.lower()) - -reveal_type(Bar) # N: Revealed type is "def (name: Any) -> __main__.Bar" -bar = Bar("Hello") -reveal_type(bar.name) # N: Revealed type is "builtins.str" - -[builtins fixtures/tuple.pyi] - -[case testAttrsNestedClass] -from typing import List -import attr - -@attr.s -class C: - @attr.s - class D: - pass - x = attr.ib(type=List[D]) - -c = C(x=[C.D()]) -reveal_type(c.x) # N: Revealed type is "builtins.list[__main__.C.D]" -[builtins fixtures/list.pyi] - -[case testRedefinitionInFrozenClassNoCrash] -import attr - -@attr.s -class MyData: - is_foo: bool = attr.ib() - - @staticmethod # E: Name "is_foo" already defined on line 5 - def is_foo(string: str) -> bool: ... -[builtins fixtures/classmethod.pyi] - -[case testOverrideWithPropertyInFrozenClassNoCrash] -from attrs import frozen - -@frozen(kw_only=True) -class Base: - name: str - -@frozen(kw_only=True) -class Sub(Base): - first_name: str - last_name: str - - @property - def name(self) -> str: ... -[builtins fixtures/plugin_attrs.pyi] - -[case testOverrideWithPropertyInFrozenClassChecked] -from attrs import frozen - -@frozen(kw_only=True) -class Base: - name: str - -@frozen(kw_only=True) -class Sub(Base): - first_name: str - last_name: str - - @property - def name(self) -> int: ... # E: Signature of "name" incompatible with supertype "Base" \ - # N: Superclass: \ - # N: str \ - # N: Subclass: \ - # N: int - -# This matches runtime semantics -reveal_type(Sub) # N: Revealed type is "def (*, name: builtins.str, first_name: builtins.str, last_name: builtins.str) -> __main__.Sub" -[builtins fixtures/plugin_attrs.pyi] - -[case testFinalInstanceAttribute] -from attrs import define -from typing import Final - -@define -class C: - a: Final[int] - -reveal_type(C) # N: Revealed type is "def (a: builtins.int) -> __main__.C" - -C(1).a = 2 # E: Cannot assign to final attribute "a" - -[builtins fixtures/property.pyi] - -[case testFinalInstanceAttributeInheritance] -from attrs import define -from typing import Final - -@define -class C: - a: Final[int] - -@define -class D(C): - b: Final[str] - -reveal_type(D) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> __main__.D" - -D(1, "").a = 2 # E: Cannot assign to final attribute "a" -D(1, "").b = "2" # E: Cannot assign to final attribute "b" - -[builtins fixtures/property.pyi] - -[case testEvolve] -import attr - -class Base: - pass - -class Derived(Base): - pass - -class Other: - pass - -@attr.s(auto_attribs=True) -class C: - name: str - b: Base - -c = C(name='foo', b=Derived()) -c = attr.evolve(c) -c = attr.evolve(c, name='foo') -c = attr.evolve(c, 'foo') # E: Too many positional arguments for "evolve" of "C" -c = attr.evolve(c, b=Derived()) -c = attr.evolve(c, b=Base()) -c = attr.evolve(c, b=Other()) # E: Argument "b" to "evolve" of "C" has incompatible type "Other"; expected "Base" -c = attr.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str" -c = attr.evolve(c, foobar=42) # E: Unexpected keyword argument "foobar" for "evolve" of "C" - -# test passing instance as 'inst' kw -c = attr.evolve(inst=c, name='foo') -c = attr.evolve(not_inst=c, name='foo') # E: Missing positional argument "inst" in call to "evolve" - -# test determining type of first argument's expression from something that's not NameExpr -def f() -> C: - return c - -c = attr.evolve(f(), name='foo') - -[builtins fixtures/plugin_attrs.pyi] - -[case testEvolveFromNonAttrs] -import attr - -attr.evolve(42, name='foo') # E: Argument 1 to "evolve" has incompatible type "int"; expected an attrs class -attr.evolve(None, name='foo') # E: Argument 1 to "evolve" has incompatible type "None"; expected an attrs class -[case testEvolveFromAny] -from typing import Any -import attr - -any: Any = 42 -ret = attr.evolve(any, name='foo') -reveal_type(ret) # N: Revealed type is "Any" - -[typing fixtures/typing-medium.pyi] - -[case testEvolveGeneric] -import attrs -from typing import Generic, TypeVar - -T = TypeVar('T') - -@attrs.define -class A(Generic[T]): - x: T - - -a = A(x=42) -reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]" -a2 = attrs.evolve(a, x=42) -reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" -a2 = attrs.evolve(a, x='42') # E: Argument "x" to "evolve" of "A[int]" has incompatible type "str"; expected "int" -reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" - -[builtins fixtures/plugin_attrs.pyi] - -[case testEvolveUnion] -from typing import Generic, TypeVar -import attrs - -T = TypeVar('T') - - -@attrs.define -class A(Generic[T]): - x: T # exercises meet(T=int, int) = int - y: bool # exercises meet(bool, int) = bool - z: str # exercises meet(str, bytes) = Never - w: dict # exercises meet(dict, Never) = Never - - -@attrs.define -class B: - x: int - y: bool - z: bytes - - -a_or_b: A[int] | B -a2 = attrs.evolve(a_or_b, x=42, y=True) -a2 = attrs.evolve(a_or_b, x=42, y=True, z='42') # E: Argument "z" to "evolve" of "A[int] | B" has incompatible type "str"; expected "Never" -a2 = attrs.evolve(a_or_b, x=42, y=True, w={}) # E: Argument "w" to "evolve" of "A[int] | B" has incompatible type "dict[Never, Never]"; expected "Never" - -[builtins fixtures/plugin_attrs.pyi] - -[case testEvolveUnionOfTypeVar] -import attrs -from typing import TypeVar - -@attrs.define -class A: - x: int - y: int - z: str - w: dict - - -class B: - pass - -TA = TypeVar('TA', bound=A) -TB = TypeVar('TB', bound=B) - -def f(b_or_t: TA | TB | int) -> None: - a2 = attrs.evolve(b_or_t) # E: Argument 1 to "evolve" has type "TA | TB | int" whose item "TB" is not bound to an attrs class \ - # E: Argument 1 to "evolve" has incompatible type "TA | TB | int" whose item "int" is not an attrs class - - -[builtins fixtures/plugin_attrs.pyi] - -[case testEvolveTypeVarBound] -import attrs -from typing import TypeVar - -@attrs.define -class A: - x: int - -@attrs.define -class B(A): - pass - -TA = TypeVar('TA', bound=A) - -def f(t: TA) -> TA: - t2 = attrs.evolve(t, x=42) - reveal_type(t2) # N: Revealed type is "TA`-1" - t3 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "TA" has incompatible type "str"; expected "int" - return t2 - -f(A(x=42)) -f(B(x=42)) - -[builtins fixtures/plugin_attrs.pyi] - -[case testEvolveTypeVarBoundNonAttrs] -import attrs -from typing import Union, TypeVar - -TInt = TypeVar('TInt', bound=int) -TAny = TypeVar('TAny') -TNone = TypeVar('TNone', bound=None) -TUnion = TypeVar('TUnion', bound=Union[str, int]) - -def f(t: TInt) -> None: - _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TInt" not bound to an attrs class - -def g(t: TAny) -> None: - _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TAny" not bound to an attrs class - -def h(t: TNone) -> None: - _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TNone" not bound to an attrs class - -def x(t: TUnion) -> None: - _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has incompatible type "TUnion" whose item "str" is not an attrs class \ - # E: Argument 1 to "evolve" has incompatible type "TUnion" whose item "int" is not an attrs class - -[builtins fixtures/plugin_attrs.pyi] - -[case testEvolveTypeVarConstrained] +[case testAttrsSimple_no_empty] +import attr +@attr.s +class A: + a = attr.ib() + _b = attr.ib() + c = attr.ib(18) + _d = attr.ib(validator=None, default=18) + E = 18 + + def foo(self): + return self.a +reveal_type(A) # N: Revealed type is "def (a: Any, b: Any, c: Any =, d: Any =) -> __main__.A" +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] + +[case testAttrsAnnotated] +import attr +from typing import List, ClassVar +@attr.s +class A: + a: int = attr.ib() + _b: List[int] = attr.ib() + c: str = attr.ib('18') + _d: int = attr.ib(validator=None, default=18) + E = 7 + F: ClassVar[int] = 22 +reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> __main__.A" +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] + +[case testAttrsTypeComments] +import attr +from typing import List, ClassVar +@attr.s +class A: + a = attr.ib() # type: int + _b = attr.ib() # type: List[int] + c = attr.ib('18') # type: str + _d = attr.ib(validator=None, default=18) # type: int + E = 7 + F: ClassVar[int] = 22 +reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> __main__.A" +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] + +[case testAttrsAutoAttribs] +import attr +from typing import List, ClassVar +@attr.s(auto_attribs=True) +class A: + a: int + _b: List[int] + c: str = '18' + _d: int = attr.ib(validator=None, default=18) + E = 7 + F: ClassVar[int] = 22 +reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> __main__.A" +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] + +[case testAttrsUntypedNoUntypedDefs] +# flags: --disallow-untyped-defs +import attr +@attr.s +class A: + a = attr.ib() # E: Need type annotation for "a" + _b = attr.ib() # E: Need type annotation for "_b" + 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] + +[case testAttrsWrongReturnValue] +import attr +@attr.s +class A: + x: int = attr.ib(8) + def foo(self) -> str: + return self.x # E: Incompatible return value type (got "int", expected "str") +@attr.s +class B: + x = attr.ib(8) # type: int + def foo(self) -> str: + return self.x # E: Incompatible return value type (got "int", expected "str") +@attr.dataclass +class C: + x: int = 8 + def foo(self) -> str: + return self.x # E: Incompatible return value type (got "int", expected "str") +@attr.s +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] + +[case testAttrsSeriousNames] +from attr import attrib, attrs +from typing import List +@attrs(init=True) +class A: + a = attrib() + _b: List[int] = attrib() + c = attrib(18) + _d = attrib(validator=None, default=18) + CLASS_VAR = 18 +reveal_type(A) # N: Revealed type is "def (a: Any, b: builtins.list[builtins.int], c: Any =, d: Any =) -> __main__.A" +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] + +[case testAttrsDefaultErrors] +import attr +@attr.s +class A: + x = attr.ib(default=17) + y = attr.ib() # E: Non-default attributes not allowed after default attributes. +@attr.s(auto_attribs=True) +class B: + x: int = 17 + y: int # E: Non-default attributes not allowed after default attributes. +@attr.s(auto_attribs=True) +class C: + x: int = attr.ib(default=17) + y: int # E: Non-default attributes not allowed after default attributes. +@attr.s +class D: + x = attr.ib() + y = attr.ib() # E: Non-default attributes not allowed after default attributes. + + @x.default + def foo(self): + return 17 +[builtins fixtures/bool.pyi] + +[case testAttrsNotBooleans] +import attr +x = True +@attr.s(cmp=x) # E: "cmp" argument must be a True, False, or None literal +class A: + a = attr.ib(init=x) # E: "init" argument must be a True or False literal +[builtins fixtures/bool.pyi] + +[case testAttrsInitFalse] +from attr import attrib, attrs +@attrs(auto_attribs=True, init=False) +class A: + a: int + _b: int + c: int = 18 + _d: int = attrib(validator=None, default=18) +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] + +[case testAttrsInitAttribFalse] +from attr import attrib, attrs +@attrs +class A: + a = attrib(init=False) + b = attrib() +reveal_type(A) # N: Revealed type is "def (b: Any) -> __main__.A" +[builtins fixtures/bool.pyi] + +[case testAttrsCmpTrue] +from attr import attrib, attrs +@attrs(auto_attribs=True) +class A: + a: int +reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" +reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`3, other: _AT`3) -> builtins.bool" +reveal_type(A.__le__) # N: Revealed type is "def [_AT] (self: _AT`4, other: _AT`4) -> builtins.bool" +reveal_type(A.__gt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool" +reveal_type(A.__ge__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool" + +A(1) < A(2) +A(1) <= A(2) +A(1) > A(2) +A(1) >= A(2) +A(1) == A(2) +A(1) != A(2) + +A(1) < 1 # E: Unsupported operand types for < ("A" and "int") +A(1) <= 1 # E: Unsupported operand types for <= ("A" and "int") +A(1) > 1 # E: Unsupported operand types for > ("A" and "int") +A(1) >= 1 # E: Unsupported operand types for >= ("A" and "int") +A(1) == 1 +A(1) != 1 + +1 < A(1) # E: Unsupported operand types for > ("A" and "int") +1 <= A(1) # E: Unsupported operand types for >= ("A" and "int") +1 > A(1) # E: Unsupported operand types for < ("A" and "int") +1 >= A(1) # E: Unsupported operand types for <= ("A" and "int") +1 == A(1) +1 != A(1) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsEqFalse] +from attr import attrib, attrs +@attrs(auto_attribs=True, eq=False) +class A: + a: int +reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" +reveal_type(A.__eq__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" +reveal_type(A.__ne__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" + +A(1) < A(2) # E: Unsupported left operand type for < ("A") +A(1) <= A(2) # E: Unsupported left operand type for <= ("A") +A(1) > A(2) # E: Unsupported left operand type for > ("A") +A(1) >= A(2) # E: Unsupported left operand type for >= ("A") +A(1) == A(2) +A(1) != A(2) + +A(1) < 1 # E: Unsupported left operand type for < ("A") +A(1) <= 1 # E: Unsupported left operand type for <= ("A") +A(1) > 1 # E: Unsupported left operand type for > ("A") +A(1) >= 1 # E: Unsupported left operand type for >= ("A") +A(1) == 1 +A(1) != 1 + +1 < A(1) # E: Unsupported left operand type for < ("int") +1 <= A(1) # E: Unsupported left operand type for <= ("int") +1 > A(1) # E: Unsupported left operand type for > ("int") +1 >= A(1) # E: Unsupported left operand type for >= ("int") +1 == A(1) +1 != A(1) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsOrderFalse] +from attr import attrib, attrs +@attrs(auto_attribs=True, order=False) +class A: + a: int +reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> __main__.A" + +A(1) < A(2) # E: Unsupported left operand type for < ("A") +A(1) <= A(2) # E: Unsupported left operand type for <= ("A") +A(1) > A(2) # E: Unsupported left operand type for > ("A") +A(1) >= A(2) # E: Unsupported left operand type for >= ("A") +A(1) == A(2) +A(1) != A(2) + +A(1) < 1 # E: Unsupported left operand type for < ("A") +A(1) <= 1 # E: Unsupported left operand type for <= ("A") +A(1) > 1 # E: Unsupported left operand type for > ("A") +A(1) >= 1 # E: Unsupported left operand type for >= ("A") +A(1) == 1 +A(1) != 1 + +1 < A(1) # E: Unsupported left operand type for < ("int") +1 <= A(1) # E: Unsupported left operand type for <= ("int") +1 > A(1) # E: Unsupported left operand type for > ("int") +1 >= A(1) # E: Unsupported left operand type for >= ("int") +1 == A(1) +1 != A(1) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsCmpEqOrderValues] +from attr import attrib, attrs +@attrs(cmp=True) +class DeprecatedTrue: + ... + +@attrs(cmp=False) +class DeprecatedFalse: + ... + +@attrs(cmp=False, eq=True) # E: Don't mix "cmp" with "eq" and "order" +class Mixed: + ... + +@attrs(order=True, eq=False) # E: eq must be True if order is True +class Confused: + ... +[builtins fixtures/plugin_attrs.pyi] + + +[case testAttrsInheritance] +import attr +@attr.s +class A: + a: int = attr.ib() +@attr.s +class B: + b: str = attr.ib() +@attr.s +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] + +[case testAttrsNestedInClasses] +import attr +@attr.s +class C: + y = attr.ib() + @attr.s + class D: + 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] + +[case testAttrsInheritanceOverride] +import attr + +@attr.s +class A: + a: int = attr.ib() + x: int = attr.ib() + +@attr.s +class B(A): + b: str = attr.ib() + x: int = attr.ib(default=22) + +@attr.s +class C(B): + c: bool = attr.ib() # No error here because the x below overwrites the x above. + x: int = attr.ib() + +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] + +[case testAttrsTypeEquals] +import attr + +@attr.s +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] + +[case testAttrsFrozen] +import attr + +@attr.s(frozen=True) +class A: + a = attr.ib() + +a = A(5) +a.a = 16 # E: Property "a" defined in "A" is read-only +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsNextGenFrozen] +from attr import frozen, field + +@frozen +class A: + a = field() + +a = A(5) +a.a = 16 # E: Property "a" defined in "A" is read-only +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsNextGenDetect] +from attr import define, field + +@define +class A: + a = field() + +@define +class B: + a: int + +@define +class C: + a: int = field() + b = field() + +@define +class D: + a: int + b = field() + +reveal_type(A) # N: Revealed type is "def (a: Any) -> __main__.A" +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] + +[case testAttrsOldPackage] +import attr +@attr.s(auto_attribs=True) +class A: + a: int = attr.ib() + b: bool + +@attr.s(auto_attribs=True, frozen=True) +class B: + a: bool + b: int + +@attr.s +class C: + a = attr.ib(type=int) + +reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.bool) -> __main__.A" +reveal_type(B) # N: Revealed type is "def (a: builtins.bool, b: builtins.int) -> __main__.B" +reveal_type(C) # N: Revealed type is "def (a: builtins.int) -> __main__.C" + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsDataClass] +import attr +from typing import List, ClassVar +@attr.dataclass +class A: + a: int + _b: List[str] + c: str = '18' + _d: int = attr.ib(validator=None, default=18) + E = 7 + 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] + +[case testAttrsTypeAlias] +from typing import List +import attr +Alias = List[int] +@attr.s(auto_attribs=True) +class A: + Alias2 = List[str] + 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] + +[case testAttrsGeneric] +from typing import TypeVar, Generic, List +import attr +T = TypeVar('T') +@attr.s(auto_attribs=True) +class A(Generic[T]): + x: List[T] + y: T = attr.ib() + def foo(self) -> List[T]: + return [self.y] + def bar(self) -> T: + return self.x[0] + def problem(self) -> T: + return self.x # E: Incompatible return value type (got "list[T]", expected "T") +reveal_type(A) # N: Revealed type is "def [T] (x: builtins.list[T`1], y: T`1) -> __main__.A[T`1]" +a = A([1], 2) +reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]" +reveal_type(a.x) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(a.y) # N: Revealed type is "builtins.int" + +A(['str'], 7) # E: Cannot infer value of type parameter "T" of "A" +A([1], '2') # E: Cannot infer value of type parameter "T" of "A" + +[builtins fixtures/list.pyi] + +[case testAttrsGenericWithConverter] +from typing import TypeVar, Generic, List, Iterable, Iterator, Callable +import attr +T = TypeVar('T') + +def int_gen() -> Iterator[int]: + yield 1 + +def list_converter(x: Iterable[T]) -> List[T]: + return list(x) + +@attr.s(auto_attribs=True) +class A(Generic[T]): + x: List[T] = attr.ib(converter=list_converter) + y: T = attr.ib() + def foo(self) -> List[T]: + return [self.y] + def bar(self) -> T: + return self.x[0] + def problem(self) -> T: + return self.x # E: Incompatible return value type (got "list[T]", expected "T") +reveal_type(A) # N: Revealed type is "def [T] (x: typing.Iterable[T`1], y: T`1) -> __main__.A[T`1]" +a1 = A([1], 2) +reveal_type(a1) # N: Revealed type is "__main__.A[builtins.int]" +reveal_type(a1.x) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(a1.y) # N: Revealed type is "builtins.int" + +a2 = A(int_gen(), 2) +reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" +reveal_type(a2.x) # N: Revealed type is "builtins.list[builtins.int]" +reveal_type(a2.y) # N: Revealed type is "builtins.int" + + +def get_int() -> int: + return 1 + +class Other(Generic[T]): + def __init__(self, x: T) -> None: + pass + +@attr.s(auto_attribs=True) +class B(Generic[T]): + x: Other[Callable[..., T]] = attr.ib(converter=Other[Callable[..., T]]) + +b1 = B(get_int) +reveal_type(b1) # N: Revealed type is "__main__.B[builtins.int]" +reveal_type(b1.x) # N: Revealed type is "__main__.Other[def (*Any, **Any) -> builtins.int]" + +[builtins fixtures/list.pyi] + + +[case testAttrsUntypedGenericInheritance] +from typing import Generic, TypeVar +import attr + +T = TypeVar("T") + +@attr.s(auto_attribs=True) +class Base(Generic[T]): + attr: T + +@attr.s(auto_attribs=True) +class Sub(Base): + pass + +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] + + +[case testAttrsGenericInheritance] +from typing import Generic, TypeVar +import attr + +S = TypeVar("S") +T = TypeVar("T") + +@attr.s(auto_attribs=True) +class Base(Generic[T]): + attr: T + +@attr.s(auto_attribs=True) +class Sub(Base[S]): + pass + +sub_int = Sub[int](attr=1) +reveal_type(sub_int) # N: Revealed type is "__main__.Sub[builtins.int]" +reveal_type(sub_int.attr) # N: Revealed type is "builtins.int" + +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] + + +[case testAttrsGenericInheritance2] +from typing import Generic, TypeVar +import attr + +T1 = TypeVar("T1") +T2 = TypeVar("T2") +T3 = TypeVar("T3") + +@attr.s(auto_attribs=True) +class Base(Generic[T1, T2, T3]): + one: T1 + two: T2 + three: T3 + +@attr.s(auto_attribs=True) +class Sub(Base[int, str, float]): + pass + +sub = Sub(one=1, two='ok', three=3.14) +reveal_type(sub) # N: Revealed type is "__main__.Sub" +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] + + +[case testAttrsGenericInheritance3] +import attr +from typing import Any, Callable, Generic, TypeVar, List + +T = TypeVar("T") +S = TypeVar("S") + +@attr.s(auto_attribs=True) +class Parent(Generic[T]): + f: Callable[[T], Any] + +@attr.s(auto_attribs=True) +class Child(Parent[T]): ... + +class A: ... +def func(obj: A) -> bool: ... + +reveal_type(Child[A](func).f) # N: Revealed type is "def (__main__.A) -> Any" + +@attr.s(auto_attribs=True) +class Parent2(Generic[T]): + a: List[T] + +@attr.s(auto_attribs=True) +class Child2(Generic[T, S], Parent2[S]): + b: List[T] + +reveal_type(Child2([A()], [1]).a) # N: Revealed type is "builtins.list[__main__.A]" +reveal_type(Child2[int, A]([A()], [1]).b) # N: Revealed type is "builtins.list[builtins.int]" +[builtins fixtures/list.pyi] + +[case testAttrsMultiGenericInheritance] +from typing import Generic, TypeVar +import attr + +T = TypeVar("T") + +@attr.s(auto_attribs=True, eq=False) +class Base(Generic[T]): + base_attr: T + +S = TypeVar("S") + +@attr.s(auto_attribs=True, eq=False) +class Middle(Base[int], Generic[S]): + middle_attr: S + +@attr.s(auto_attribs=True, eq=False) +class Sub(Middle[str]): + pass + +sub = Sub(base_attr=1, middle_attr='ok') +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] + + +[case testAttrsGenericClassmethod] +from typing import TypeVar, Generic, Optional +import attr +T = TypeVar('T') +@attr.s(auto_attribs=True) +class A(Generic[T]): + x: Optional[T] + @classmethod + def clsmeth(cls) -> None: + reveal_type(cls) # N: Revealed type is "type[__main__.A[T`1]]" + +[builtins fixtures/classmethod.pyi] + +[case testAttrsForwardReference] +# flags: --no-strict-optional +import attr +@attr.s(auto_attribs=True) +class A: + parent: 'B' + +@attr.s(auto_attribs=True) +class B: + parent: A + +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] + +[case testAttrsForwardReferenceInClass] +# flags: --no-strict-optional +import attr +@attr.s(auto_attribs=True) +class A: + parent: A.B + + @attr.s(auto_attribs=True) + class B: + parent: 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] + +[case testAttrsImporting] +from helper import A +reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> helper.A" +[file helper.py] +import attr +@attr.s(auto_attribs=True) +class A: + a: int + b: str = attr.ib() +[builtins fixtures/list.pyi] + +[case testAttrsOtherMethods] +import attr +@attr.s(auto_attribs=True) +class A: + a: int + b: str = attr.ib() + @classmethod + def new(cls) -> A: + reveal_type(cls) # N: Revealed type is "type[__main__.A]" + return cls(6, 'hello') + @classmethod + def bad(cls) -> A: + return cls(17) # E: Missing positional argument "b" in call to "A" + def foo(self) -> int: + return self.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] + +[case testAttrsOtherOverloads] +import attr +from typing import overload, Union + +@attr.s +class A: + a = attr.ib() + b = attr.ib(default=3) + + @classmethod + def other(cls) -> str: + return "..." + + @overload + @classmethod + def foo(cls, x: int) -> int: ... + + @overload + @classmethod + def foo(cls, x: str) -> str: ... + + @classmethod + def foo(cls, x: Union[int, str]) -> Union[int, str]: + reveal_type(cls) # N: Revealed type is "type[__main__.A]" + reveal_type(cls.other()) # N: Revealed type is "builtins.str" + return x + +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] + +[case testAttrsDefaultDecorator] +import attr +@attr.s +class C(object): + x: int = attr.ib(default=1) + y: int = attr.ib() + @y.default + def name_does_not_matter(self): + return self.x + 1 +C() +[builtins fixtures/list.pyi] + +[case testAttrsValidatorDecorator] +import attr +@attr.s +class C(object): + x = attr.ib() + @x.validator + def check(self, attribute, value): + if value > 42: + raise ValueError("x must be smaller or equal to 42") +C(42) +C(43) +[builtins fixtures/exception.pyi] + +[case testAttrsLocalVariablesInClassMethod] +import attr +@attr.s(auto_attribs=True) +class A: + a: int + b: int = attr.ib() + @classmethod + def new(cls, foo: int) -> A: + a = foo + b = a + return cls(a, b) +[builtins fixtures/classmethod.pyi] + +[case testAttrsUnionForward] +import attr +from typing import Union, List + +@attr.s(auto_attribs=True) +class A: + frob: List['AOrB'] + +class B: + pass + +AOrB = Union[A, B] + +reveal_type(A) # N: Revealed type is "def (frob: builtins.list[__main__.A | __main__.B]) -> __main__.A" +reveal_type(B) # N: Revealed type is "def () -> __main__.B" + +A([B()]) +[builtins fixtures/list.pyi] + +[case testAttrsUsingConvert] +import attr + +def convert(s:int) -> str: + return 'hello' + +@attr.s +class C: + x: str = attr.ib(convert=convert) # E: convert is deprecated, use converter + +# 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] + +[case testAttrsUsingConverter] +import attr +import helper + +def converter2(s:int) -> str: + return 'hello' + +@attr.s +class C: + x: str = attr.ib(converter=helper.converter) + y: str = attr.ib(converter=converter2) + +# Because of the converter the __init__ takes an int, but the variable is a str. +reveal_type(C) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> __main__.C" +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] + +[case testAttrsUsingConvertAndConverter] +import attr + +def converter(s:int) -> str: + return 'hello' + +@attr.s +class C: + x: str = attr.ib(converter=converter, convert=converter) # E: Can't pass both "convert" and "converter". + +[builtins fixtures/list.pyi] + +[case testAttrsUsingBadConverter] +# flags: --no-strict-optional +import attr +from typing import overload +@overload +def bad_overloaded_converter(x: int, y: int) -> int: + ... +@overload +def bad_overloaded_converter(x: str, y: str) -> str: + ... +def bad_overloaded_converter(x, y=7): + return x +def bad_converter() -> str: + return '' +@attr.dataclass +class A: + bad: str = attr.ib(converter=bad_converter) + bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) +reveal_type(A) +[out] +main:16: error: Cannot determine __init__ type from converter +main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], 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] + +[case testAttrsUsingBadConverterReprocess] +# flags: --no-strict-optional +import attr +from typing import overload +forward: 'A' +@overload +def bad_overloaded_converter(x: int, y: int) -> int: + ... +@overload +def bad_overloaded_converter(x: str, y: str) -> str: + ... +def bad_overloaded_converter(x, y=7): + return x +def bad_converter() -> str: + return '' +@attr.dataclass +class A: + bad: str = attr.ib(converter=bad_converter) + bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) +reveal_type(A) +[out] +main:17: error: Cannot determine __init__ type from converter +main:17: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], 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] + +[case testAttrsUsingUnsupportedConverter] +import attr +class Thing: + def do_it(self, int) -> str: + ... +thing = Thing() +def factory(default: int): + ... +@attr.s +class C: + x: str = attr.ib(converter=thing.do_it) # E: Unsupported converter, only named functions, types and lambdas are currently supported + y: str = attr.ib(converter=lambda x: x) + z: str = attr.ib(converter=factory(8)) # E: Unsupported converter, only named functions, types and lambdas are currently supported +reveal_type(C) # N: Revealed type is "def (x: Any, y: Any, z: Any) -> __main__.C" +[builtins fixtures/list.pyi] + +[case testAttrsUsingConverterAndSubclass] +import attr + +def converter(s:int) -> str: + return 'hello' + +@attr.s +class C: + x: str = attr.ib(converter=converter) + +@attr.s +class A(C): + pass + +# 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] + +[case testAttrsUsingConverterWithTypes] +from typing import overload +import attr + +@attr.dataclass +class A: + x: str + +@attr.s +class C: + x: complex = attr.ib(converter=complex) + y: int = attr.ib(converter=int) + z: A = attr.ib(converter=A) + +o = C("1", "2", "3") +o = C(1, 2, "3") +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsCmpWithSubclasses] +import attr +@attr.s +class A: pass +@attr.s +class B: pass +@attr.s +class C(A, B): pass +@attr.s +class D(A): pass + +reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`5, other: _AT`5) -> builtins.bool" +reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`6, other: _AT`6) -> builtins.bool" +reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`7, other: _AT`7) -> builtins.bool" +reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`8, other: _AT`8) -> builtins.bool" + +A() < A() +B() < B() +A() < B() # E: Unsupported operand types for < ("A" and "B") + +C() > A() +C() > B() +C() > C() +C() > D() # E: Unsupported operand types for > ("C" and "D") + +D() >= A() +D() >= B() # E: Unsupported operand types for >= ("D" and "B") +D() >= C() # E: Unsupported operand types for >= ("D" and "C") +D() >= D() + +A() <= 1 # E: Unsupported operand types for <= ("A" and "int") +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] + +[case testAttrsComplexSuperclass] +import attr +@attr.s +class C: + x: int = attr.ib(default=1) + y: int = attr.ib() + @y.default + def name_does_not_matter(self): + return self.x + 1 +@attr.s +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] + +[case testAttrsMultiAssign] +import attr +@attr.s +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] + +[case testAttrsMultiAssign2] +import attr +@attr.s +class A: + x = y = z = attr.ib() # E: Too many names for one attribute +[builtins fixtures/list.pyi] + +[case testAttrsPrivateInit] +import attr +@attr.s +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] + +[case testAttrsAliasForInit] +from attrs import define, field + +@define +class C1: + _x: int = field(alias="x1") + +c1 = C1(x1=42) +reveal_type(c1._x) # N: Revealed type is "builtins.int" +c1.x1 # E: "C1" has no attribute "x1" +C1(_x=42) # E: Unexpected keyword argument "_x" for "C1" + +alias = "x2" +@define +class C2: + _x: int = field(alias=alias) # E: "alias" argument to attrs field must be a string literal + +@define +class C3: + _x: int = field(alias="_x") + +c3 = C3(_x=1) +reveal_type(c3._x) # N: Revealed type is "builtins.int" +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsAutoMustBeAll] +import attr +@attr.s(auto_attribs=True) +class A: + a: int + b = 17 + # The following forms are not allowed with auto_attribs=True + 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] + +[case testAttrsRepeatedName] +import attr +@attr.s +class A: + a = attr.ib(default=8) + b = attr.ib() + a = attr.ib() +reveal_type(A) # N: Revealed type is "def (b: Any, a: Any) -> __main__.A" +@attr.s +class B: + a: int = attr.ib(default=8) + b: int = attr.ib() + a: int = attr.ib() # E: Name "a" already defined on line 10 +reveal_type(B) # N: Revealed type is "def (b: builtins.int, a: builtins.int) -> __main__.B" +@attr.s(auto_attribs=True) +class C: + a: int = 8 + 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] + +[case testAttrsFrozenSubclass] +import attr + +@attr.dataclass +class NonFrozenBase: + a: int + +@attr.dataclass(frozen=True) +class FrozenBase: + a: int + +@attr.dataclass(frozen=True) +class FrozenNonFrozen(NonFrozenBase): + b: int + +@attr.dataclass(frozen=True) +class FrozenFrozen(FrozenBase): + b: int + +@attr.dataclass +class NonFrozenFrozen(FrozenBase): + b: int + +# Make sure these are untouched +non_frozen_base = NonFrozenBase(1) +non_frozen_base.a = 17 +frozen_base = FrozenBase(1) +frozen_base.a = 17 # E: Property "a" defined in "FrozenBase" is read-only + +a = FrozenNonFrozen(1, 2) +a.a = 17 # E: Property "a" defined in "FrozenNonFrozen" is read-only +a.b = 17 # E: Property "b" defined in "FrozenNonFrozen" is read-only + +b = FrozenFrozen(1, 2) +b.a = 17 # E: Property "a" defined in "FrozenFrozen" is read-only +b.b = 17 # E: Property "b" defined in "FrozenFrozen" is read-only + +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/plugin_attrs.pyi] +[case testAttrsCallableAttributes] +from typing import Callable +import attr +def blah(a: int, b: int) -> bool: + return True + +@attr.s(auto_attribs=True) +class F: + _cb: Callable[[int, int], bool] = blah + def foo(self) -> bool: + return self._cb(5, 6) + +@attr.s +class G: + _cb: Callable[[int, int], bool] = attr.ib(blah) + def foo(self) -> bool: + return self._cb(5, 6) + +@attr.s(auto_attribs=True, frozen=True) +class FFrozen(F): + def bar(self) -> bool: + return self._cb(5, 6) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsWithFactory] +from typing import List +import attr +def my_factory() -> int: + return 7 +@attr.s +class A: + x: List[int] = attr.ib(factory=list) + y: int = attr.ib(factory=my_factory) +A() +[builtins fixtures/list.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] + +[case testAttrsFactoryBadReturn] +import attr +def my_factory() -> int: + return 7 +@attr.s +class A: + x: int = attr.ib(factory=list) # E: Incompatible types in assignment (expression has type "list[Never]", 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] + +[case testAttrsDefaultAndInit] +import attr + +@attr.s +class C: + a = attr.ib(init=False, default=42) + b = attr.ib() # Ok because previous attribute is init=False + c = attr.ib(default=44) + 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] + +[case testAttrsOptionalConverter] +import attr +from attr.converters import optional +from typing import Optional + +def converter(s:int) -> str: + return 'hello' + + +@attr.s +class A: + y: Optional[int] = attr.ib(converter=optional(int)) + z: Optional[str] = attr.ib(converter=optional(converter)) + + +A(None, None) + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsOptionalConverterNewPackage] +import attrs +from attrs.converters import optional +from typing import Optional + +def converter(s:int) -> str: + return 'hello' + + +@attrs.define +class A: + y: Optional[int] = attrs.field(converter=optional(int)) + z: Optional[str] = attrs.field(converter=optional(converter)) + + +A(None, None) + +[builtins fixtures/plugin_attrs.pyi] + + +[case testAttrsTypeVarNoCollision] +from typing import TypeVar, Generic +import attr + +T = TypeVar("T", bytes, str) + +# Make sure the generated __le__ (and friends) don't use T for their arguments. +@attr.s(auto_attribs=True) +class A(Generic[T]): + v: T +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsKwOnlyAttrib] +import attr +@attr.s +class A: + a = attr.ib(kw_only=True) +A() # E: Missing named argument "a" for "A" +A(15) # E: Too many positional arguments for "A" +A(a=15) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsKwOnlyClass] +import attr +@attr.s(kw_only=True, auto_attribs=True) +class A: + a: int + b: bool +A() # E: Missing named argument "a" for "A" # E: Missing named argument "b" for "A" +A(b=True, a=15) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsKwOnlyClassNoInit] +import attr +@attr.s(kw_only=True) +class B: + a = attr.ib(init=False) + b = attr.ib() +B(b=True) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsKwOnlyWithDefault] +import attr +@attr.s +class C: + a = attr.ib(0) + b = attr.ib(kw_only=True) + c = attr.ib(16, kw_only=True) +C(b=17) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsKwOnlyClassWithMixedDefaults] +import attr +@attr.s(kw_only=True) +class D: + a = attr.ib(10) + b = attr.ib() + c = attr.ib(15) +D(b=17) +[builtins fixtures/plugin_attrs.pyi] + + +[case testAttrsKwOnlyClassAttributeFalse] +from attrs import define, field +@define(kw_only=True) +class A: + a: int = field(kw_only=False) + b: int = 1 +A(0) +A(1, b=3) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsKwOnlyClassAttributeFalseSubclass] +from attrs import define, field +@define(kw_only=True) +class A: + a: int = field(kw_only=False) + b: int = 1 +@define(kw_only=True) +class B(A): + c: int = field(kw_only=False) + d: int = 3 + +B(0, 1) +B(0, 1, b=3, d=4) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsKwOnlyClassAttributeFalseSubclassOnly] +from attrs import define, field +@define +class A: + a: int + b: int = field(kw_only=True, default=1) +@define(kw_only=True) +class B(A): + c: int = field(kw_only=False) + d: int = 3 + +B(0, 1) +B(0, 1, b=3, d=4) +[builtins fixtures/plugin_attrs.pyi] + + +[case testAttrsKwOnlySubclass] +import attr +@attr.s +class A2: + a = attr.ib(default=0) +@attr.s +class B2(A2): + b = attr.ib(kw_only=True) +B2(b=1) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsNonKwOnlyAfterKwOnly] +import attr +@attr.s(kw_only=True) +class A: + a = attr.ib(default=0) +@attr.s +class B(A): + b = attr.ib() +@attr.s +class C: + a = attr.ib(kw_only=True) + b = attr.ib(15) + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsDisallowUntypedWorksForward] +# flags: --disallow-untyped-defs +import attr +from typing import List + +@attr.s +class B: + x: C = attr.ib() + +class C(List[C]): + pass + +reveal_type(B) # N: Revealed type is "def (x: __main__.C) -> __main__.B" +[builtins fixtures/list.pyi] + +[case testDisallowUntypedWorksForwardBad] +# flags: --disallow-untyped-defs +import attr + +@attr.s +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] + +[case testAttrsDefaultDecoratorDeferred] +defer: Yes + +import attr +@attr.s +class C(object): + x: int = attr.ib(default=1) + y: int = attr.ib() + @y.default + def inc(self): + return self.x + 1 + +class Yes: ... +[builtins fixtures/list.pyi] + +[case testAttrsValidatorDecoratorDeferred] +defer: Yes + +import attr +@attr.s +class C(object): + x = attr.ib() + @x.validator + def check(self, attribute, value): + if value > 42: + raise ValueError("x must be smaller or equal to 42") +C(42) +C(43) + +class Yes: ... +[builtins fixtures/exception.pyi] + +[case testTypeInAttrUndefined] +import attr + +@attr.s +class C: + total = attr.ib(type=Bad) # E: Name "Bad" is not defined +[builtins fixtures/bool.pyi] + +[case testTypeInAttrForwardInRuntime] +import attr + +@attr.s +class C: + total = attr.ib(type=Forward) + +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] + +[case testDefaultInAttrForward] +import attr + +@attr.s +class C: + total = attr.ib(default=func()) + +def func() -> int: ... + +C() +C(1) +C(1, 2) # E: Too many arguments for "C" +[builtins fixtures/bool.pyi] + +[case testTypeInAttrUndefinedFrozen] +import attr + +@attr.s(frozen=True) +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/plugin_attrs.pyi] + +[case testTypeInAttrDeferredStar] +import lib +[file lib.py] +import attr +MYPY = False +if MYPY: # Force deferral + from other import * + +@attr.s +class C: + total = attr.ib(type=int) + +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] + +[case testAttrsDefaultsMroOtherFile] +import a + +[file a.py] +import attr +from b import A1, A2 + +@attr.s +class Asdf(A1, A2): # E: Non-default attributes not allowed after default attributes. + pass + +[file b.py] +import attr + +@attr.s +class A1: + a: str = attr.ib('test') + +@attr.s +class A2: + b: int = attr.ib() + +[builtins fixtures/list.pyi] + +[case testAttrsInheritanceNoAnnotation] +import attr + +@attr.s +class A: + foo = attr.ib() # type: int + +x = 0 +@attr.s +class B(A): + foo = x + +reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" +[builtins fixtures/bool.pyi] + +[case testAttrsClassHasMagicAttribute] +import attr + +@attr.s +class A: + b: int = attr.ib() + c: str = attr.ib() + +reveal_type(A.__attrs_attrs__) # N: Revealed type is "tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" +reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtins.int]" +reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]" +A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsBareClassHasMagicAttribute] +import attr + +@attr.s +class A: + b = attr.ib() + c = attr.ib() + +reveal_type(A.__attrs_attrs__) # N: Revealed type is "tuple[attr.Attribute[Any], attr.Attribute[Any], fallback=__main__.A.____main___A_AttrsAttributes__]" +reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[Any]" +reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[Any]" +A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsNGClassHasMagicAttribute] +import attr + +@attr.define +class A: + b: int + c: str + +reveal_type(A.__attrs_attrs__) # N: Revealed type is "tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" +reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtins.int]" +reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]" +A.__attrs_attrs__.x # E: "____main___A_AttrsAttributes__" has no attribute "x" + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsMagicAttributeProtocol] +import attr +from typing import Any, Protocol, Type, ClassVar + +class AttrsInstance(Protocol): + __attrs_attrs__: ClassVar[Any] + +@attr.define +class A: + b: int + c: str + +def takes_attrs_cls(cls: Type[AttrsInstance]) -> None: + pass + +def takes_attrs_instance(inst: AttrsInstance) -> None: + pass + +takes_attrs_cls(A) +takes_attrs_instance(A(1, "")) + +takes_attrs_cls(A(1, "")) # E: Argument 1 to "takes_attrs_cls" has incompatible type "A"; expected "type[AttrsInstance]" +takes_attrs_instance(A) # E: Argument 1 to "takes_attrs_instance" has incompatible type "type[A]"; expected "AttrsInstance" # N: ClassVar protocol member AttrsInstance.__attrs_attrs__ can never be matched by a class object +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsFields] +import attr +from attrs import fields as f # Common usage. + +@attr.define +class A: + b: int + c: str + +reveal_type(f(A)) # N: Revealed type is "tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" +reveal_type(f(A)[0]) # N: Revealed type is "attr.Attribute[builtins.int]" +reveal_type(f(A).b) # N: Revealed type is "attr.Attribute[builtins.int]" +f(A).x # E: "____main___A_AttrsAttributes__" has no attribute "x" + +for ff in f(A): + reveal_type(ff) # N: Revealed type is "attr.Attribute[Any]" + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsGenericFields] +from typing import TypeVar + +import attr +from attrs import fields + +@attr.define +class A: + b: int + c: str + +TA = TypeVar('TA', bound=A) + +def f(t: TA) -> None: + reveal_type(fields(t)) # N: Revealed type is "tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]" + reveal_type(fields(t)[0]) # N: Revealed type is "attr.Attribute[builtins.int]" + reveal_type(fields(t).b) # N: Revealed type is "attr.Attribute[builtins.int]" + fields(t).x # E: "____main___A_AttrsAttributes__" has no attribute "x" + + +[builtins fixtures/plugin_attrs.pyi] + +[case testNonattrsFields] +from typing import Any, cast, Type +from attrs import fields, has + +class A: + b: int + c: str + +if has(A): + fields(A) +else: + fields(A) # E: Argument 1 to "fields" has incompatible type "type[A]"; expected "type[AttrsInstance]" +fields(None) # E: Argument 1 to "fields" has incompatible type "None"; expected "type[AttrsInstance]" +fields(cast(Any, 42)) +fields(cast(Type[Any], 43)) + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsInitMethodAlwaysGenerates] +from typing import Tuple +import attr + +@attr.define(init=False) +class A: + b: int + c: str + def __init__(self, bc: Tuple[int, str]) -> None: + b, c = bc + self.__attrs_init__(b, c) + +reveal_type(A) # N: Revealed type is "def (bc: tuple[builtins.int, builtins.str]) -> __main__.A" +reveal_type(A.__init__) # N: Revealed type is "def (self: __main__.A, bc: tuple[builtins.int, builtins.str])" +reveal_type(A.__attrs_init__) # N: Revealed type is "def (self: __main__.A, b: builtins.int, c: builtins.str)" + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsClassWithSlots] +import attr + +@attr.define +class Define: + b: int = attr.ib() + + def __attrs_post_init__(self) -> None: + self.b = 1 + self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.Define" + + +@attr.define(slots=False) +class DefineSlotsFalse: + b: int = attr.ib() + + def __attrs_post_init__(self) -> None: + self.b = 1 + self.c = 2 + + +@attr.s(slots=True) +class A: + b: int = attr.ib() + + def __attrs_post_init__(self) -> None: + self.b = 1 + self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.A" + +@attr.dataclass(slots=True) +class B: + __slots__ = () # would be replaced + b: int + + def __attrs_post_init__(self) -> None: + self.b = 1 + self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.B" + +@attr.dataclass(slots=False) +class C: + __slots__ = () # would not be replaced + b: int + + def __attrs_post_init__(self) -> None: + self.b = 1 # E: Trying to assign name "b" that is not in "__slots__" of type "__main__.C" + self.c = 2 # E: Trying to assign name "c" that is not in "__slots__" of type "__main__.C" +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsClassWithSlotsDerivedFromNonSlots] +import attrs + +class A: + pass + +@attrs.define(slots=True) +class B(A): + x: int + + def __attrs_post_init__(self) -> None: + self.y = 42 + +[builtins fixtures/plugin_attrs.pyi] + +[case testRuntimeSlotsAttr] +from attr import dataclass + +@dataclass(slots=True) +class Some: + x: int + y: str + z: bool + +reveal_type(Some.__slots__) # N: Revealed type is "tuple[builtins.str, builtins.str, builtins.str]" + +@dataclass(slots=True) +class Other: + x: int + y: str + +reveal_type(Other.__slots__) # N: Revealed type is "tuple[builtins.str, builtins.str]" + + +@dataclass +class NoSlots: + x: int + y: str + +NoSlots.__slots__ # E: "type[NoSlots]" has no attribute "__slots__" +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsWithMatchArgs] +import attr + +@attr.s(match_args=True, auto_attribs=True) +class ToMatch: + x: int + y: int + # Not included: + z: int = attr.field(kw_only=True) + i: int = attr.field(init=False) + +reveal_type(ToMatch(x=1, y=2, z=3).__match_args__) # N: Revealed type is "tuple[Literal['x']?, Literal['y']?]" +reveal_type(ToMatch(1, 2, z=3).__match_args__) # N: Revealed type is "tuple[Literal['x']?, Literal['y']?]" +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsWithMatchArgsDefaultCase] +import attr + +@attr.s(auto_attribs=True) +class ToMatch1: + x: int + y: int + +t1: ToMatch1 +reveal_type(t1.__match_args__) # N: Revealed type is "tuple[Literal['x']?, Literal['y']?]" + +@attr.define +class ToMatch2: + x: int + y: int + +t2: ToMatch2 +reveal_type(t2.__match_args__) # N: Revealed type is "tuple[Literal['x']?, Literal['y']?]" +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsWithMatchArgsOverrideExisting] +import attr +from typing import Final + +@attr.s(match_args=True, auto_attribs=True) +class ToMatch: + __match_args__: Final = ('a', 'b') + x: int + y: int + +# It works the same way runtime does: +reveal_type(ToMatch(x=1, y=2).__match_args__) # N: Revealed type is "tuple[Literal['a']?, Literal['b']?]" + +@attr.s(auto_attribs=True) +class WithoutMatch: + __match_args__: Final = ('a', 'b') + x: int + y: int + +reveal_type(WithoutMatch(x=1, y=2).__match_args__) # N: Revealed type is "tuple[Literal['a']?, Literal['b']?]" +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsMultipleInheritance] +import attr + +@attr.s +class A: + x = attr.ib(type=int) + +@attr.s +class B: + y = attr.ib(type=int) + +class AB(A, B): + pass +[builtins fixtures/plugin_attrs.pyi] +[typing fixtures/typing-full.pyi] + +[case testAttrsForwardReferenceInTypeVarBound] +from typing import TypeVar, Generic +import attr + +T = TypeVar("T", bound="C") + +@attr.define +class D(Generic[T]): + x: int + +class C: + pass +[builtins fixtures/plugin_attrs.pyi] + +[case testComplexTypeInAttrIb] +import a + +[file a.py] +import attr +import b +from typing import Callable + +@attr.s +class C: + a = attr.ib(type=Lst[int]) # E: Name "Lst" is not defined + # Note that for this test, the 'Value of type "int" is not indexable' errors are silly, + # and a consequence of Callable etc. being set to an int in the test stub. + b = attr.ib(type=Callable[[], C]) # E: Value of type "int" is not indexable +[file b.py] +import attr +import a +from typing import List as Lst, Optional + +@attr.s +class D: + a = attr.ib(type=Lst[int]) + b = attr.ib(type=Optional[int]) # E: Value of type "int" is not indexable +[builtins fixtures/list.pyi] + +[case testAttrsGenericInheritanceSpecialCase1] +import attr +from typing import Generic, TypeVar, List + +T = TypeVar("T") + +@attr.define +class Parent(Generic[T]): + x: List[T] + +@attr.define +class Child1(Parent["Child2"]): ... + +@attr.define +class Child2(Parent["Child1"]): ... + +def f(c: Child2) -> None: + reveal_type(Child1([c]).x) # N: Revealed type is "builtins.list[__main__.Child2]" + +def g(c: Child1) -> None: + reveal_type(Child2([c]).x) # N: Revealed type is "builtins.list[__main__.Child1]" +[builtins fixtures/list.pyi] + +[case testAttrsGenericInheritanceSpecialCase2] +import attr +from typing import Generic, TypeVar + +T = TypeVar("T") + +# A subclass might be analyzed before base in import cycles. They are +# defined here in reversed order to simulate this. + +@attr.define +class Child1(Parent["Child2"]): + x: int + +@attr.define +class Child2(Parent["Child1"]): + y: int + +@attr.define +class Parent(Generic[T]): + key: str + +Child1(x=1, key='') +Child2(y=1, key='') +[builtins fixtures/list.pyi] + +[case testAttrsUnsupportedConverterWithDisallowUntypedDefs] +# flags: --disallow-untyped-defs +import attr +from typing import Mapping, Any, Union + +def default_if_none(factory: Any) -> Any: pass + +@attr.s(slots=True, frozen=True) +class C: + name: Union[str, None] = attr.ib(default=None) + options: Mapping[str, Mapping[str, Any]] = attr.ib( + default=None, converter=default_if_none(factory=dict) \ + # E: Unsupported converter, only named functions, types and lambdas are currently supported + ) +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsUnannotatedConverter] +import attr + +def foo(value): + return value.split() + +@attr.s +class Bar: + field = attr.ib(default=None, converter=foo) + +reveal_type(Bar) # N: Revealed type is "def (field: Any =) -> __main__.Bar" +bar = Bar("Hello") +reveal_type(bar.field) # N: Revealed type is "Any" + +[builtins fixtures/tuple.pyi] + +[case testAttrsLambdaConverter] +import attr + +@attr.s +class Bar: + name: str = attr.ib(converter=lambda s: s.lower()) + +reveal_type(Bar) # N: Revealed type is "def (name: Any) -> __main__.Bar" +bar = Bar("Hello") +reveal_type(bar.name) # N: Revealed type is "builtins.str" + +[builtins fixtures/tuple.pyi] + +[case testAttrsNestedClass] +from typing import List +import attr + +@attr.s +class C: + @attr.s + class D: + pass + x = attr.ib(type=List[D]) + +c = C(x=[C.D()]) +reveal_type(c.x) # N: Revealed type is "builtins.list[__main__.C.D]" +[builtins fixtures/list.pyi] + +[case testRedefinitionInFrozenClassNoCrash] +import attr + +@attr.s +class MyData: + is_foo: bool = attr.ib() + + @staticmethod # E: Name "is_foo" already defined on line 5 + def is_foo(string: str) -> bool: ... +[builtins fixtures/classmethod.pyi] + +[case testOverrideWithPropertyInFrozenClassNoCrash] +from attrs import frozen + +@frozen(kw_only=True) +class Base: + name: str + +@frozen(kw_only=True) +class Sub(Base): + first_name: str + last_name: str + + @property + def name(self) -> str: ... +[builtins fixtures/plugin_attrs.pyi] + +[case testOverrideWithPropertyInFrozenClassChecked] +from attrs import frozen + +@frozen(kw_only=True) +class Base: + name: str + +@frozen(kw_only=True) +class Sub(Base): + first_name: str + last_name: str + + @property + def name(self) -> int: ... # E: Signature of "name" incompatible with supertype "Base" \ + # N: Superclass: \ + # N: str \ + # N: Subclass: \ + # N: int + +# This matches runtime semantics +reveal_type(Sub) # N: Revealed type is "def (*, name: builtins.str, first_name: builtins.str, last_name: builtins.str) -> __main__.Sub" +[builtins fixtures/plugin_attrs.pyi] + +[case testFinalInstanceAttribute] +from attrs import define +from typing import Final + +@define +class C: + a: Final[int] + +reveal_type(C) # N: Revealed type is "def (a: builtins.int) -> __main__.C" + +C(1).a = 2 # E: Cannot assign to final attribute "a" + +[builtins fixtures/property.pyi] + +[case testFinalInstanceAttributeInheritance] +from attrs import define +from typing import Final + +@define +class C: + a: Final[int] + +@define +class D(C): + b: Final[str] + +reveal_type(D) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> __main__.D" + +D(1, "").a = 2 # E: Cannot assign to final attribute "a" +D(1, "").b = "2" # E: Cannot assign to final attribute "b" + +[builtins fixtures/property.pyi] + +[case testEvolve] +import attr + +class Base: + pass + +class Derived(Base): + pass + +class Other: + pass + +@attr.s(auto_attribs=True) +class C: + name: str + b: Base + +c = C(name='foo', b=Derived()) +c = attr.evolve(c) +c = attr.evolve(c, name='foo') +c = attr.evolve(c, 'foo') # E: Too many positional arguments for "evolve" of "C" +c = attr.evolve(c, b=Derived()) +c = attr.evolve(c, b=Base()) +c = attr.evolve(c, b=Other()) # E: Argument "b" to "evolve" of "C" has incompatible type "Other"; expected "Base" +c = attr.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str" +c = attr.evolve(c, foobar=42) # E: Unexpected keyword argument "foobar" for "evolve" of "C" + +# test passing instance as 'inst' kw +c = attr.evolve(inst=c, name='foo') +c = attr.evolve(not_inst=c, name='foo') # E: Missing positional argument "inst" in call to "evolve" + +# test determining type of first argument's expression from something that's not NameExpr +def f() -> C: + return c + +c = attr.evolve(f(), name='foo') + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveFromNonAttrs] +import attr + +attr.evolve(42, name='foo') # E: Argument 1 to "evolve" has incompatible type "int"; expected an attrs class +attr.evolve(None, name='foo') # E: Argument 1 to "evolve" has incompatible type "None"; expected an attrs class +[case testEvolveFromAny] +from typing import Any +import attr + +any: Any = 42 +ret = attr.evolve(any, name='foo') +reveal_type(ret) # N: Revealed type is "Any" + +[typing fixtures/typing-medium.pyi] + +[case testEvolveGeneric] +import attrs +from typing import Generic, TypeVar + +T = TypeVar('T') + +@attrs.define +class A(Generic[T]): + x: T + + +a = A(x=42) +reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]" +a2 = attrs.evolve(a, x=42) +reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" +a2 = attrs.evolve(a, x='42') # E: Argument "x" to "evolve" of "A[int]" has incompatible type "str"; expected "int" +reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveUnion] +from typing import Generic, TypeVar +import attrs + +T = TypeVar('T') + + +@attrs.define +class A(Generic[T]): + x: T # exercises meet(T=int, int) = int + y: bool # exercises meet(bool, int) = bool + z: str # exercises meet(str, bytes) = Never + w: dict # exercises meet(dict, Never) = Never + + +@attrs.define +class B: + x: int + y: bool + z: bytes + + +a_or_b: A[int] | B +a2 = attrs.evolve(a_or_b, x=42, y=True) +a2 = attrs.evolve(a_or_b, x=42, y=True, z='42') # E: Argument "z" to "evolve" of "A[int] | B" has incompatible type "str"; expected "Never" +a2 = attrs.evolve(a_or_b, x=42, y=True, w={}) # E: Argument "w" to "evolve" of "A[int] | B" has incompatible type "dict[Never, Never]"; expected "Never" + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveUnionOfTypeVar] +import attrs +from typing import TypeVar + +@attrs.define +class A: + x: int + y: int + z: str + w: dict + + +class B: + pass + +TA = TypeVar('TA', bound=A) +TB = TypeVar('TB', bound=B) + +def f(b_or_t: TA | TB | int) -> None: + a2 = attrs.evolve(b_or_t) # E: Argument 1 to "evolve" has type "TA | TB | int" whose item "TB" is not bound to an attrs class \ + # E: Argument 1 to "evolve" has incompatible type "TA | TB | int" whose item "int" is not an attrs class + + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveTypeVarBound] +import attrs +from typing import TypeVar + +@attrs.define +class A: + x: int + +@attrs.define +class B(A): + pass + +TA = TypeVar('TA', bound=A) + +def f(t: TA) -> TA: + t2 = attrs.evolve(t, x=42) + reveal_type(t2) # N: Revealed type is "TA`-1" + t3 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "TA" has incompatible type "str"; expected "int" + return t2 + +f(A(x=42)) +f(B(x=42)) + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveTypeVarBoundNonAttrs] +import attrs +from typing import Union, TypeVar + +TInt = TypeVar('TInt', bound=int) +TAny = TypeVar('TAny') +TNone = TypeVar('TNone', bound=None) +TUnion = TypeVar('TUnion', bound=Union[str, int]) + +def f(t: TInt) -> None: + _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TInt" not bound to an attrs class + +def g(t: TAny) -> None: + _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TAny" not bound to an attrs class + +def h(t: TNone) -> None: + _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TNone" not bound to an attrs class + +def x(t: TUnion) -> None: + _ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has incompatible type "TUnion" whose item "str" is not an attrs class \ + # E: Argument 1 to "evolve" has incompatible type "TUnion" whose item "int" is not an attrs class + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveTypeVarConstrained] +import attrs +from typing import TypeVar + +@attrs.define +class A: + x: int + +@attrs.define +class B: + x: str # conflicting with A.x + +T = TypeVar('T', A, B) + +def f(t: T) -> T: + t2 = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "B" has incompatible type "int"; expected "str" + reveal_type(t2) # N: Revealed type is "__main__.A" # N: Revealed type is "__main__.B" + t2 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "A" has incompatible type "str"; expected "int" + return t2 + +f(A(x=42)) +f(B(x='42')) + +[builtins fixtures/plugin_attrs.pyi] + +[case testEvolveVariants] +from typing import Any +import attr +import attrs + + +@attr.s(auto_attribs=True) +class C: + name: str + +c = C(name='foo') + +c = attr.assoc(c, name='test') +c = attr.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str" + +c = attrs.evolve(c, name='test') +c = attrs.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str" + +c = attrs.assoc(c, name='test') +c = attrs.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str" + +[builtins fixtures/plugin_attrs.pyi] +[typing fixtures/typing-medium.pyi] + +[case testFrozenInheritFromGeneric] +from typing import Generic, TypeVar +from attrs import field, frozen + +T = TypeVar('T') + +def f(s: str) -> int: + ... + +@frozen +class A(Generic[T]): + x: T + y: int = field(converter=f) + +@frozen +class B(A[int]): + pass + +b = B(42, 'spam') +reveal_type(b.x) # N: Revealed type is "builtins.int" +reveal_type(b.y) # N: Revealed type is "builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testDefaultHashability] +from attrs import define + +@define +class A: + a: int + +reveal_type(A.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testFrozenHashability] +from attrs import frozen + +@frozen +class A: + a: int + +reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testManualHashHashability] +from attrs import define + +@define(hash=True) +class A: + a: int + +reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testManualUnsafeHashHashability] +from attrs import define + +@define(unsafe_hash=True) +class A: + a: int + +reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassingHashability] +from attrs import define + +@define(unsafe_hash=True) +class A: + a: int + +@define +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testManualOwnHashability] +from attrs import define, frozen + +@define +class A: + a: int + def __hash__(self) -> int: + ... + +reveal_type(A.__hash__) # N: Revealed type is "def (self: __main__.A) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassDefaultLosesHashability] +from attrs import define, frozen + +@define +class A: + a: int + def __hash__(self) -> int: + ... + +@define +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassEqFalseKeepsHashability] +from attrs import define, frozen + +@define +class A: + a: int + def __hash__(self) -> int: + ... + +@define(eq=False) +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "def (self: __main__.A) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassingFrozenHashability] +from attrs import define, frozen + +@define +class A: + a: int + +@frozen +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +[builtins fixtures/plugin_attrs.pyi] + +[case testSubclassingFrozenHashOffHashability] +from attrs import define, frozen + +@define +class A: + a: int + def __hash__(self) -> int: + ... + +@frozen(unsafe_hash=False) +class B(A): + pass + +reveal_type(B.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testUnsafeHashPrecedence] +from attrs import define, frozen + +@define(unsafe_hash=True, hash=False) +class A: + pass +reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" + +@define(unsafe_hash=False, hash=True) +class B: + pass +reveal_type(B.__hash__) # N: Revealed type is "None" + +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsStrictOptionalSetProperly] +from typing import Generic, Optional, TypeVar + +import attr + +T = TypeVar("T") + +@attr.mutable() +class Parent(Generic[T]): + run_type: Optional[int] = None + +@attr.mutable() +class Child(Parent[float]): + pass + +Parent(run_type = None) +c = Child(run_type = None) +reveal_type(c.run_type) # N: Revealed type is "builtins.int | None" +[builtins fixtures/plugin_attrs.pyi] + +[case testAttrsStackedDecoratorsWithAnnotatedOrderMethod] import attrs -from typing import TypeVar @attrs.define -class A: - x: int - -@attrs.define -class B: - x: str # conflicting with A.x - -T = TypeVar('T', A, B) - -def f(t: T) -> T: - t2 = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "B" has incompatible type "int"; expected "str" - reveal_type(t2) # N: Revealed type is "__main__.A" # N: Revealed type is "__main__.B" - t2 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "A" has incompatible type "str"; expected "int" - return t2 - -f(A(x=42)) -f(B(x='42')) - -[builtins fixtures/plugin_attrs.pyi] - -[case testEvolveVariants] -from typing import Any -import attr -import attrs - - -@attr.s(auto_attribs=True) +@attrs.frozen class C: - name: str - -c = C(name='foo') - -c = attr.assoc(c, name='test') -c = attr.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str" - -c = attrs.evolve(c, name='test') -c = attrs.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str" - -c = attrs.assoc(c, name='test') -c = attrs.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str" - -[builtins fixtures/plugin_attrs.pyi] -[typing fixtures/typing-medium.pyi] - -[case testFrozenInheritFromGeneric] -from typing import Generic, TypeVar -from attrs import field, frozen - -T = TypeVar('T') - -def f(s: str) -> int: - ... - -@frozen -class A(Generic[T]): - x: T - y: int = field(converter=f) - -@frozen -class B(A[int]): - pass - -b = B(42, 'spam') -reveal_type(b.x) # N: Revealed type is "builtins.int" -reveal_type(b.y) # N: Revealed type is "builtins.int" - -[builtins fixtures/plugin_attrs.pyi] - -[case testDefaultHashability] -from attrs import define - -@define -class A: - a: int - -reveal_type(A.__hash__) # N: Revealed type is "None" + __lt__: int -[builtins fixtures/plugin_attrs.pyi] - -[case testFrozenHashability] -from attrs import frozen - -@frozen -class A: - a: int - -reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" - -[builtins fixtures/plugin_attrs.pyi] - -[case testManualHashHashability] -from attrs import define - -@define(hash=True) -class A: - a: int - -reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" - -[builtins fixtures/plugin_attrs.pyi] - -[case testManualUnsafeHashHashability] -from attrs import define - -@define(unsafe_hash=True) -class A: - a: int - -reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" - -[builtins fixtures/plugin_attrs.pyi] - -[case testSubclassingHashability] -from attrs import define - -@define(unsafe_hash=True) -class A: - a: int - -@define -class B(A): - pass - -reveal_type(B.__hash__) # N: Revealed type is "None" - -[builtins fixtures/plugin_attrs.pyi] - -[case testManualOwnHashability] -from attrs import define, frozen - -@define -class A: - a: int - def __hash__(self) -> int: - ... - -reveal_type(A.__hash__) # N: Revealed type is "def (self: __main__.A) -> builtins.int" - -[builtins fixtures/plugin_attrs.pyi] - -[case testSubclassDefaultLosesHashability] -from attrs import define, frozen - -@define -class A: - a: int - def __hash__(self) -> int: - ... - -@define -class B(A): - pass - -reveal_type(B.__hash__) # N: Revealed type is "None" - -[builtins fixtures/plugin_attrs.pyi] - -[case testSubclassEqFalseKeepsHashability] -from attrs import define, frozen - -@define -class A: - a: int - def __hash__(self) -> int: - ... - -@define(eq=False) -class B(A): - pass - -reveal_type(B.__hash__) # N: Revealed type is "def (self: __main__.A) -> builtins.int" - -[builtins fixtures/plugin_attrs.pyi] - -[case testSubclassingFrozenHashability] -from attrs import define, frozen - -@define -class A: - a: int - -@frozen -class B(A): - pass - -reveal_type(B.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" - -[builtins fixtures/plugin_attrs.pyi] - -[case testSubclassingFrozenHashOffHashability] -from attrs import define, frozen - -@define -class A: - a: int - def __hash__(self) -> int: - ... - -@frozen(unsafe_hash=False) -class B(A): - pass - -reveal_type(B.__hash__) # N: Revealed type is "None" - -[builtins fixtures/plugin_attrs.pyi] - -[case testUnsafeHashPrecedence] -from attrs import define, frozen - -@define(unsafe_hash=True, hash=False) -class A: - pass -reveal_type(A.__hash__) # N: Revealed type is "def (self: builtins.object) -> builtins.int" - -@define(unsafe_hash=False, hash=True) -class B: - pass -reveal_type(B.__hash__) # N: Revealed type is "None" - -[builtins fixtures/plugin_attrs.pyi] - -[case testAttrsStrictOptionalSetProperly] -from typing import Generic, Optional, TypeVar - -import attr - -T = TypeVar("T") - -@attr.mutable() -class Parent(Generic[T]): - run_type: Optional[int] = None - -@attr.mutable() -class Child(Parent[float]): - pass - -Parent(run_type = None) -c = Child(run_type = None) -reveal_type(c.run_type) # N: Revealed type is "builtins.int | None" +@attrs.define +@attrs.frozen +class D: + __lt__: int = 0 [builtins fixtures/plugin_attrs.pyi]