diff --git a/mypy/checker.py b/mypy/checker.py index 12afa4d3edf5..04f1f23362e2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -12,13 +12,19 @@ import mypy.checkexpr from mypy import errorcodes as codes, join, message_registry, nodes, operators from mypy.binder import ConditionalTypeBinder, Frame, get_declaration -from mypy.checkmember import analyze_member_access +from mypy.checkmember import ( + MemberContext, + analyze_class_attribute_access, + analyze_instance_member_access, + analyze_member_access, + is_instance_var, +) from mypy.checkpattern import PatternChecker from mypy.constraints import SUPERTYPE_OF from mypy.erasetype import erase_type, erase_typevars, remove_instance_last_known_values from mypy.errorcodes import TYPE_VAR, UNUSED_AWAITABLE, UNUSED_COROUTINE, ErrorCode from mypy.errors import Errors, ErrorWatcher, report_internal_error -from mypy.expandtype import expand_self_type, expand_type, expand_type_by_instance +from mypy.expandtype import expand_self_type, expand_type from mypy.literals import Key, extract_var_from_literal_hash, literal, literal_hash from mypy.maptype import map_instance_to_supertype from mypy.meet import is_overlapping_erased_types, is_overlapping_types, meet_types @@ -3256,16 +3262,6 @@ def check_assignment( if active_class and dataclasses_plugin.is_processed_dataclass(active_class): self.fail(message_registry.DATACLASS_POST_INIT_MUST_BE_A_FUNCTION, rvalue) - # Defer PartialType's super type checking. - if ( - isinstance(lvalue, RefExpr) - and not (isinstance(lvalue_type, PartialType) and lvalue_type.type is None) - and not (isinstance(lvalue, NameExpr) and lvalue.name == "__match_args__") - ): - if self.check_compatibility_all_supers(lvalue, lvalue_type, rvalue): - # We hit an error on this line; don't check for any others - return - if isinstance(lvalue, MemberExpr) and lvalue.name == "__match_args__": self.fail(message_registry.CANNOT_MODIFY_MATCH_ARGS, lvalue) @@ -3297,12 +3293,6 @@ def check_assignment( # Try to infer a partial type. No need to check the return value, as # an error will be reported elsewhere. self.infer_partial_type(lvalue_type.var, lvalue, rvalue_type) - # Handle None PartialType's super type checking here, after it's resolved. - if isinstance(lvalue, RefExpr) and self.check_compatibility_all_supers( - lvalue, lvalue_type, rvalue - ): - # We hit an error on this line; don't check for any others - return elif ( is_literal_none(rvalue) and isinstance(lvalue, NameExpr) @@ -3394,7 +3384,7 @@ def check_assignment( self.check_indexed_assignment(index_lvalue, rvalue, lvalue) if inferred: - type_context = self.get_variable_type_context(inferred) + type_context = self.get_variable_type_context(inferred, rvalue) rvalue_type = self.expr_checker.accept(rvalue, type_context=type_context) if not ( inferred.is_final @@ -3404,15 +3394,33 @@ def check_assignment( rvalue_type = remove_instance_last_known_values(rvalue_type) self.infer_variable_type(inferred, lvalue, rvalue_type, rvalue) self.check_assignment_to_slots(lvalue) + if isinstance(lvalue, RefExpr) and not ( + isinstance(lvalue, NameExpr) and lvalue.name == "__match_args__" + ): + # We check override here at the end after storing the inferred type, since + # override check will try to access the current attribute via symbol tables + # (like a regular attribute access). + self.check_compatibility_all_supers(lvalue, rvalue) # (type, operator) tuples for augmented assignments supported with partial types partial_type_augmented_ops: Final = {("builtins.list", "+"), ("builtins.set", "|")} - def get_variable_type_context(self, inferred: Var) -> Type | None: + def get_variable_type_context(self, inferred: Var, rvalue: Expression) -> Type | None: type_contexts = [] if inferred.info: for base in inferred.info.mro[1:]: - base_type, base_node = self.lvalue_type_from_base(inferred, base) + if inferred.name not in base.names: + continue + # For inference within class body, get supertype attribute as it would look on + # a class object for lambdas overriding methods, etc. + base_node = base.names[inferred.name].node + base_type, _ = self.lvalue_type_from_base( + inferred, + base, + is_class=is_method(base_node) + or isinstance(base_node, Var) + and not is_instance_var(base_node), + ) if ( base_type and not (isinstance(base_node, Var) and base_node.invalid_partial_type) @@ -3479,15 +3487,21 @@ def try_infer_partial_generic_type_from_assignment( var.type = fill_typevars_with_any(typ.type) del partial_types[var] - def check_compatibility_all_supers( - self, lvalue: RefExpr, lvalue_type: Type | None, rvalue: Expression - ) -> bool: + def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) -> None: lvalue_node = lvalue.node # Check if we are a class variable with at least one base class if ( isinstance(lvalue_node, Var) - and lvalue.kind in (MDEF, None) - and len(lvalue_node.info.bases) > 0 # None for Vars defined via self + # If we have explicit annotation, there is no point in checking the override + # for each assignment, so we check only for the first one. + # TODO: for some reason annotated attributes on self are stored as inferred vars. + and ( + lvalue_node.line == lvalue.line + or lvalue_node.is_inferred + and not lvalue_node.explicit_self_type + ) + and lvalue.kind in (MDEF, None) # None for Vars defined via self + and len(lvalue_node.info.bases) > 0 ): for base in lvalue_node.info.mro[1:]: tnode = base.names.get(lvalue_node.name) @@ -3503,6 +3517,21 @@ def check_compatibility_all_supers( direct_bases = lvalue_node.info.direct_base_classes() last_immediate_base = direct_bases[-1] if direct_bases else None + # The historical behavior for inferred vars was to compare rvalue type against + # the type declared in a superclass. To preserve this behavior, we temporarily + # store the rvalue type on the variable. + actual_lvalue_type = None + if lvalue_node.is_inferred and not lvalue_node.explicit_self_type: + rvalue_type = self.expr_checker.accept(rvalue, lvalue_node.type) + actual_lvalue_type = lvalue_node.type + lvalue_node.type = rvalue_type + lvalue_type, _ = self.lvalue_type_from_base(lvalue_node, lvalue_node.info) + if lvalue_node.is_inferred and not lvalue_node.explicit_self_type: + lvalue_node.type = actual_lvalue_type + + if not lvalue_type: + return + for base in lvalue_node.info.mro[1:]: # The type of "__slots__" and some other attributes usually doesn't need to # be compatible with a base class. We'll still check the type of "__slots__" @@ -3523,7 +3552,6 @@ def check_compatibility_all_supers( if base_type: assert base_node is not None if not self.check_compatibility_super( - lvalue, lvalue_type, rvalue, base, @@ -3533,7 +3561,7 @@ def check_compatibility_all_supers( ): # Only show one error per variable; even if other # base classes are also incompatible - return True + return if lvalue_type and custom_setter: base_type, _ = self.lvalue_type_from_base( lvalue_node, base, setter_type=True @@ -3545,96 +3573,49 @@ def check_compatibility_all_supers( self.msg.incompatible_setter_override( lvalue, lvalue_type, base_type, base ) - return True + return if base is last_immediate_base: # At this point, the attribute was found to be compatible with all # immediate parents. break - return False def check_compatibility_super( self, - lvalue: RefExpr, - lvalue_type: Type | None, + compare_type: Type, rvalue: Expression, base: TypeInfo, base_type: Type, base_node: Node, always_allow_covariant: bool, ) -> bool: - lvalue_node = lvalue.node - assert isinstance(lvalue_node, Var) - - # Do not check whether the rvalue is compatible if the - # lvalue had a type defined; this is handled by other - # parts, and all we have to worry about in that case is - # that lvalue is compatible with the base class. - compare_node = None - if lvalue_type: - compare_type = lvalue_type - compare_node = lvalue.node - else: - compare_type = self.expr_checker.accept(rvalue, base_type) - if isinstance(rvalue, NameExpr): - compare_node = rvalue.node - if isinstance(compare_node, Decorator): - compare_node = compare_node.func - - base_type = get_proper_type(base_type) - compare_type = get_proper_type(compare_type) - if compare_type: - if isinstance(base_type, CallableType) and isinstance(compare_type, CallableType): - base_static = is_node_static(base_node) - compare_static = is_node_static(compare_node) - - # In case compare_static is unknown, also check - # if 'definition' is set. The most common case for - # this is with TempNode(), where we lose all - # information about the real rvalue node (but only get - # the rvalue type) - if compare_static is None and compare_type.definition: - compare_static = is_node_static(compare_type.definition) - - # Compare against False, as is_node_static can return None - if base_static is False and compare_static is False: - # Class-level function objects and classmethods become bound - # methods: the former to the instance, the latter to the - # class - base_type = bind_self(base_type, self.scope.active_self_type()) - compare_type = bind_self(compare_type, self.scope.active_self_type()) - - # If we are a static method, ensure to also tell the - # lvalue it now contains a static method - if base_static and compare_static: - lvalue_node.is_staticmethod = True - + # TODO: check __set__() type override for custom descriptors. + # TODO: for descriptors check also class object access override. + ok = self.check_subtype( + compare_type, + base_type, + rvalue, + message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + "expression has type", + f'base class "{base.name}" defined the type as', + ) + if ( + ok + and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes + and self.is_writable_attribute(base_node) + and not always_allow_covariant + ): ok = self.check_subtype( - compare_type, base_type, + compare_type, rvalue, - message_registry.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - "expression has type", + message_registry.COVARIANT_OVERRIDE_OF_MUTABLE_ATTRIBUTE, f'base class "{base.name}" defined the type as', + "expression has type", ) - if ( - ok - and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes - and self.is_writable_attribute(base_node) - and not always_allow_covariant - ): - ok = self.check_subtype( - base_type, - compare_type, - rvalue, - message_registry.COVARIANT_OVERRIDE_OF_MUTABLE_ATTRIBUTE, - f'base class "{base.name}" defined the type as', - "expression has type", - ) - return ok - return True + return ok def lvalue_type_from_base( - self, expr_node: Var, base: TypeInfo, setter_type: bool = False + self, expr_node: Var, base: TypeInfo, setter_type: bool = False, is_class: bool = False ) -> tuple[Type | None, SymbolNode | None]: """Find a type for a variable name in base class. @@ -3647,49 +3628,41 @@ def lvalue_type_from_base( expr_name = expr_node.name base_var = base.names.get(expr_name) - if not base_var: - return None, None - base_node = base_var.node - base_type = base_var.type - if isinstance(base_node, Var) and base_type is not None: - base_type = expand_self_type(base_node, base_type, fill_typevars(expr_node.info)) - if isinstance(base_node, Decorator): - base_node = base_node.func - base_type = base_node.type - - if not base_type: + # TODO: defer current node if the superclass node is not ready. + if ( + not base_var + or not base_var.type + or isinstance(base_var.type, PartialType) + and base_var.type.type is not None + ): return None, None - if not has_no_typevars(base_type): - self_type = self.scope.active_self_type() - assert self_type is not None, "Internal error: base lookup outside class" - if isinstance(self_type, TupleType): - instance = tuple_fallback(self_type) - else: - instance = self_type - itype = map_instance_to_supertype(instance, base) - base_type = expand_type_by_instance(base_type, itype) - - base_type = get_proper_type(base_type) - if isinstance(base_type, CallableType) and isinstance(base_node, FuncDef): - # If we are a property, return the Type of the return - # value, not the Callable - if base_node.is_property: - base_type = get_proper_type(base_type.ret_type) - if isinstance(base_type, FunctionLike) and isinstance(base_node, OverloadedFuncDef): - # Same for properties with setter - if base_node.is_property: - if setter_type: - assert isinstance(base_node.items[0], Decorator) - base_type = base_node.items[0].var.setter_type - # This flag is True only for custom properties, so it is safe to assert. - assert base_type is not None - base_type = self.bind_and_map_method(base_var, base_type, expr_node.info, base) - assert isinstance(base_type, CallableType) - base_type = get_proper_type(base_type.arg_types[0]) - else: - base_type = base_type.items[0].ret_type - return base_type, base_node + self_type = self.scope.current_self_type() + assert self_type is not None, "Internal error: base lookup outside class" + if isinstance(self_type, TupleType): + instance = tuple_fallback(self_type) + else: + instance = self_type + + mx = MemberContext( + is_lvalue=setter_type, + is_super=False, + is_operator=mypy.checkexpr.is_operator_method(expr_name), + original_type=self_type, + context=expr_node, + chk=self, + suppress_errors=True, + ) + # TODO: we should not filter "cannot determine type" errors here. + with self.msg.filter_errors(filter_deprecated=True): + if is_class: + fallback = instance.type.metaclass_type or mx.named_type("builtins.type") + base_type = analyze_class_attribute_access( + instance, expr_name, mx, mcs_fallback=fallback, override_info=base + ) + else: + base_type = analyze_instance_member_access(expr_name, instance, mx, base) + return base_type, base_var.node def check_compatibility_classvar_super( self, node: Var, base: TypeInfo, base_node: Node | None @@ -4515,6 +4488,7 @@ def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> None: refers to the variable (lvalue). If var is None, do nothing. """ if var and not self.current_node_deferred: + # TODO: should we also set 'is_ready = True' here? var.type = type var.is_inferred = True if var not in self.var_decl_frames: @@ -4525,12 +4499,16 @@ def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> None: if lvalue.def_var is not None: self.inferred_attribute_types[lvalue.def_var] = type self.store_type(lvalue, type) + p_type = get_proper_type(type) + if isinstance(p_type, CallableType) and is_node_static(p_type.definition): + # TODO: handle aliases to class methods (similarly). + var.is_staticmethod = True def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type) -> None: """Store best known type for variable if type inference failed. If a program ignores error on type inference error, the variable should get some - inferred type so that if can used later on in the program. Example: + inferred type so that it can used later on in the program. Example: x = [] # type: ignore x.append(1) # Should be ok! @@ -8687,6 +8665,13 @@ def active_self_type(self) -> Instance | TupleType | None: return fill_typevars(info) return None + def current_self_type(self) -> Instance | TupleType | None: + """Same as active_self_type() but handle functions nested in methods.""" + for item in reversed(self.stack): + if isinstance(item, TypeInfo): + return fill_typevars(item) + return None + @contextmanager def push_function(self, item: FuncItem) -> Iterator[None]: self.stack.append(item) @@ -9190,3 +9175,11 @@ def is_typeddict_type_context(lvalue_type: Type | None) -> bool: return False lvalue_proper = get_proper_type(lvalue_type) return isinstance(lvalue_proper, TypedDictType) + + +def is_method(node: SymbolNode | None) -> bool: + if isinstance(node, OverloadedFuncDef): + return not node.is_property + if isinstance(node, Decorator): + return not node.var.is_property + return isinstance(node, FuncDef) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ebc4fe8705ce..44a20341807b 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -93,11 +93,12 @@ def __init__( original_type: Type, context: Context, chk: mypy.checker.TypeChecker, - self_type: Type | None, + self_type: Type | None = None, module_symbol_table: SymbolTable | None = None, no_deferral: bool = False, is_self: bool = False, rvalue: Expression | None = None, + suppress_errors: bool = False, ) -> None: self.is_lvalue = is_lvalue self.is_super = is_super @@ -113,6 +114,7 @@ def __init__( if rvalue is not None: assert is_lvalue self.rvalue = rvalue + self.suppress_errors = suppress_errors def named_type(self, name: str) -> Instance: return self.chk.named_type(name) @@ -120,6 +122,10 @@ def named_type(self, name: str) -> Instance: def not_ready_callback(self, name: str, context: Context) -> None: self.chk.handle_cannot_determine_type(name, context) + def fail(self, msg: str) -> None: + if not self.suppress_errors: + self.msg.fail(msg, self.context) + def copy_modified( self, *, @@ -138,6 +144,7 @@ def copy_modified( module_symbol_table=self.module_symbol_table, no_deferral=self.no_deferral, rvalue=self.rvalue, + suppress_errors=self.suppress_errors, ) if self_type is not None: mx.self_type = self_type @@ -165,6 +172,7 @@ def analyze_member_access( no_deferral: bool = False, is_self: bool = False, rvalue: Expression | None = None, + suppress_errors: bool = False, ) -> Type: """Return the type of attribute 'name' of 'typ'. @@ -191,6 +199,11 @@ def analyze_member_access( 'rvalue' can be provided optionally to infer better setter type when is_lvalue is True, most notably this helps for descriptors with overloaded __set__() method. + + 'suppress_errors' will skip any logic that is only needed to generate error messages. + Note that this more of a performance optimization, one should not rely on this to not + show any messages, as some may be show e.g. by callbacks called here, + use msg.filter_errors(), if needed. """ mx = MemberContext( is_lvalue=is_lvalue, @@ -204,6 +217,7 @@ def analyze_member_access( no_deferral=no_deferral, is_self=is_self, rvalue=rvalue, + suppress_errors=suppress_errors, ) result = _analyze_member_access(name, typ, mx, override_info) possible_literal = get_proper_type(result) @@ -251,7 +265,8 @@ def _analyze_member_access( ) return _analyze_member_access(name, typ.upper_bound, mx, override_info) elif isinstance(typ, DeletedType): - mx.msg.deleted_as_rvalue(typ, mx.context) + if not mx.suppress_errors: + mx.msg.deleted_as_rvalue(typ, mx.context) return AnyType(TypeOfAny.from_error) return report_missing_attribute(mx.original_type, typ, name, mx) @@ -280,6 +295,8 @@ def report_missing_attribute( mx: MemberContext, override_info: TypeInfo | None = None, ) -> Type: + if mx.suppress_errors: + return AnyType(TypeOfAny.from_error) error_code = mx.msg.has_no_attr(original_type, typ, name, mx.context, mx.module_symbol_table) if not mx.msg.prefer_simple_messages(): if may_be_awaitable_attribute(name, typ, mx, override_info): @@ -297,7 +314,7 @@ def analyze_instance_member_access( if name == "__init__" and not mx.is_super: # Accessing __init__ in statically typed code would compromise # type safety unless used via super(). - mx.msg.fail(message_registry.CANNOT_ACCESS_INIT, mx.context) + mx.fail(message_registry.CANNOT_ACCESS_INIT) return AnyType(TypeOfAny.from_error) # The base object has an instance type. @@ -310,13 +327,14 @@ def analyze_instance_member_access( state.find_occurrences and info.name == state.find_occurrences[0] and name == state.find_occurrences[1] + and not mx.suppress_errors ): mx.msg.note("Occurrence of '{}.{}'".format(*state.find_occurrences), mx.context) # Look up the member. First look up the method dictionary. method = info.get_method(name) if method and not isinstance(method, Decorator): - if mx.is_super: + if mx.is_super and not mx.suppress_errors: validate_super_call(method, mx) if method.is_property: @@ -327,7 +345,7 @@ def analyze_instance_member_access( mx.chk.warn_deprecated(items[1], mx.context) return analyze_var(name, getter.var, typ, mx) - if mx.is_lvalue: + if mx.is_lvalue and not mx.suppress_errors: mx.msg.cant_assign_to_method(mx.context) if not isinstance(method, OverloadedFuncDef): signature = function_type(method, mx.named_type("builtins.function")) @@ -361,7 +379,6 @@ def validate_super_call(node: FuncBase, mx: MemberContext) -> None: unsafe_super = False if isinstance(node, FuncDef) and node.is_trivial_body: unsafe_super = True - impl = node elif isinstance(node, OverloadedFuncDef): if node.impl: impl = node.impl if isinstance(node.impl, FuncDef) else node.impl.func @@ -505,7 +522,7 @@ def analyze_member_var_access( if isinstance(vv, Decorator): # The associated Var node of a decorator contains the type. v = vv.var - if mx.is_super: + if mx.is_super and not mx.suppress_errors: validate_super_call(vv.func, mx) if isinstance(vv, TypeInfo): @@ -603,7 +620,7 @@ def analyze_member_var_access( if not itype.extra_attrs.mod_name: return itype.extra_attrs.attrs[name] - if mx.is_super: + if mx.is_super and not mx.suppress_errors: mx.msg.undefined_in_superclass(name, mx.context) return AnyType(TypeOfAny.from_error) else: @@ -669,11 +686,10 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: dunder_get = descriptor_type.type.get_method("__get__") if dunder_get is None: - mx.msg.fail( + mx.fail( message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( descriptor_type.str_with_options(mx.msg.options) - ), - mx.context, + ) ) return AnyType(TypeOfAny.from_error) @@ -732,11 +748,10 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type: return inferred_dunder_get_type if not isinstance(inferred_dunder_get_type, CallableType): - mx.msg.fail( + mx.fail( message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format( descriptor_type.str_with_options(mx.msg.options) - ), - mx.context, + ) ) return AnyType(TypeOfAny.from_error) @@ -747,11 +762,10 @@ def analyze_descriptor_assign(descriptor_type: Instance, mx: MemberContext) -> T instance_type = get_proper_type(mx.self_type) dunder_set = descriptor_type.type.get_method("__set__") if dunder_set is None: - mx.chk.fail( + mx.fail( message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format( descriptor_type.str_with_options(mx.msg.options) - ), - mx.context, + ).value ) return AnyType(TypeOfAny.from_error) @@ -851,11 +865,11 @@ def analyze_var( if typ: if isinstance(typ, PartialType): return mx.chk.handle_partial_var_type(typ, mx.is_lvalue, var, mx.context) - if mx.is_lvalue and var.is_property and not var.is_settable_property: - # TODO allow setting attributes in subclass (although it is probably an error) - mx.msg.read_only_property(name, itype.type, mx.context) - if mx.is_lvalue and var.is_classvar: - mx.msg.cant_assign_to_classvar(name, mx.context) + if mx.is_lvalue and not mx.suppress_errors: + if var.is_property and not var.is_settable_property: + mx.msg.read_only_property(name, itype.type, mx.context) + if var.is_classvar: + mx.msg.cant_assign_to_classvar(name, mx.context) t = freshen_all_functions_type_vars(typ) t = expand_self_type_if_needed(t, mx, var, original_itype) t = expand_type_by_instance(t, itype) @@ -875,11 +889,10 @@ def analyze_var( call_type = typ if isinstance(call_type, FunctionLike) and not call_type.is_type_obj(): - if mx.is_lvalue: - if var.is_property: - if not var.is_settable_property: - mx.msg.read_only_property(name, itype.type, mx.context) - else: + if mx.is_lvalue and not mx.suppress_errors: + if var.is_property and not var.is_settable_property: + mx.msg.read_only_property(name, itype.type, mx.context) + elif not var.is_property: mx.msg.cant_assign_to_method(mx.context) if not var.is_staticmethod: @@ -1073,22 +1086,20 @@ def analyze_class_attribute_access( is_decorated = isinstance(node.node, Decorator) is_method = is_decorated or isinstance(node.node, FuncBase) - if mx.is_lvalue: + if mx.is_lvalue and not mx.suppress_errors: if is_method: mx.msg.cant_assign_to_method(mx.context) if isinstance(node.node, TypeInfo): - mx.msg.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, mx.context) + mx.fail(message_registry.CANNOT_ASSIGN_TO_TYPE) # Refuse class attribute access if slot defined if info.slots and name in info.slots: - mx.msg.fail(message_registry.CLASS_VAR_CONFLICTS_SLOTS.format(name), mx.context) + mx.fail(message_registry.CLASS_VAR_CONFLICTS_SLOTS.format(name)) # If a final attribute was declared on `self` in `__init__`, then it # can't be accessed on the class object. if node.implicit and isinstance(node.node, Var) and node.node.is_final: - mx.msg.fail( - message_registry.CANNOT_ACCESS_FINAL_INSTANCE_ATTR.format(node.node.name), mx.context - ) + mx.fail(message_registry.CANNOT_ACCESS_FINAL_INSTANCE_ATTR.format(node.node.name)) # An assignment to final attribute on class object is also always an error, # independently of types. @@ -1146,7 +1157,7 @@ def analyze_class_attribute_access( message = message_registry.GENERIC_CLASS_VAR_ACCESS else: message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS - mx.msg.fail(message, mx.context) + mx.fail(message) t = expand_self_type_if_needed(t, mx, node.node, itype, is_class=True) # Erase non-mapped variables, but keep mapped ones, even if there is an error. # In the above example this means that we infer following types: @@ -1176,9 +1187,7 @@ def analyze_class_attribute_access( return AnyType(TypeOfAny.special_form) if isinstance(node.node, TypeVarExpr): - mx.msg.fail( - message_registry.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format(info.name, name), mx.context - ) + mx.fail(message_registry.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format(info.name, name)) return AnyType(TypeOfAny.from_error) # TODO: some logic below duplicates analyze_ref_expr in checkexpr.py @@ -1267,7 +1276,7 @@ def analyze_typeddict_access( typ, mx.context.index, setitem=True ) assigned_readonly_keys = typ.readonly_keys & key_names - if assigned_readonly_keys: + if assigned_readonly_keys and not mx.suppress_errors: mx.msg.readonly_keys_mutated(assigned_readonly_keys, context=mx.context) else: # It can also be `a.__setitem__(...)` direct call. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 0da0f7c3bbcd..e9667db3086e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -145,7 +145,7 @@ class Base: pass class Derived(Base): - __hash__ = 1 # E: Incompatible types in assignment (expression has type "int", base class "Base" defined the type as "Callable[[Base], int]") + __hash__ = 1 # E: Incompatible types in assignment (expression has type "int", base class "Base" defined the type as "Callable[[], int]") [case testOverridePartialAttributeWithMethod] # This was crashing: https://github.com/python/mypy/issues/11686. @@ -4453,7 +4453,7 @@ class A: def a(self) -> None: pass b = 1 class B(A): - a = 1 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "Callable[[A], None]") + a = 1 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "Callable[[], None]") def b(self) -> None: pass # E: Signature of "b" incompatible with supertype "A" \ # N: Superclass: \ # N: int \ @@ -4546,20 +4546,20 @@ main:7: error: Incompatible types in assignment (expression has type "Callable[[ [case testClassSpec] from typing import Callable class A(): - b = None # type: Callable[[A, int], int] + b = None # type: Callable[[int], int] class B(A): def c(self, a: int) -> int: pass b = c +reveal_type(A().b) # N: Revealed type is "def (builtins.int) -> builtins.int" +reveal_type(B().b) # N: Revealed type is "def (a: builtins.int) -> builtins.int" [case testClassSpecError] from typing import Callable class A(): - b = None # type: Callable[[A, int], int] + b = None # type: Callable[[int], int] class B(A): def c(self, a: str) -> int: pass - b = c -[out] -main:6: error: Incompatible types in assignment (expression has type "Callable[[str], int]", base class "A" defined the type as "Callable[[int], int]") + b = c # E: Incompatible types in assignment (expression has type "Callable[[str], int]", base class "A" defined the type as "Callable[[int], int]") [case testClassStaticMethod] class A(): @@ -4581,10 +4581,11 @@ class A(): class B(A): @staticmethod def b(a: str) -> None: pass - c = b + c = b # E: Incompatible types in assignment (expression has type "Callable[[str], None]", base class "A" defined the type as "Callable[[int], None]") +a: A +reveal_type(a.a) # N: Revealed type is "def (a: builtins.int)" +reveal_type(a.c) # N: Revealed type is "def (a: builtins.int)" [builtins fixtures/staticmethod.pyi] -[out] -main:8: error: Incompatible types in assignment (expression has type "Callable[[str], None]", base class "A" defined the type as "Callable[[int], None]") [case testClassStaticMethodSubclassing] class A: @@ -4649,22 +4650,20 @@ class B(A): class A: x = 1 class B(A): - x = "a" + x = "a" # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") class C(B): - x = object() -[out] -main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") -main:6: error: Incompatible types in assignment (expression has type "object", base class "A" defined the type as "int") + x = object() # E: Incompatible types in assignment (expression has type "object", base class "B" defined the type as "str") [case testClassOneErrorPerLine] class A: - x = 1 + x = 1 class B(A): - x = "" - x = 1.0 -[out] -main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") -main:5: error: Incompatible types in assignment (expression has type "float", base class "A" defined the type as "int") + x: str = "" # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") + x = 1.0 # E: Incompatible types in assignment (expression has type "float", variable has type "str") +class BInfer(A): + x = "" # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") + x = 1.0 # E: Incompatible types in assignment (expression has type "float", variable has type "str") \ + # E: Incompatible types in assignment (expression has type "float", base class "A" defined the type as "int") [case testClassIgnoreType_RedefinedAttributeAndGrandparentAttributeTypesNotIgnored] class A: @@ -4672,8 +4671,7 @@ class A: class B(A): x = '' # type: ignore class C(B): - x = '' # E: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") -[out] + x = '' [case testClassIgnoreType_RedefinedAttributeTypeIgnoredInChildren] class A: @@ -4682,7 +4680,6 @@ class B(A): x = '' # type: ignore class C(B): x = '' # type: ignore -[out] [case testInvalidMetaclassStructure] class X(type): pass @@ -8586,3 +8583,68 @@ class Wrapper(Generic[T, R]): def __call__(self, s: T) -> list[R]: ... def deco_instance(fn: Callable[[T, int], R]) -> Wrapper[T, R]: ... [builtins fixtures/property.pyi] + +[case testOverridePropertyWithDescriptor] +from typing import Any + +class StrProperty: + def __get__(self, instance: Any, owner: Any) -> str: ... + +class Base: + @property + def id(self) -> str: ... + +class BadBase: + @property + def id(self) -> int: ... + +class Derived(Base): + id = StrProperty() + +class BadDerived(BadBase): + id = StrProperty() # E: Incompatible types in assignment (expression has type "str", base class "BadBase" defined the type as "int") +[builtins fixtures/property.pyi] + +[case testLambdaInOverrideInference] +class B: + def f(self, x: int) -> int: ... +class C(B): + f = lambda s, x: x + +reveal_type(C().f) # N: Revealed type is "def (x: builtins.int) -> builtins.int" + +[case testGenericDecoratorInOverrideInference] +from typing import Any, Callable, TypeVar +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") +T = TypeVar("T") +def wrap(f: Callable[Concatenate[Any, P], T]) -> Callable[Concatenate[Any, P], T]: ... + +class Base: + def g(self, a: int) -> int: + return a + 1 + +class Derived(Base): + def _g(self, a: int) -> int: + return a + 2 + g = wrap(_g) + +reveal_type(Derived().g) # N: Revealed type is "def (a: builtins.int) -> builtins.int" +[builtins fixtures/paramspec.pyi] + +[case testClassVarOverrideWithSubclass] +class A: ... +class B(A): ... +class AA: + cls = A +class BB(AA): + cls = B + +[case testSelfReferenceWithinMethodFunction] +class B: + x: str +class C(B): + def meth(self) -> None: + def cb() -> None: + self.x: int = 1 # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "str") diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index a0a6e9d60920..9d22619590e3 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3528,8 +3528,8 @@ class Parent: def method_with(self, param: str) -> "Parent": ... class Child(Parent): - method_without: Callable[["Child"], "Child"] - method_with: Callable[["Child", str], "Child"] # E: Incompatible types in assignment (expression has type "Callable[[str], Child]", base class "Parent" defined the type as "Callable[[Arg(str, 'param')], Parent]") + method_without: Callable[[], "Child"] + method_with: Callable[[str], "Child"] # E: Incompatible types in assignment (expression has type "Callable[[str], Child]", base class "Parent" defined the type as "Callable[[Arg(str, 'param')], Parent]") [builtins fixtures/tuple.pyi] [case testDistinctFormattingUnion] diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 26ef6cb589ed..9d5902246ae5 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5235,11 +5235,7 @@ class Sub(Base): [builtins fixtures/property.pyi] [out] -tmp/a.py:3: error: Cannot determine type of "foo" -tmp/a.py:4: error: Cannot determine type of "foo" [out2] -tmp/a.py:3: error: Cannot determine type of "foo" -tmp/a.py:4: error: Cannot determine type of "foo" [case testRedefinitionClass] import b diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 22b149174541..3ac669eb93a3 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -548,7 +548,7 @@ b = B._make(['']) # type: B [case testNamedTupleIncompatibleRedefinition] from typing import NamedTuple class Crash(NamedTuple): - count: int # E: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[int, ...], object], int]") + count: int # E: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[object], int]") [builtins fixtures/tuple.pyi] [case testNamedTupleInClassNamespace] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index a7124b7a83d3..34e3f3e88080 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -332,7 +332,7 @@ class MyHashable(Protocol): class C(MyHashable): __my_hash__ = None # E: Incompatible types in assignment \ -(expression has type "None", base class "MyHashable" defined the type as "Callable[[MyHashable], int]") +(expression has type "None", base class "MyHashable" defined the type as "Callable[[], int]") [case testProtocolsWithNoneAndStrictOptional] from typing import Protocol diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 4c49bd7093cd..03229ccc92e2 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -1803,7 +1803,7 @@ class C: def bar(self) -> Self: ... foo: Callable[[S, Self], Tuple[Self, S]] -reveal_type(C().foo) # N: Revealed type is "def [S] (S`1, __main__.C) -> Tuple[__main__.C, S`1]" +reveal_type(C().foo) # N: Revealed type is "def [S] (S`2, __main__.C) -> Tuple[__main__.C, S`2]" reveal_type(C().foo(42, C())) # N: Revealed type is "Tuple[__main__.C, builtins.int]" class This: ... [builtins fixtures/tuple.pyi] @@ -1899,7 +1899,7 @@ class C: class D(C): ... -reveal_type(D.f) # N: Revealed type is "def [T] (T`1) -> T`1" +reveal_type(D.f) # N: Revealed type is "def [T] (T`3) -> T`3" reveal_type(D().f) # N: Revealed type is "def () -> __main__.D" [case testTypingSelfOnSuperTypeVarValues] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index d2b1a8a92b80..df244b3135e9 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -4520,9 +4520,9 @@ x = 0 x = '' [builtins fixtures/tuple.pyi] [out] -b.py:5: error: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[int, ...], object], int]") +b.py:5: error: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[object], int]") == -b.py:5: error: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[int, ...], object], int]") +b.py:5: error: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[object], int]") [case testReprocessEllipses1] import a