From 5547c843f0dfa012481504e19b09da3b1ed77738 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 28 Oct 2016 00:49:02 +0200 Subject: [PATCH 01/39] For class variables, lookup type in base classes (#1338, #2022, #2211) --- mypy/semanal.py | 15 +++++++- test-data/unit/check-classes.test | 58 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 999c847cceb9..67cd92213897 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1161,7 +1161,20 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: if (s.type is None and len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr)): if s.lvalues[0].is_def: - s.type = self.analyze_simple_literal_type(s.rvalue) + if (isinstance(s.lvalues[0].node, Var) and + s.lvalues[0].kind == MDEF): + # Try if any base class already defines a + # type for this class variable. + var_node = s.lvalues[0].node + for base in var_node.info.mro[1:]: + base_var = base.names.get(var_node.name()) + if base_var and base_var.type: + s.type = base_var.type + break + + if s.type is None: + s.type = self.analyze_simple_literal_type(s.rvalue) + res = analyze_type_alias(s.rvalue, self.lookup_qualified, self.lookup_fully_qualified, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 445bc9900f4c..a198538cae67 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2055,3 +2055,61 @@ class B(object, A): # E: Cannot determine consistent method resolution order (MR # flags: --fast-parser class C(metaclass=int()): # E: Dynamic metaclass not supported for 'C' pass + +[case testVariableSubclass] +class A: + a = 1 # type: int +class B(A): + a = None +[out] + +[case testVariableSubclassAssignMismatch] +class A: + a = 1 # type: int +class B(A): + a = "a" +[out] +main: note: In class "B": +main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testVariableSubclassAssignment] +class A: + a = None # type: int +class B(A): + def __init__(self) -> None: + self.a = "a" +[out] +main: note: In member "__init__" of class "B": +main:5: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testVariableSubclassTypeOverwrite] +class A: + a = None # type: int +class B(A): + a = None # type: str +class C(B): + a = "a" +[out] + +[case testVariableSuperUsage] +class A: + a = [] # type: list +class B(A): + a = [1, 2] +class C(B): + a = B.a + [3] +[builtins fixtures/list.pyi] +[out] + +[case testVariableRvalue] +class A: + a = None +class B(A): + a = 1 +class C(B): + a = "a" +[out] +main: note: In class "A": +main:2: error: Need type annotation for variable +main: note: In class "C": +main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") From dd2df987f4b6fc3ca6ef6c92ece57b17f7d2b685 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sat, 29 Oct 2016 20:01:03 +0200 Subject: [PATCH 02/39] Ignore TypeVars from base class when looking up types of class variables --- mypy/semanal.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 67cd92213897..70181c2ef25b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1169,8 +1169,10 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: for base in var_node.info.mro[1:]: base_var = base.names.get(var_node.name()) if base_var and base_var.type: - s.type = base_var.type - break + # TODO -- Can we resolve/support TypeVars at this stage? + if not isinstance(base_var.type, TypeVarType): + s.type = base_var.type + break if s.type is None: s.type = self.analyze_simple_literal_type(s.rvalue) From d7373ab5025b2ce06981ae1c1c302af49fab7205 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sat, 29 Oct 2016 20:19:27 +0200 Subject: [PATCH 03/39] Break even if the base type is a TypeVar This avoids looking into the base class of a base class while the type has been changed --- mypy/semanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 70181c2ef25b..1dc8a03f27b2 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1172,7 +1172,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: # TODO -- Can we resolve/support TypeVars at this stage? if not isinstance(base_var.type, TypeVarType): s.type = base_var.type - break + break if s.type is None: s.type = self.analyze_simple_literal_type(s.rvalue) From 450c12e5bb37be4441c3f6a0d696cfddd41c43ca Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 30 Oct 2016 01:15:47 +0200 Subject: [PATCH 04/39] Support TypeVar when lookup up class variables in a base class --- mypy/semanal.py | 8 ++++++-- test-data/unit/check-classes.test | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 1dc8a03f27b2..886ea0b9bbe3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -79,6 +79,8 @@ from mypy.sametypes import is_same_type from mypy.erasetype import erase_typevars from mypy.options import Options +from mypy.expandtype import expand_type_by_instance +from mypy.maptype import map_instance_to_supertype T = TypeVar('T') @@ -1169,8 +1171,10 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: for base in var_node.info.mro[1:]: base_var = base.names.get(var_node.name()) if base_var and base_var.type: - # TODO -- Can we resolve/support TypeVars at this stage? - if not isinstance(base_var.type, TypeVarType): + if isinstance(base_var.type, TypeVarType): + itype = map_instance_to_supertype(var_node.info.bases[0], base) + s.type = expand_type_by_instance(base_var.type, itype) + else: s.type = base_var.type break diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a198538cae67..02f583d7f901 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2113,3 +2113,22 @@ main: note: In class "A": main:2: error: Need type annotation for variable main: note: In class "C": main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testVariableTypeVar] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + a = None # type: T +class B(A[int]): + a = 1 + +[case testVariableTypeVarInvalid] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + a = None # type: T +class B(A[int]): + a = "abc" +[out] +main: note: In class "B": +main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") From 1744b9fa7a72d28f9467200c77dc767c71c71cf9 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 30 Oct 2016 12:15:18 +0100 Subject: [PATCH 05/39] An additional test-case to see if TypeVars are resolved with an extra subclass --- test-data/unit/check-classes.test | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 02f583d7f901..f87824ba6802 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2132,3 +2132,16 @@ class B(A[int]): [out] main: note: In class "B": main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testVariableTypeVarIndirectly] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + a = None # type: T +class B(A[int]): + pass +class C(B): + a = "a" +[out] +main: note: In class "C": +main:8: error: Incompatible types in assignment (expression has type "str", variable has type "int") From 150361aa518c3ae65326e5dd3afede9ccce83677 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 31 Oct 2016 19:07:35 +0100 Subject: [PATCH 06/39] Also handle TypeVars in, for example, a List --- mypy/semanal.py | 6 +++--- test-data/unit/check-classes.test | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 886ea0b9bbe3..87ed4628ff45 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1171,11 +1171,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: for base in var_node.info.mro[1:]: base_var = base.names.get(var_node.name()) if base_var and base_var.type: - if isinstance(base_var.type, TypeVarType): + if has_no_typevars(base_var.type): + s.type = base_var.type + else: itype = map_instance_to_supertype(var_node.info.bases[0], base) s.type = expand_type_by_instance(base_var.type, itype) - else: - s.type = base_var.type break if s.type is None: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index f87824ba6802..141ece1fe4bd 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2145,3 +2145,17 @@ class C(B): [out] main: note: In class "C": main:8: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testVariableTypeVarList] +from typing import List, TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + a = None # type: List[T] + b = None # type: List[T] +class B(A[int]): + a = [1] + b = [''] +[builtins fixtures/list.pyi] +[out] +main: note: In class "B": +main:8: error: List item 0 has incompatible type "str" From 01224adef9f8d65c6e9503393f4efefa86936782 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sat, 5 Nov 2016 13:08:39 +0100 Subject: [PATCH 07/39] Fix initial test-case --- test-data/unit/check-classes.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 141ece1fe4bd..2defba2c8d3d 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2058,9 +2058,9 @@ class C(metaclass=int()): # E: Dynamic metaclass not supported for 'C' [case testVariableSubclass] class A: - a = 1 # type: int + a = 1 # type: int class B(A): - a = None + a = 1 [out] [case testVariableSubclassAssignMismatch] From 8a1469766d40891a8190911277ff0f9db20d8df3 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sat, 5 Nov 2016 15:25:22 +0100 Subject: [PATCH 08/39] Validate if type is defined with an incompatible value against the type of a base class --- mypy/checker.py | 17 +++++++++++-- mypy/checkexpr.py | 2 +- mypy/checkmember.py | 31 +++++++++++++++++++++-- mypy/semanal.py | 41 ++++--------------------------- test-data/unit/check-classes.test | 11 +++++++++ 5 files changed, 61 insertions(+), 41 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 0835133fc112..b25556cbe879 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -36,14 +36,17 @@ from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder import mypy.checkexpr -from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound +from mypy.checkmember import ( + map_type_from_supertype, bind_self, erase_to_bound, find_type_from_bases +) from mypy import messages from mypy.subtypes import ( is_subtype, is_equivalent, is_proper_subtype, is_more_precise, restrict_subtype_away, is_subtype_ignoring_tvars ) from mypy.maptype import map_instance_to_supertype -from mypy.semanal import fill_typevars, set_callable_name, refers_to_fullname +from mypy.typevars import fill_typevars +from mypy.semanal import set_callable_name, refers_to_fullname from mypy.erasetype import erase_typevars from mypy.expandtype import expand_type from mypy.visitor import NodeVisitor @@ -1112,6 +1115,16 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type else: lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if lvalue_type: + if isinstance(lvalue, NameExpr): + base_type = find_type_from_bases(lvalue) + + # If a type is known, validate the type is a subtype of the base type + if base_type: + self.check_subtype(lvalue_type, base_type, lvalue, + messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + 'expression has type', + 'variable has type') + if isinstance(lvalue_type, PartialType) and lvalue_type.type is None: # Try to infer a proper type for a variable with a partial None type. rvalue_type = self.accept(rvalue) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6feda60a7979..fca760603a0f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -37,7 +37,7 @@ from mypy.checkstrformat import StringFormatterChecker from mypy.expandtype import expand_type from mypy.util import split_module_names -from mypy.semanal import fill_typevars +from mypy.typevars import fill_typevars from mypy import experiments diff --git a/mypy/checkmember.py b/mypy/checkmember.py index b57cb57ba016..5c420e09440f 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -7,14 +7,17 @@ Overloaded, TypeVarType, UnionType, PartialType, DeletedType, NoneTyp, TypeType, function_type ) -from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile +from mypy.nodes import ( + TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, MDEF, + NameExpr +) from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2 from mypy.nodes import Decorator, OverloadedFuncDef from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype from mypy.expandtype import expand_type_by_instance, expand_type from mypy.infer import infer_type_arguments -from mypy.semanal import fill_typevars +from mypy.typevars import fill_typevars, has_no_typevars from mypy import messages from mypy import subtypes MYPY = False @@ -615,3 +618,27 @@ def erase_to_bound(t: Type): if isinstance(t.item, TypeVarType): return TypeType(t.item.upper_bound) return t + + +def find_type_from_bases(e: NameExpr): + """For a NameExpr that is part of a class, walk all base classes and try + to find the first class that defines a Type for the same name.""" + + expr_node = e.node + if not (isinstance(expr_node, Var) and e.kind == MDEF and + len(expr_node.info.bases) > 0): + return None + + expr_name = expr_node.name() + expr_base = expr_node.info.bases[0] + + for base in expr_node.info.mro[1:]: + base_var = base.names.get(expr_name) + if base_var and base_var.type: + if has_no_typevars(base_var.type): + base_type = base_var.type + else: + itype = map_instance_to_supertype(expr_base, base) + base_type = expand_type_by_instance(base_var.type, itype) + + return base_type diff --git a/mypy/semanal.py b/mypy/semanal.py index de5813947176..3d3cce565dce 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -47,6 +47,7 @@ List, Dict, Set, Tuple, cast, TypeVar, Union, Optional, Callable ) +from mypy.checkmember import find_type_from_bases from mypy.nodes import ( MypyFile, TypeInfo, Node, AssignmentStmt, FuncDef, OverloadedFuncDef, ClassDef, Var, GDEF, MODULE_REF, FuncItem, Import, Expression, Lvalue, @@ -66,6 +67,7 @@ IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ) +from mypy.typevars import has_no_typevars, fill_typevars from mypy.visitor import NodeVisitor from mypy.traverser import TraverserVisitor from mypy.errors import Errors, report_internal_error @@ -77,10 +79,7 @@ from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.sametypes import is_same_type -from mypy.erasetype import erase_typevars from mypy.options import Options -from mypy.expandtype import expand_type_by_instance -from mypy.maptype import map_instance_to_supertype T = TypeVar('T') @@ -1161,25 +1160,12 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: s.type = self.anal_type(s.type, allow_tuple_literal) else: # For simple assignments, allow binding type aliases. - # Also set the type if the rvalue is a simple literal. + # Also use the type of the base class if available, or + # set the type if the rvalue is a simple literal. if (s.type is None and len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr)): if s.lvalues[0].is_def: - if (isinstance(s.lvalues[0].node, Var) and - s.lvalues[0].kind == MDEF): - # Try if any base class already defines a - # type for this class variable. - var_node = s.lvalues[0].node - for base in var_node.info.mro[1:]: - base_var = base.names.get(var_node.name()) - if base_var and base_var.type: - if has_no_typevars(base_var.type): - s.type = base_var.type - else: - itype = map_instance_to_supertype(var_node.info.bases[0], base) - s.type = expand_type_by_instance(base_var.type, itype) - break - + s.type = find_type_from_bases(s.lvalues[0]) if s.type is None: s.type = self.analyze_simple_literal_type(s.rvalue) @@ -3132,19 +3118,6 @@ def builtin_type(self, name: str, args: List[Type] = None) -> Instance: return Instance(sym.node, args or []) -def fill_typevars(typ: TypeInfo) -> Union[Instance, TupleType]: - """For a non-generic type, return instance type representing the type. - For a generic G type with parameters T1, .., Tn, return G[T1, ..., Tn]. - """ - tv = [] # type: List[Type] - for i in range(len(typ.type_vars)): - tv.append(TypeVarType(typ.defn.type_vars[i])) - inst = Instance(typ, tv) - if typ.tuple_type is None: - return inst - return typ.tuple_type.copy_modified(fallback=inst) - - def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike: if isinstance(sig, CallableType): return sig.copy_modified(arg_types=[new] + sig.arg_types[1:]) @@ -3516,7 +3489,3 @@ def find_fixed_callable_return(expr: Expression) -> Optional[CallableType]: if isinstance(t.ret_type, CallableType): return t.ret_type return None - - -def has_no_typevars(typ: Type) -> bool: - return is_same_type(typ, erase_typevars(typ)) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 2defba2c8d3d..d07f03c63f31 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2090,6 +2090,17 @@ class B(A): class C(B): a = "a" [out] +main: note: In class "B": +main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testVariableSubclassTypeOverwriteImplicit] +class A: + a = 1 +class B(A): + a = None # type: str +[out] +main: note: In class "B": +main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") [case testVariableSuperUsage] class A: From 08cd7043371ffdeeb9adc9ad6e78c8fcd715814b Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sat, 5 Nov 2016 15:29:50 +0100 Subject: [PATCH 09/39] Additional test case to ensure methods/variables don't overwrite each other --- test-data/unit/check-classes.test | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index d07f03c63f31..a9abb3dc54b9 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2170,3 +2170,15 @@ class B(A[int]): [out] main: note: In class "B": main:8: error: List item 0 has incompatible type "str" + +[case testVariableMethod] +class A: + def a(self) -> None: pass + b = 1 +class B(A): + a = 1 + def b(self) -> None: pass +[out] +main: note: In class "B": +main:5: error: Incompatible types in assignment (expression has type "int", variable has type Callable[[A], None]) +main:6: error: Signature of "b" incompatible with supertype "A" From afab148902c61e31ba37cfa828453a50c8612703 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sat, 5 Nov 2016 16:09:54 +0100 Subject: [PATCH 10/39] Also support properties --- mypy/checkmember.py | 7 +++++++ test-data/unit/check-classes.test | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 5c420e09440f..66857e3add88 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -641,4 +641,11 @@ def find_type_from_bases(e: NameExpr): itype = map_instance_to_supertype(expr_base, base) base_type = expand_type_by_instance(base_var.type, itype) + if isinstance(base_type, CallableType): + # If we are a property, return the Type of the return value, not the Callable + if isinstance(base_var.node, Decorator) and base_var.node.func.is_property: + base_type = base_type.ret_type + elif isinstance(base_var.node, FuncDef) and base_var.node.is_property: + base_type = base_type.ret_type + return base_type diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a9abb3dc54b9..102ae1a2b0c6 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2182,3 +2182,18 @@ class B(A): main: note: In class "B": main:5: error: Incompatible types in assignment (expression has type "int", variable has type Callable[[A], None]) main:6: error: Signature of "b" incompatible with supertype "A" + +[case testVariableProperty] +class A: + @property + def a(self) -> bool: pass +class B(A): + a = None # type: bool +class C(A): + a = True +class D(A): + a = 1 +[builtins fixtures/property.pyi] +[out] +main: note: In class "D": +main:9: error: Incompatible types in assignment (expression has type "int", variable has type "bool") From 8ab3463be6e903d81e1a51446fe0038e145fc1fd Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sat, 5 Nov 2016 16:13:29 +0100 Subject: [PATCH 11/39] Add missing file --- mypy/typevars.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 mypy/typevars.py diff --git a/mypy/typevars.py b/mypy/typevars.py new file mode 100644 index 000000000000..1bdb1049ebed --- /dev/null +++ b/mypy/typevars.py @@ -0,0 +1,24 @@ +from typing import Union + +from mypy.nodes import TypeInfo + +from mypy.erasetype import erase_typevars +from mypy.sametypes import is_same_type +from mypy.types import Instance, TypeVarType, TupleType, Type + + +def fill_typevars(typ: TypeInfo) -> Union[Instance, TupleType]: + """For a non-generic type, return instance type representing the type. + For a generic G type with parameters T1, .., Tn, return G[T1, ..., Tn]. + """ + tv = [] # type: List[Type] + for i in range(len(typ.type_vars)): + tv.append(TypeVarType(typ.defn.type_vars[i])) + inst = Instance(typ, tv) + if typ.tuple_type is None: + return inst + return typ.tuple_type.copy_modified(fallback=inst) + + +def has_no_typevars(typ: Type) -> bool: + return is_same_type(typ, erase_typevars(typ)) From e20372e627d13de205e613502d308d84d914653e Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 6 Nov 2016 01:28:01 +0100 Subject: [PATCH 12/39] Sync typeshed --- typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typeshed b/typeshed index 5a2a46d3bd43..cecb64b59fbd 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 5a2a46d3bd43f5fffb9a6216fe6dedf59ce2f7a1 +Subproject commit cecb64b59fbd3b74a4354f2e3156368e92237ec7 From 007b303ec2d215ef455fec408e7d466ee759ba0e Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Tue, 8 Nov 2016 08:31:07 +0100 Subject: [PATCH 13/39] Sync typeshed --- typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typeshed b/typeshed index cecb64b59fbd..970b6d5eb014 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit cecb64b59fbd3b74a4354f2e3156368e92237ec7 +Subproject commit 970b6d5eb01424e59d95842243520a51ff2e304e From 3518c9467df2d69c2c116a962c81d2895f38dbc0 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 27 Nov 2016 12:48:40 +0100 Subject: [PATCH 14/39] Add test case to show class variable type can be overwritten with Any --- test-data/unit/check-classes.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e0d8fd6999b0..6f5acccaa06e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2265,3 +2265,11 @@ class D(A): [out] main: note: In class "D": main:9: error: Incompatible types in assignment (expression has type "int", variable has type "bool") + +[case testVariableOverwriteAny] +from typing import Any +class A: + a = 1 +class B(A): + a = 'x' # type: Any +[out] From 90cc595b2307a93a0be333e493a555e02bac0985 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 30 Nov 2016 00:13:18 +0100 Subject: [PATCH 15/39] Ensure that the 'self' in methods is bound to our current class for base lookups (#2503) --- mypy/checkmember.py | 21 ++++++++++++++++----- test-data/unit/check-classes.test | 19 ++++++++++++++++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 4de316f40845..b1269c7d0cf0 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -643,10 +643,21 @@ def find_type_from_bases(e: NameExpr): base_type = expand_type_by_instance(base_var.type, itype) if isinstance(base_type, CallableType): - # If we are a property, return the Type of the return value, not the Callable - if isinstance(base_var.node, Decorator) and base_var.node.func.is_property: - base_type = base_type.ret_type - elif isinstance(base_var.node, FuncDef) and base_var.node.is_property: - base_type = base_type.ret_type + base_func = None + + if isinstance(base_var.node, Decorator): + base_func = base_var.node.func + elif isinstance(base_var.node, FuncDef): + base_func = base_var.node + + if base_func: + if base_func.is_property: + # If we are a property, return the Type of the return value, not the Callable + base_type = base_type.ret_type + elif not base_func.is_static: + # Ensure that the 'self' is bound to our current class for methods + if isinstance(base_type.arg_types[0], Instance) and base_type.arg_types[0].type == base: + arg_types = [fill_typevars(expr_node.info)] + base_type.arg_types[1:] + base_type = base_type.copy_modified(arg_types=arg_types) return base_type diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 6f5acccaa06e..a64a1b588581 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2248,7 +2248,7 @@ class B(A): def b(self) -> None: pass [out] main: note: In class "B": -main:5: error: Incompatible types in assignment (expression has type "int", variable has type Callable[[A], None]) +main:5: error: Incompatible types in assignment (expression has type "int", variable has type Callable[[B], None]) main:6: error: Signature of "b" incompatible with supertype "A" [case testVariableProperty] @@ -2273,3 +2273,20 @@ class A: class B(A): a = 'x' # type: Any [out] + +[case testMethodOverwrite] +class B(): + def n(self, a: int) -> None: pass +class C(B): + def m(self, a: int) -> None: pass + n = m +[out] + +[case testMethodOverwriteTypevar] +from typing import Generic, TypeVar +T = TypeVar("T") +class B(Generic[T]): + def n(self, a: T) -> None: pass +class C(B[int]): + def m(self, a: int) -> None: pass + n = m From 2c102d8c5dab1f8781f793244ad96ec91b68f192 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 30 Nov 2016 00:28:20 +0100 Subject: [PATCH 16/39] Fix mypy and lint errors in last commit --- mypy/checkmember.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index b1269c7d0cf0..e98d2b17bdfb 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -652,12 +652,16 @@ def find_type_from_bases(e: NameExpr): if base_func: if base_func.is_property: - # If we are a property, return the Type of the return value, not the Callable + # If we are a property, return the Type of the return + # value, not the Callable base_type = base_type.ret_type elif not base_func.is_static: # Ensure that the 'self' is bound to our current class for methods - if isinstance(base_type.arg_types[0], Instance) and base_type.arg_types[0].type == base: - arg_types = [fill_typevars(expr_node.info)] + base_type.arg_types[1:] + if (isinstance(base_type.arg_types[0], Instance) and + base_type.arg_types[0].type == base): + arg_types = [] # type: List[Type] + arg_types.append(fill_typevars(expr_node.info)) + arg_types.extend(base_type.arg_types[1:]) base_type = base_type.copy_modified(arg_types=arg_types) return base_type From fbb9ed6b7f0b5a45b54fe1c1d52a270c8970ba12 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 30 Nov 2016 01:05:56 +0100 Subject: [PATCH 17/39] Support decorators correctly and ensure 'cls' in methods is bound to our current class for base lookups --- mypy/checkmember.py | 55 +++++++++++++++++-------------- test-data/unit/check-classes.test | 24 ++++++++++++++ 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index e98d2b17bdfb..732b3ba2bfc5 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -635,33 +635,38 @@ def find_type_from_bases(e: NameExpr): for base in expr_node.info.mro[1:]: base_var = base.names.get(expr_name) - if base_var and base_var.type: - if has_no_typevars(base_var.type): - base_type = base_var.type - else: + if not base_var: + continue + + base_node = base_var.node + if isinstance(base_var.node, Decorator): + base_node = base_node.func + + base_type = base_node.type + if base_type: + if not has_no_typevars(base_type): itype = map_instance_to_supertype(expr_base, base) - base_type = expand_type_by_instance(base_var.type, itype) + base_type = expand_type_by_instance(base_type, itype) if isinstance(base_type, CallableType): - base_func = None - - if isinstance(base_var.node, Decorator): - base_func = base_var.node.func - elif isinstance(base_var.node, FuncDef): - base_func = base_var.node - - if base_func: - if base_func.is_property: - # If we are a property, return the Type of the return - # value, not the Callable - base_type = base_type.ret_type - elif not base_func.is_static: - # Ensure that the 'self' is bound to our current class for methods - if (isinstance(base_type.arg_types[0], Instance) and - base_type.arg_types[0].type == base): - arg_types = [] # type: List[Type] - arg_types.append(fill_typevars(expr_node.info)) - arg_types.extend(base_type.arg_types[1:]) - base_type = base_type.copy_modified(arg_types=arg_types) + if base_node.is_property: + # If we are a property, return the Type of the return + # value, not the Callable + base_type = base_type.ret_type + elif not base_node.is_static: + cls_or_self_arg = base_type.arg_types[0] + + # Ensure that the 'self' is bound to our current class for methods + if isinstance(cls_or_self_arg, Instance) and cls_or_self_arg.type == base: + arg_types = [] # type: List[Type] + arg_types.append(fill_typevars(expr_node.info)) + arg_types.extend(base_type.arg_types[1:]) + base_type = base_type.copy_modified(arg_types=arg_types) + if isinstance(cls_or_self_arg, CallableType) and cls_or_self_arg.is_type_obj(): + cls = base_type.arg_types[0].copy_modified(ret_type=fill_typevars(expr_node.info)) + arg_types = [] # type: List[Type] + arg_types.append(cls) + arg_types.extend(base_type.arg_types[1:]) + base_type = base_type.copy_modified(arg_types=arg_types) return base_type diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a64a1b588581..667557330616 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2290,3 +2290,27 @@ class B(Generic[T]): class C(B[int]): def m(self, a: int) -> None: pass n = m + +[case testClassMethodOverwrite] +class B(): + @classmethod + def n(self, a: int) -> None: pass +class C(B): + @classmethod + def m(self, a: int) -> None: pass + n = m +[builtins fixtures/classmethod.pyi] +[out] + +[case testClassMethodOverwriteError] +class B(): + @classmethod + def n(self, a: int) -> None: pass +class C(B): + @classmethod + def m(self, a: str) -> None: pass + n = m +[builtins fixtures/classmethod.pyi] +[out] +main: note: In class "C": +main:7: error: Incompatible types in assignment (expression has type Callable[[C, str], None], variable has type Callable[[C, int], None]) From a5441a5343ad5a661ce529edbe9039843926d3aa Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 30 Nov 2016 01:10:03 +0100 Subject: [PATCH 18/39] Additional test cases to ensure certain errors trigger --- test-data/unit/check-classes.test | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 667557330616..894c93337d17 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2274,7 +2274,7 @@ class B(A): a = 'x' # type: Any [out] -[case testMethodOverwrite] +[case testInstnaceMethodOverwrite] class B(): def n(self, a: int) -> None: pass class C(B): @@ -2282,7 +2282,17 @@ class C(B): n = m [out] -[case testMethodOverwriteTypevar] +[case testInstnaceMethodOverwriteError] +class B(): + def n(self, a: int) -> None: pass +class C(B): + def m(self, a: str) -> None: pass + n = m +[out] +main: note: In class "C": +main:5: error: Incompatible types in assignment (expression has type Callable[[C, str], None], variable has type Callable[[C, int], None]) + +[case testInstanceMethodOverwriteTypevar] from typing import Generic, TypeVar T = TypeVar("T") class B(Generic[T]): From e0ebf9013895f76fcf5b4edc7c3fcfe4df408605 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 30 Nov 2016 01:21:26 +0100 Subject: [PATCH 19/39] Some refactoring and fixing mypy/lint errors --- mypy/checkmember.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 732b3ba2bfc5..fc2487392ba7 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -639,34 +639,34 @@ def find_type_from_bases(e: NameExpr): continue base_node = base_var.node - if isinstance(base_var.node, Decorator): + base_type = base_var.type + if isinstance(base_node, Decorator): base_node = base_node.func + base_type = base_node.type - base_type = base_node.type if base_type: if not has_no_typevars(base_type): itype = map_instance_to_supertype(expr_base, base) base_type = expand_type_by_instance(base_type, itype) - if isinstance(base_type, CallableType): + if isinstance(base_type, CallableType) and isinstance(base_node, FuncDef): if base_node.is_property: # If we are a property, return the Type of the return # value, not the Callable base_type = base_type.ret_type elif not base_node.is_static: cls_or_self_arg = base_type.arg_types[0] + arg_types = base_type.arg_types[:] # Ensure that the 'self' is bound to our current class for methods - if isinstance(cls_or_self_arg, Instance) and cls_or_self_arg.type == base: - arg_types = [] # type: List[Type] - arg_types.append(fill_typevars(expr_node.info)) - arg_types.extend(base_type.arg_types[1:]) - base_type = base_type.copy_modified(arg_types=arg_types) - if isinstance(cls_or_self_arg, CallableType) and cls_or_self_arg.is_type_obj(): - cls = base_type.arg_types[0].copy_modified(ret_type=fill_typevars(expr_node.info)) - arg_types = [] # type: List[Type] - arg_types.append(cls) - arg_types.extend(base_type.arg_types[1:]) - base_type = base_type.copy_modified(arg_types=arg_types) + if isinstance(cls_or_self_arg, Instance): + slf = fill_typevars(expr_node.info) + arg_types[0] = slf + elif (isinstance(cls_or_self_arg, CallableType) and + cls_or_self_arg.is_type_obj()): + cls = cls_or_self_arg.copy_modified(ret_type=fill_typevars(expr_node.info)) + arg_types[0] = cls + + base_type = base_type.copy_modified(arg_types=arg_types) return base_type From 6f31bf33bbf316ae903b6dfa4b0b2a604eff16f6 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 11 Dec 2016 17:17:22 +0100 Subject: [PATCH 20/39] Reworked how the type of base classes are validated, to correctly support functions too --- mypy/checker.py | 27 ++++++++++++++++++++++----- mypy/checkmember.py | 18 ++++-------------- mypy/semanal.py | 4 +--- test-data/unit/check-classes.test | 20 ++++++++++++++++---- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index ce892abd2b95..f7461dffa877 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1115,17 +1115,34 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type infer_lvalue_type) else: lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) - if lvalue_type: - if isinstance(lvalue, NameExpr): - base_type = find_type_from_bases(lvalue) - # If a type is known, validate the type is a subtype of the base type - if base_type: + if isinstance(lvalue, NameExpr): + base_type = find_type_from_bases(lvalue) + + # If we are a class member and we have a base class that + # defined our lvalue too, ensure we are compatible + if base_type: + # Do not check if 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 that base class. + + if lvalue_type: + # If lvalue has a type, ensure that type is compatible + # with the base class self.check_subtype(lvalue_type, base_type, lvalue, messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, 'expression has type', 'variable has type') + else: + rvalue_type = self.accept(rvalue) + if rvalue_type: + self.check_subtype(rvalue_type, base_type, lvalue, + messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + 'expression has type', + 'variable has type') + if lvalue_type: if isinstance(lvalue_type, PartialType) and lvalue_type.type is None: # Try to infer a proper type for a variable with a partial None type. rvalue_type = self.accept(rvalue) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index fc2487392ba7..73acf970b2d1 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -654,19 +654,9 @@ def find_type_from_bases(e: NameExpr): # If we are a property, return the Type of the return # value, not the Callable base_type = base_type.ret_type - elif not base_node.is_static: - cls_or_self_arg = base_type.arg_types[0] - arg_types = base_type.arg_types[:] - - # Ensure that the 'self' is bound to our current class for methods - if isinstance(cls_or_self_arg, Instance): - slf = fill_typevars(expr_node.info) - arg_types[0] = slf - elif (isinstance(cls_or_self_arg, CallableType) and - cls_or_self_arg.is_type_obj()): - cls = cls_or_self_arg.copy_modified(ret_type=fill_typevars(expr_node.info)) - arg_types[0] = cls - - base_type = base_type.copy_modified(arg_types=arg_types) +# else: + # Otherwise this is a member function; the type for + # this is not a simple type, so do not return it +# base_type = None return base_type diff --git a/mypy/semanal.py b/mypy/semanal.py index 89ea4e41a806..1b0c752b69d9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1168,9 +1168,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: if (s.type is None and len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr)): if s.lvalues[0].is_def: - s.type = find_type_from_bases(s.lvalues[0]) - if s.type is None: - s.type = self.analyze_simple_literal_type(s.rvalue) + s.type = self.analyze_simple_literal_type(s.rvalue) res = analyze_type_alias(s.rvalue, self.lookup_qualified, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 894c93337d17..f75f18c3709b 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2237,7 +2237,7 @@ class B(A[int]): [builtins fixtures/list.pyi] [out] main: note: In class "B": -main:8: error: List item 0 has incompatible type "str" +main:8: error: Incompatible types in assignment (expression has type List[str], variable has type List[int]) [case testVariableMethod] class A: @@ -2248,7 +2248,7 @@ class B(A): def b(self) -> None: pass [out] main: note: In class "B": -main:5: error: Incompatible types in assignment (expression has type "int", variable has type Callable[[B], None]) +main:5: error: Incompatible types in assignment (expression has type "int", variable has type Callable[[A], None]) main:6: error: Signature of "b" incompatible with supertype "A" [case testVariableProperty] @@ -2290,7 +2290,7 @@ class C(B): n = m [out] main: note: In class "C": -main:5: error: Incompatible types in assignment (expression has type Callable[[C, str], None], variable has type Callable[[C, int], None]) +main:5: error: Incompatible types in assignment (expression has type Callable[[C, str], None], variable has type Callable[[B, int], None]) [case testInstanceMethodOverwriteTypevar] from typing import Generic, TypeVar @@ -2301,6 +2301,18 @@ class C(B[int]): def m(self, a: int) -> None: pass n = m +[case testInstanceMethodOverwriteTwice] +class I: + def foo(self) -> None: pass +class A(I): + def foo(self) -> None: pass +class B(A): + def bar(self) -> None: pass + foo = bar +class C(B): + def bar(self) -> None: pass + foo = bar + [case testClassMethodOverwrite] class B(): @classmethod @@ -2323,4 +2335,4 @@ class C(B): [builtins fixtures/classmethod.pyi] [out] main: note: In class "C": -main:7: error: Incompatible types in assignment (expression has type Callable[[C, str], None], variable has type Callable[[C, int], None]) +main:7: error: Incompatible types in assignment (expression has type Callable[[C, str], None], variable has type Callable[[B, int], None]) From 71f8730ad3a13b8f5c10f2afb2ea9ef97f9411d6 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 11 Dec 2016 17:48:00 +0100 Subject: [PATCH 21/39] Use bind_self() for member functions --- mypy/checker.py | 10 +++++++++- mypy/checkmember.py | 14 ++++++-------- test-data/unit/check-classes.test | 22 ++++++++++++++++++++-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b808bed04885..9d41193251fa 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1120,7 +1120,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if isinstance(lvalue, NameExpr): - base_type = find_type_from_bases(lvalue) + base_type, base_node = find_type_from_bases(lvalue) # If we are a class member and we have a base class that # defined our lvalue too, ensure we are compatible @@ -1140,6 +1140,14 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type else: rvalue_type = self.accept(rvalue) if rvalue_type: + # Class-level function objects and classmethods become bound + # methods: the former to the instance, the latter to the + # class + if (isinstance(base_type, CallableType) + and isinstance(rvalue_type, CallableType)): + base_type = bind_self(base_type) + rvalue_type = bind_self(rvalue_type) + self.check_subtype(rvalue_type, base_type, lvalue, messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, 'expression has type', diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 73acf970b2d1..a11b4dd1da08 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -628,7 +628,7 @@ def find_type_from_bases(e: NameExpr): expr_node = e.node if not (isinstance(expr_node, Var) and e.kind == MDEF and len(expr_node.info.bases) > 0): - return None + return None, None expr_name = expr_node.name() expr_base = expr_node.info.bases[0] @@ -650,13 +650,11 @@ def find_type_from_bases(e: NameExpr): base_type = expand_type_by_instance(base_type, itype) 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: - # If we are a property, return the Type of the return - # value, not the Callable base_type = base_type.ret_type -# else: - # Otherwise this is a member function; the type for - # this is not a simple type, so do not return it -# base_type = None - return base_type + return base_type, base_node + + return None, None diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index d91367aeccec..4e8a6de79c44 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2218,7 +2218,7 @@ class C(B): def m(self, a: str) -> None: pass n = m [out] -main:5: error: Incompatible types in assignment (expression has type Callable[[C, str], None], variable has type Callable[[B, int], None]) +main:5: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) [case testInstanceMethodOverwriteTypevar] from typing import Generic, TypeVar @@ -2262,4 +2262,22 @@ class C(B): n = m [builtins fixtures/classmethod.pyi] [out] -main:7: error: Incompatible types in assignment (expression has type Callable[[C, str], None], variable has type Callable[[B, int], None]) +main:7: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) + +[case testClassSpec] +from typing import Callable +class A(): + b = None # type: Callable[[A, int], int] +class B(A): + def c(self, a: int) -> int: pass + b = c + +[case testClassSpecError] +from typing import Callable +class A(): + b = None # type: Callable[[A, 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], variable has type Callable[[int], int]) From 4df17e33c0ba46ea567a7a097b794887f07e22df Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 11 Dec 2016 17:58:59 +0100 Subject: [PATCH 22/39] Removed comment about code that was added then removed from this patch Makes the diff of the PR a bit smaller --- mypy/semanal.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 1b0c752b69d9..0f8b187c8829 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1163,13 +1163,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: s.type = self.anal_type(s.type, allow_tuple_literal) else: # For simple assignments, allow binding type aliases. - # Also use the type of the base class if available, or - # set the type if the rvalue is a simple literal. + # Also set the type if the rvalue is a simple literal. if (s.type is None and len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr)): if s.lvalues[0].is_def: s.type = self.analyze_simple_literal_type(s.rvalue) - res = analyze_type_alias(s.rvalue, self.lookup_qualified, self.lookup_fully_qualified, From a9d619fce128328c9fd4cf0210160844c2a9a0fa Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 11 Dec 2016 18:04:27 +0100 Subject: [PATCH 23/39] Minor refactoring - Deduplication of code - Removed unused parameter --- mypy/checker.py | 39 ++++++++++++++++----------------------- mypy/checkmember.py | 5 ++--- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9d41193251fa..707a03e46b52 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1120,38 +1120,31 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if isinstance(lvalue, NameExpr): - base_type, base_node = find_type_from_bases(lvalue) + base_type = find_type_from_bases(lvalue) - # If we are a class member and we have a base class that - # defined our lvalue too, ensure we are compatible if base_type: # Do not check if 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 that base class. - + # is compatible with the base class. if lvalue_type: - # If lvalue has a type, ensure that type is compatible - # with the base class - self.check_subtype(lvalue_type, base_type, lvalue, + compare_type = lvalue_type + else: + compare_type = self.accept(rvalue) + + if compare_type: + # Class-level function objects and classmethods become bound + # methods: the former to the instance, the latter to the + # class + if (isinstance(base_type, CallableType) + and isinstance(compare_type, CallableType)): + base_type = bind_self(base_type) + compare_type = bind_self(compare_type) + + self.check_subtype(compare_type, base_type, lvalue, messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, 'expression has type', 'variable has type') - else: - rvalue_type = self.accept(rvalue) - if rvalue_type: - # Class-level function objects and classmethods become bound - # methods: the former to the instance, the latter to the - # class - if (isinstance(base_type, CallableType) - and isinstance(rvalue_type, CallableType)): - base_type = bind_self(base_type) - rvalue_type = bind_self(rvalue_type) - - self.check_subtype(rvalue_type, base_type, lvalue, - messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - 'expression has type', - 'variable has type') if lvalue_type: if isinstance(lvalue_type, PartialType) and lvalue_type.type is None: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index a11b4dd1da08..b91724739cc4 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -628,7 +628,7 @@ def find_type_from_bases(e: NameExpr): expr_node = e.node if not (isinstance(expr_node, Var) and e.kind == MDEF and len(expr_node.info.bases) > 0): - return None, None + return None expr_name = expr_node.name() expr_base = expr_node.info.bases[0] @@ -655,6 +655,5 @@ def find_type_from_bases(e: NameExpr): if base_node.is_property: base_type = base_type.ret_type - return base_type, base_node + return base_type - return None, None From 0de9059b37bb3e1c1fe4897fe2546473f18c3cf5 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 11 Dec 2016 18:23:43 +0100 Subject: [PATCH 24/39] Fix blank line at end of file --- mypy/checkmember.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index b91724739cc4..8db2c1307691 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -656,4 +656,3 @@ def find_type_from_bases(e: NameExpr): base_type = base_type.ret_type return base_type - From f95efbf6769cf76e06b33784c089461caeac32da Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 18 Dec 2016 12:59:01 +0100 Subject: [PATCH 25/39] Code beautification and minimized patch size --- mypy/checker.py | 56 +++++++++++++++++++++++++------ mypy/checkmember.py | 44 ++---------------------- mypy/semanal.py | 1 - test-data/unit/check-classes.test | 50 +++++++++++++-------------- 4 files changed, 72 insertions(+), 79 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 707a03e46b52..bac9206620f4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -25,7 +25,7 @@ DictionaryComprehension, ComplexExpr, EllipsisExpr, TypeAliasExpr, RefExpr, YieldExpr, BackquoteExpr, ImportFrom, ImportAll, ImportBase, AwaitExpr, - CONTRAVARIANT, COVARIANT) + CONTRAVARIANT, COVARIANT, MDEF) from mypy import nodes from mypy.types import ( Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, @@ -36,19 +36,17 @@ from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder import mypy.checkexpr -from mypy.checkmember import ( - map_type_from_supertype, bind_self, erase_to_bound, find_type_from_bases -) +from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound from mypy import messages from mypy.subtypes import ( is_subtype, is_equivalent, is_proper_subtype, is_more_precise, restrict_subtype_away, is_subtype_ignoring_tvars ) from mypy.maptype import map_instance_to_supertype -from mypy.typevars import fill_typevars +from mypy.typevars import fill_typevars, has_no_typevars from mypy.semanal import set_callable_name, refers_to_fullname from mypy.erasetype import erase_typevars -from mypy.expandtype import expand_type +from mypy.expandtype import expand_type, expand_type_by_instance from mypy.visitor import NodeVisitor from mypy.join import join_types from mypy.treetransform import TransformVisitor @@ -1120,13 +1118,13 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if isinstance(lvalue, NameExpr): - base_type = find_type_from_bases(lvalue) + base_type = self.find_type_from_bases(lvalue) if base_type: - # Do not check if 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. + # 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. if lvalue_type: compare_type = lvalue_type else: @@ -1195,6 +1193,42 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.infer_variable_type(inferred, lvalue, self.accept(rvalue), rvalue) + def find_type_from_bases(self, e: NameExpr): + """For a NameExpr that is part of a class, walk all base classes and try + to find the first class that defines a Type for the same name.""" + + expr_node = e.node + if not (isinstance(expr_node, Var) and e.kind == MDEF and + len(expr_node.info.bases) > 0): + return None + + expr_name = expr_node.name() + expr_base = expr_node.info.bases[0] + + for base in reversed(expr_node.info.mro[1:]): + base_var = base.names.get(expr_name) + if not base_var: + continue + + base_node = base_var.node + base_type = base_var.type + if isinstance(base_node, Decorator): + base_node = base_node.func + base_type = base_node.type + + if base_type: + if not has_no_typevars(base_type): + itype = map_instance_to_supertype(expr_base, base) + base_type = expand_type_by_instance(base_type, itype) + + 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 = base_type.ret_type + + return base_type + def check_assignment_to_multiple_lvalues(self, lvalues: List[Lvalue], rvalue: Expression, context: Context, infer_lvalue_type: bool = True) -> None: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 8db2c1307691..781b75bea109 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -7,17 +7,14 @@ Overloaded, TypeVarType, UnionType, PartialType, DeletedType, NoneTyp, TypeType, function_type ) -from mypy.nodes import ( - TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, MDEF, - NameExpr -) +from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile from mypy.nodes import ARG_POS, ARG_STAR, ARG_STAR2 from mypy.nodes import Decorator, OverloadedFuncDef from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype from mypy.expandtype import expand_type_by_instance, expand_type from mypy.infer import infer_type_arguments -from mypy.typevars import fill_typevars, has_no_typevars +from mypy.typevars import fill_typevars from mypy import messages from mypy import subtypes MYPY = False @@ -619,40 +616,3 @@ def erase_to_bound(t: Type): if isinstance(t.item, TypeVarType): return TypeType(t.item.upper_bound) return t - - -def find_type_from_bases(e: NameExpr): - """For a NameExpr that is part of a class, walk all base classes and try - to find the first class that defines a Type for the same name.""" - - expr_node = e.node - if not (isinstance(expr_node, Var) and e.kind == MDEF and - len(expr_node.info.bases) > 0): - return None - - expr_name = expr_node.name() - expr_base = expr_node.info.bases[0] - - for base in expr_node.info.mro[1:]: - base_var = base.names.get(expr_name) - if not base_var: - continue - - base_node = base_var.node - base_type = base_var.type - if isinstance(base_node, Decorator): - base_node = base_node.func - base_type = base_node.type - - if base_type: - if not has_no_typevars(base_type): - itype = map_instance_to_supertype(expr_base, base) - base_type = expand_type_by_instance(base_type, itype) - - 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 = base_type.ret_type - - return base_type diff --git a/mypy/semanal.py b/mypy/semanal.py index 0f8b187c8829..334021f6ff7e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -48,7 +48,6 @@ List, Dict, Set, Tuple, cast, TypeVar, Union, Optional, Callable ) -from mypy.checkmember import find_type_from_bases from mypy.nodes import ( MypyFile, TypeInfo, Node, AssignmentStmt, FuncDef, OverloadedFuncDef, ClassDef, Var, GDEF, MODULE_REF, FuncItem, Import, Expression, Lvalue, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 4e8a6de79c44..8b7b14f405ca 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2205,18 +2205,18 @@ class B(A): [case testInstnaceMethodOverwrite] class B(): - def n(self, a: int) -> None: pass + def n(self, a: int) -> None: pass class C(B): - def m(self, a: int) -> None: pass - n = m + def m(self, a: int) -> None: pass + n = m [out] [case testInstnaceMethodOverwriteError] class B(): - def n(self, a: int) -> None: pass + def n(self, a: int) -> None: pass class C(B): - def m(self, a: str) -> None: pass - n = m + def m(self, a: str) -> None: pass + n = m [out] main:5: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) @@ -2224,10 +2224,10 @@ main:5: error: Incompatible types in assignment (expression has type Callable[[s from typing import Generic, TypeVar T = TypeVar("T") class B(Generic[T]): - def n(self, a: T) -> None: pass + def n(self, a: T) -> None: pass class C(B[int]): - def m(self, a: int) -> None: pass - n = m + def m(self, a: int) -> None: pass + n = m [case testInstanceMethodOverwriteTwice] class I: @@ -2243,23 +2243,23 @@ class C(B): [case testClassMethodOverwrite] class B(): - @classmethod - def n(self, a: int) -> None: pass + @classmethod + def n(self, a: int) -> None: pass class C(B): - @classmethod - def m(self, a: int) -> None: pass - n = m + @classmethod + def m(self, a: int) -> None: pass + n = m [builtins fixtures/classmethod.pyi] [out] [case testClassMethodOverwriteError] class B(): - @classmethod - def n(self, a: int) -> None: pass + @classmethod + def n(self, a: int) -> None: pass class C(B): - @classmethod - def m(self, a: str) -> None: pass - n = m + @classmethod + def m(self, a: str) -> None: pass + n = m [builtins fixtures/classmethod.pyi] [out] main:7: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) @@ -2267,17 +2267,17 @@ main:7: error: Incompatible types in assignment (expression has type Callable[[s [case testClassSpec] from typing import Callable class A(): - b = None # type: Callable[[A, int], int] + b = None # type: Callable[[A, int], int] class B(A): - def c(self, a: int) -> int: pass - b = c + def c(self, a: int) -> int: pass + b = c [case testClassSpecError] from typing import Callable class A(): - b = None # type: Callable[[A, int], int] + b = None # type: Callable[[A, int], int] class B(A): - def c(self, a: str) -> int: pass - b = c + def c(self, a: str) -> int: pass + b = c [out] main:6: error: Incompatible types in assignment (expression has type Callable[[str], int], variable has type Callable[[int], int]) From ff1fdcdf748c659d68164279653e78e52ac128dc Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 18 Dec 2016 13:25:32 +0100 Subject: [PATCH 26/39] Split the code is better named/scoped functions and ensure we work from the current active class --- mypy/checker.py | 73 +++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index bac9206620f4..00cbb18d7458 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1118,31 +1118,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if isinstance(lvalue, NameExpr): - base_type = self.find_type_from_bases(lvalue) - - if base_type: - # 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. - if lvalue_type: - compare_type = lvalue_type - else: - compare_type = self.accept(rvalue) - - if compare_type: - # Class-level function objects and classmethods become bound - # methods: the former to the instance, the latter to the - # class - if (isinstance(base_type, CallableType) - and isinstance(compare_type, CallableType)): - base_type = bind_self(base_type) - compare_type = bind_self(compare_type) - - self.check_subtype(compare_type, base_type, lvalue, - messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - 'expression has type', - 'variable has type') + self.check_compatibility_super(lvalue, lvalue_type, rvalue) if lvalue_type: if isinstance(lvalue_type, PartialType) and lvalue_type.type is None: @@ -1193,19 +1169,45 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.infer_variable_type(inferred, lvalue, self.accept(rvalue), rvalue) - def find_type_from_bases(self, e: NameExpr): - """For a NameExpr that is part of a class, walk all base classes and try - to find the first class that defines a Type for the same name.""" + def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue: Expression): + lvalue_node = lvalue.node - expr_node = e.node - if not (isinstance(expr_node, Var) and e.kind == MDEF and - len(expr_node.info.bases) > 0): - return None + # Check if we are a class variable with at least one base class + if (isinstance(lvalue_node, Var) and + lvalue.kind == MDEF and + len(lvalue_node.info.bases) > 0): + base_type = self.lvalue_type_from_base(lvalue_node) + if base_type: + # 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. + if lvalue_type: + compare_type = lvalue_type + else: + compare_type = self.accept(rvalue) + + if compare_type: + # Class-level function objects and classmethods become bound + # methods: the former to the instance, the latter to the + # class + if (isinstance(base_type, CallableType) and + isinstance(compare_type, CallableType)): + base_type = bind_self(base_type, self.scope.active_class()) + compare_type = bind_self(compare_type, self.scope.active_class()) + + self.check_subtype(compare_type, base_type, lvalue, + messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + 'expression has type', + 'variable has type') + + def lvalue_type_from_base(self, expr_node: Var): + """For a NameExpr that is part of a class, walk all base classes and try + to find the first class that defines a Type for the same name.""" expr_name = expr_node.name() - expr_base = expr_node.info.bases[0] - for base in reversed(expr_node.info.mro[1:]): + for base in expr_node.info.mro[1:]: base_var = base.names.get(expr_name) if not base_var: continue @@ -1218,7 +1220,8 @@ def find_type_from_bases(self, e: NameExpr): if base_type: if not has_no_typevars(base_type): - itype = map_instance_to_supertype(expr_base, base) + instance = cast(Instance, self.scope.active_class()) + itype = map_instance_to_supertype(instance, base) base_type = expand_type_by_instance(base_type, itype) if isinstance(base_type, CallableType) and isinstance(base_node, FuncDef): From 20efc80d312d5eee6de0b915822e4018c2f9ffc1 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 18 Dec 2016 13:32:09 +0100 Subject: [PATCH 27/39] Change the order of base class checks; the top base class type now wins --- mypy/checker.py | 2 +- test-data/unit/check-classes.test | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 00cbb18d7458..70ee9c1df548 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1207,7 +1207,7 @@ def lvalue_type_from_base(self, expr_node: Var): to find the first class that defines a Type for the same name.""" expr_name = expr_node.name() - for base in expr_node.info.mro[1:]: + for base in reversed(expr_node.info.mro[1:]): base_var = base.names.get(expr_name) if not base_var: continue diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 8b7b14f405ca..81c1207fa748 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2097,6 +2097,7 @@ class C(B): a = "a" [out] main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") [case testVariableSubclassTypeOverwriteImplicit] class A: @@ -2116,16 +2117,16 @@ class C(B): [builtins fixtures/list.pyi] [out] -[case testVariableRvalue] +[case testClassTopBaseClassFirst] +from typing import Union class A: - a = None + a = None # type: Union[int, str] class B(A): a = 1 class C(B): - a = "a" -[out] -main:2: error: Need type annotation for variable -main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") + a = "str" +class D(A): + a = "str" [case testVariableTypeVar] from typing import TypeVar, Generic From 47c0573bba81a686bdacd86844a0f1db2cd67f6b Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 18 Dec 2016 14:10:16 +0100 Subject: [PATCH 28/39] Don't bind_self() when either base or compare are static members --- mypy/checker.py | 54 ++++++++++++++++++++++++++----- test-data/unit/check-classes.test | 32 ++++++++++++++++++ 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 70ee9c1df548..c9a0655c6414 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -25,7 +25,7 @@ DictionaryComprehension, ComplexExpr, EllipsisExpr, TypeAliasExpr, RefExpr, YieldExpr, BackquoteExpr, ImportFrom, ImportAll, ImportBase, AwaitExpr, - CONTRAVARIANT, COVARIANT, MDEF) + CONTRAVARIANT, COVARIANT, MDEF, Node) from mypy import nodes from mypy.types import ( Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType, @@ -1176,26 +1176,50 @@ def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue: if (isinstance(lvalue_node, Var) and lvalue.kind == MDEF and len(lvalue_node.info.bases) > 0): - base_type = self.lvalue_type_from_base(lvalue_node) + base_type, base_node = self.lvalue_type_from_base(lvalue_node) if base_type: # 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 # type: Node if lvalue_type: compare_type = lvalue_type + compare_node = lvalue_node else: compare_type = self.accept(rvalue) + if isinstance(rvalue, NameExpr): + compare_node = rvalue.node + if isinstance(compare_node, Decorator): + compare_node = compare_node.func if compare_type: - # Class-level function objects and classmethods become bound - # methods: the former to the instance, the latter to the - # class if (isinstance(base_type, CallableType) and isinstance(compare_type, CallableType)): - base_type = bind_self(base_type, self.scope.active_class()) - compare_type = bind_self(compare_type, self.scope.active_class()) + 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_class()) + compare_type = bind_self(compare_type, self.scope.active_class()) + + # 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 self.check_subtype(compare_type, base_type, lvalue, messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, @@ -1230,7 +1254,9 @@ def lvalue_type_from_base(self, expr_node: Var): if base_node.is_property: base_type = base_type.ret_type - return base_type + return base_type, base_node + + return None, None def check_assignment_to_multiple_lvalues(self, lvalues: List[Lvalue], rvalue: Expression, context: Context, @@ -2846,6 +2872,18 @@ def is_valid_inferred_type_component(typ: Type) -> bool: return True +def is_node_static(node: Node) -> Optional[bool]: + """Find out if a node describes a static function method.""" + + if isinstance(node, FuncDef): + return node.is_static + + if isinstance(node, Var): + return node.is_staticmethod + + return None + + class Scope: # We keep two stacks combined, to maintain the relative order stack = None # type: List[Union[Type, FuncItem, MypyFile]] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 81c1207fa748..45f0541f5da5 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2282,3 +2282,35 @@ class B(A): b = c [out] main:6: error: Incompatible types in assignment (expression has type Callable[[str], int], variable has type Callable[[int], int]) + +[case testClassStaticMethod] +class A(): + @staticmethod + def a(a: int) -> None: pass +class B(A): + @staticmethod + def b(a: str) -> None: pass + a = b +[builtins fixtures/staticmethod.pyi] +[out] +main:7: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) + +[case testClassStaticMethodIndirect] +class A(): + @staticmethod + def a(a: int) -> None: pass + c = a +class B(A): + @staticmethod + def b(a: str) -> None: pass + c = b +[builtins fixtures/staticmethod.pyi] +[out] +main:8: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) + +[case testTempNode] +class A(): + def a(self) -> None: pass +class B(A): + def b(self) -> None: pass + a = c = b From fec6ca9f9e855cd0cbb8dc9c6808a802096f8a49 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 19 Dec 2016 23:25:28 +0100 Subject: [PATCH 29/39] Sync typeshed --- typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typeshed b/typeshed index 6fc57a419bf2..7e89fc0d4912 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 6fc57a419bf2559e9d4a38ca7591897e19f856dc +Subproject commit 7e89fc0d49129338721b4f93fb15c8047e909505 From 1fd694656266ac9da3532a2e2fdad4d21a3d0876 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 21 Dec 2016 19:39:50 +0100 Subject: [PATCH 30/39] When accepting rvalue indicate the type context from the base This allows a type like "List[object]" to be set to a value like "[1]" --- mypy/checker.py | 2 +- test-data/unit/check-classes.test | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6175236b0b96..618aedafffe3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1194,7 +1194,7 @@ def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue: compare_type = lvalue_type compare_node = lvalue_node else: - compare_type = self.accept(rvalue) + compare_type = self.accept(rvalue, base_type) if isinstance(rvalue, NameExpr): compare_node = rvalue.node if isinstance(compare_node, Decorator): diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index d07cbf331278..74ba914456a6 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2520,7 +2520,7 @@ class B(A[int]): b = [''] [builtins fixtures/list.pyi] [out] -main:8: error: Incompatible types in assignment (expression has type List[str], variable has type List[int]) +main:8: error: List item 0 has incompatible type "str" [case testVariableMethod] class A: @@ -2665,3 +2665,11 @@ class A(): class B(A): def b(self) -> None: pass a = c = b + +[case testListObject] +from typing import List +class A: + x = [] # type: List[object] +class B(A): + x = [1] +[builtins fixtures/list.pyi] From 41717210715826de1b9452542c478ca221a86f44 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 21 Dec 2016 20:17:23 +0100 Subject: [PATCH 31/39] Check all base classes for type-correctness; not just the top or bottom one --- mypy/checker.py | 126 ++++++++++++++++-------------- test-data/unit/check-classes.test | 14 +++- 2 files changed, 82 insertions(+), 58 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 618aedafffe3..633cc7407936 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1119,7 +1119,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if isinstance(lvalue, NameExpr): - self.check_compatibility_super(lvalue, lvalue_type, rvalue) + self.check_compatibility_all_supers(lvalue, lvalue_type, rvalue) if lvalue_type: if isinstance(lvalue_type, PartialType) and lvalue_type.type is None: @@ -1175,73 +1175,85 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.infer_variable_type(inferred, lvalue, self.accept(rvalue), rvalue) - def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue: Expression): + def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type, + 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 == MDEF and len(lvalue_node.info.bases) > 0): - base_type, base_node = self.lvalue_type_from_base(lvalue_node) - if base_type: - # 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 # type: Node - if lvalue_type: - compare_type = lvalue_type - compare_node = lvalue_node - else: - compare_type = self.accept(rvalue, base_type) - if isinstance(rvalue, NameExpr): - compare_node = rvalue.node - if isinstance(compare_node, Decorator): - compare_node = compare_node.func - - 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_class()) - compare_type = bind_self(compare_type, self.scope.active_class()) - - # 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 - - self.check_subtype(compare_type, base_type, lvalue, - messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - 'expression has type', - 'variable has type') - - def lvalue_type_from_base(self, expr_node: Var): + for base in reversed(lvalue_node.info.mro[1:]): + if not self.check_compatibility_super(lvalue, lvalue_type, rvalue, base): + # Only show one error per variable; even if other + # base classes are also incompatible + break + + def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue: Expression, + base: TypeInfo) -> bool: + lvalue_node = lvalue.node + assert isinstance(lvalue_node, Var) + + base_type, base_node = self.lvalue_type_from_base(lvalue_node, base) + + if base_type: + # 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 # type: Node + if lvalue_type: + compare_type = lvalue_type + compare_node = lvalue.node + else: + compare_type = self.accept(rvalue, base_type) + if isinstance(rvalue, NameExpr): + compare_node = rvalue.node + if isinstance(compare_node, Decorator): + compare_node = compare_node.func + + 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_class()) + compare_type = bind_self(compare_type, self.scope.active_class()) + + # 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 + + return self.check_subtype(compare_type, base_type, lvalue, + messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + 'expression has type', + 'variable has type') + return True + + def lvalue_type_from_base(self, expr_node: Var, + base: TypeInfo) -> Tuple[Optional[Type], Optional[Node]]: """For a NameExpr that is part of a class, walk all base classes and try to find the first class that defines a Type for the same name.""" expr_name = expr_node.name() + base_var = base.names.get(expr_name) - for base in reversed(expr_node.info.mro[1:]): - base_var = base.names.get(expr_name) - if not base_var: - continue - + if base_var: base_node = base_var.node base_type = base_var.type if isinstance(base_node, Decorator): diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 74ba914456a6..ccdf61516a12 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2468,7 +2468,7 @@ class C(B): [builtins fixtures/list.pyi] [out] -[case testClassTopBaseClassFirst] +[case testClassAllBases] from typing import Union class A: a = None # type: Union[int, str] @@ -2478,6 +2478,8 @@ class C(B): a = "str" class D(A): a = "str" +[out] +main:7: error: Incompatible types in assignment (expression has type "str", variable has type "int") [case testVariableTypeVar] from typing import TypeVar, Generic @@ -2673,3 +2675,13 @@ class A: class B(A): x = [1] [builtins fixtures/list.pyi] + +[case testClassMemberObject] +class A: + x = object() +class B(A): + x = 1 +class C(B): + x = '' +[out] +main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") From afb89cb174655702a3b37c2fd98f6512d1d1cd91 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 23 Dec 2016 19:49:57 +0100 Subject: [PATCH 32/39] __slots__ can be assigned Tuples of different elements without error --- mypy/checker.py | 9 +++++++++ test-data/unit/check-classes.test | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 633cc7407936..0b769cb07fad 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1185,6 +1185,15 @@ def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type, len(lvalue_node.info.bases) > 0): for base in reversed(lvalue_node.info.mro[1:]): + # Only check __slots__ against the 'object' + # If a base class defines a Tuple of 3 elements, a child of + # this class should not be allowed to define it as a Tuple of + # anything other than 3 elements. The exception to this rule + # is __slots__, where it is allowed for any child class to + # redefine it. + if lvalue_node.name() == "__slots__" and base.name() != "object": + continue + if not self.check_compatibility_super(lvalue, lvalue_type, rvalue, base): # Only show one error per variable; even if other # base classes are also incompatible diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ccdf61516a12..ffeeb44ea1e2 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2685,3 +2685,9 @@ class C(B): x = '' [out] main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testSlots] +class A: + __slots__ = ("a") +class B(A): + __slots__ = ("a", "b") From 3af055b682ff745e01081f6f21fd48a6c29ab01b Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 23 Dec 2016 19:56:35 +0100 Subject: [PATCH 33/39] Sync typeshed --- typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typeshed b/typeshed index 7e89fc0d4912..7853c26f7901 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 7e89fc0d49129338721b4f93fb15c8047e909505 +Subproject commit 7853c26f790117803ff5e86e0469ce4cbf182c4c From eff4472943860d646d24d926e9794f65ba49422c Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Fri, 23 Dec 2016 20:00:24 +0100 Subject: [PATCH 34/39] Use fullname() instead of name() where possible --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 486f1ddec532..66b79e05cff7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1191,7 +1191,7 @@ def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type, # anything other than 3 elements. The exception to this rule # is __slots__, where it is allowed for any child class to # redefine it. - if lvalue_node.name() == "__slots__" and base.name() != "object": + if lvalue_node.name() == "__slots__" and base.fullname() != "builtins.object": continue if not self.check_compatibility_super(lvalue, lvalue_type, rvalue, base): From f70d65083a82d0bd2598d64aaccc1b8895727f2e Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 15 Jan 2017 14:37:37 +0100 Subject: [PATCH 35/39] Reverse the order in which class members are checked --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 66b79e05cff7..504c10bb35b0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1184,7 +1184,7 @@ def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type, lvalue.kind == MDEF and len(lvalue_node.info.bases) > 0): - for base in reversed(lvalue_node.info.mro[1:]): + for base in lvalue_node.info.mro[1:]: # Only check __slots__ against the 'object' # If a base class defines a Tuple of 3 elements, a child of # this class should not be allowed to define it as a Tuple of From 355c21b83232321869e10f5041ff1769922ac64f Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 15 Jan 2017 14:50:18 +0100 Subject: [PATCH 36/39] Only show one error per line of code about compatiblity errors --- mypy/checker.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 06f898c66a0b..8bcd5791e092 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1119,7 +1119,9 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if isinstance(lvalue, NameExpr): - self.check_compatibility_all_supers(lvalue, lvalue_type, rvalue) + 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 lvalue_type: if isinstance(lvalue_type, PartialType) and lvalue_type.type is None: @@ -1176,7 +1178,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type rvalue) def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type, - rvalue: Expression) -> None: + rvalue: Expression) -> bool: lvalue_node = lvalue.node # Check if we are a class variable with at least one base class @@ -1197,7 +1199,8 @@ def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type, if not self.check_compatibility_super(lvalue, lvalue_type, rvalue, base): # Only show one error per variable; even if other # base classes are also incompatible - break + return True + return False def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue: Expression, base: TypeInfo) -> bool: From b2672cc007a730a0bc21f31b1bcf3ef726817ca4 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 23 Jan 2017 19:30:19 +0100 Subject: [PATCH 37/39] Add test-cases for last two fixes --- test-data/unit/check-classes.test | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ae6786ae19a6..798bd1dab882 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2705,3 +2705,24 @@ class A: __slots__ = ("a") class B(A): __slots__ = ("a", "b") + +[case testClassOrderOfError] +class A: + x = 1 +class B(A): + x = "a" +class C(B): + x = object() +[out] +main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:6: error: Incompatible types in assignment (expression has type "object", variable has type "str") + +[case testClassOneErrorPerLine] +class A: + x = 1 +class B(A): + x = "" + x = 1.0 +[out] +main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:5: error: Incompatible types in assignment (expression has type "str", variable has type "int") From 194d5d228036cdf136f9dee846cd945da8930784 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 23 Jan 2017 19:45:19 +0100 Subject: [PATCH 38/39] Mention in the commit message which base class defined the type of the member --- mypy/checker.py | 2 +- test-data/unit/check-classes.test | 42 +++++++++++++++---------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index a318c40fabe3..bc901a3bba38 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1241,7 +1241,7 @@ def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue: return self.check_subtype(compare_type, base_type, lvalue, messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, 'expression has type', - 'variable has type') + 'base class "%s" defined the type as' % base.name()) return True def lvalue_type_from_base(self, expr_node: Var, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e1197df85245..feebb249fce6 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2467,7 +2467,7 @@ class A: class B(A): a = "a" [out] -main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testVariableSubclassAssignment] class A: @@ -2486,8 +2486,8 @@ class B(A): class C(B): a = "a" [out] -main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") -main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") +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 "str", base class "A" defined the type as "int") [case testVariableSubclassTypeOverwriteImplicit] class A: @@ -2495,7 +2495,7 @@ class A: class B(A): a = None # type: str [out] -main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:4: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testVariableSuperUsage] class A: @@ -2518,7 +2518,7 @@ class C(B): class D(A): a = "str" [out] -main:7: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:7: error: Incompatible types in assignment (expression has type "str", base class "B" defined the type as "int") [case testVariableTypeVar] from typing import TypeVar, Generic @@ -2536,7 +2536,7 @@ class A(Generic[T]): class B(A[int]): a = "abc" [out] -main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:6: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testVariableTypeVarIndirectly] from typing import TypeVar, Generic @@ -2548,7 +2548,7 @@ class B(A[int]): class C(B): a = "a" [out] -main:8: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:8: error: Incompatible types in assignment (expression has type "str", base class "A" defined the type as "int") [case testVariableTypeVarList] from typing import List, TypeVar, Generic @@ -2571,7 +2571,7 @@ class B(A): a = 1 def b(self) -> None: pass [out] -main:5: error: Incompatible types in assignment (expression has type "int", variable has type Callable[[A], None]) +main:5: error: Incompatible types in assignment (expression has type "int", base class "A" defined the type as Callable[[A], None]) main:6: error: Signature of "b" incompatible with supertype "A" [case testVariableProperty] @@ -2586,7 +2586,7 @@ class D(A): a = 1 [builtins fixtures/property.pyi] [out] -main:9: error: Incompatible types in assignment (expression has type "int", variable has type "bool") +main:9: error: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "bool") [case testVariableOverwriteAny] from typing import Any @@ -2596,7 +2596,7 @@ class B(A): a = 'x' # type: Any [out] -[case testInstnaceMethodOverwrite] +[case testInstanceMethodOverwrite] class B(): def n(self, a: int) -> None: pass class C(B): @@ -2604,14 +2604,14 @@ class C(B): n = m [out] -[case testInstnaceMethodOverwriteError] +[case testInstanceMethodOverwriteError] class B(): def n(self, a: int) -> None: pass class C(B): def m(self, a: str) -> None: pass n = m [out] -main:5: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) +main:5: error: Incompatible types in assignment (expression has type Callable[[str], None], base class "B" defined the type as Callable[[int], None]) [case testInstanceMethodOverwriteTypevar] from typing import Generic, TypeVar @@ -2655,7 +2655,7 @@ class C(B): n = m [builtins fixtures/classmethod.pyi] [out] -main:7: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) +main:7: error: Incompatible types in assignment (expression has type Callable[[str], None], base class "B" defined the type as Callable[[int], None]) [case testClassSpec] from typing import Callable @@ -2673,7 +2673,7 @@ 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], variable has type Callable[[int], int]) +main:6: error: Incompatible types in assignment (expression has type Callable[[str], int], base class "A" defined the type as Callable[[int], int]) [case testClassStaticMethod] class A(): @@ -2685,7 +2685,7 @@ class B(A): a = b [builtins fixtures/staticmethod.pyi] [out] -main:7: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) +main:7: error: Incompatible types in assignment (expression has type Callable[[str], None], base class "A" defined the type as Callable[[int], None]) [case testClassStaticMethodIndirect] class A(): @@ -2698,7 +2698,7 @@ class B(A): c = b [builtins fixtures/staticmethod.pyi] [out] -main:8: error: Incompatible types in assignment (expression has type Callable[[str], None], variable has type Callable[[int], None]) +main:8: error: Incompatible types in assignment (expression has type Callable[[str], None], base class "A" defined the type as Callable[[int], None]) [case testTempNode] class A(): @@ -2723,7 +2723,7 @@ class B(A): class C(B): x = '' [out] -main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") +main:6: error: Incompatible types in assignment (expression has type "str", base class "B" defined the type as "int") [case testSlots] class A: @@ -2739,8 +2739,8 @@ class B(A): class C(B): x = object() [out] -main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") -main:6: error: Incompatible types in assignment (expression has type "object", variable has type "str") +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 "B" defined the type as "str") [case testClassOneErrorPerLine] class A: @@ -2749,5 +2749,5 @@ class B(A): x = "" x = 1.0 [out] -main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") -main:5: error: Incompatible types in assignment (expression has type "str", variable has type "int") +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 "str", base class "A" defined the type as "int") From edbb306cc397c8c8d3f51758d1ba24576ebdc727 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 23 Jan 2017 20:20:19 +0100 Subject: [PATCH 39/39] Stop checking the type at the first base class that defines the type This allows types to change over base classes, allowing use-cases with #type: ignore in case you really have no other choice --- mypy/checker.py | 114 ++++++++++++++++-------------- test-data/unit/check-classes.test | 10 ++- 2 files changed, 69 insertions(+), 55 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index bc901a3bba38..1172e1972db9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1182,66 +1182,72 @@ def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type, if lvalue_node.name() == "__slots__" and base.fullname() != "builtins.object": continue - if not self.check_compatibility_super(lvalue, lvalue_type, rvalue, base): - # Only show one error per variable; even if other - # base classes are also incompatible - return True + base_type, base_node = self.lvalue_type_from_base(lvalue_node, base) + + if base_type: + if not self.check_compatibility_super(lvalue, + lvalue_type, + rvalue, + base, + base_type, + base_node): + # Only show one error per variable; even if other + # base classes are also incompatible + return True + break return False def check_compatibility_super(self, lvalue: NameExpr, lvalue_type: Type, rvalue: Expression, - base: TypeInfo) -> bool: + base: TypeInfo, base_type: Type, base_node: Node) -> bool: lvalue_node = lvalue.node assert isinstance(lvalue_node, Var) - base_type, base_node = self.lvalue_type_from_base(lvalue_node, base) - - if base_type: - # 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 # type: Node - if lvalue_type: - compare_type = lvalue_type - compare_node = lvalue.node - else: - compare_type = self.accept(rvalue, base_type) - if isinstance(rvalue, NameExpr): - compare_node = rvalue.node - if isinstance(compare_node, Decorator): - compare_node = compare_node.func - - 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_class()) - compare_type = bind_self(compare_type, self.scope.active_class()) - - # 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 - - return self.check_subtype(compare_type, base_type, lvalue, - messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - 'expression has type', - 'base class "%s" defined the type as' % base.name()) + # 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 # type: Node + if lvalue_type: + compare_type = lvalue_type + compare_node = lvalue.node + else: + compare_type = self.accept(rvalue, base_type) + if isinstance(rvalue, NameExpr): + compare_node = rvalue.node + if isinstance(compare_node, Decorator): + compare_node = compare_node.func + + 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_class()) + compare_type = bind_self(compare_type, self.scope.active_class()) + + # 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 + + return self.check_subtype(compare_type, base_type, lvalue, + messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + 'expression has type', + 'base class "%s" defined the type as' % base.name()) return True def lvalue_type_from_base(self, expr_node: Var, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index feebb249fce6..c71ffdc4270f 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2487,7 +2487,6 @@ class C(B): a = "a" [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 "str", base class "A" defined the type as "int") [case testVariableSubclassTypeOverwriteImplicit] class A: @@ -2751,3 +2750,12 @@ class B(A): [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 "str", base class "A" defined the type as "int") + +[case testClassIgnoreType] +class A: + x = 0 +class B(A): + x = '' # type: ignore +class C(B): + x = '' +[out]