Skip to content

For class variables, lookup type in base classes (#1338, #2022, #2211) #2510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Jan 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5547c84
For class variables, lookup type in base classes (#1338, #2022, #2211)
TrueBrain Oct 27, 2016
dd2df98
Ignore TypeVars from base class when looking up types of class variables
TrueBrain Oct 29, 2016
d7373ab
Break even if the base type is a TypeVar
TrueBrain Oct 29, 2016
450c12e
Support TypeVar when lookup up class variables in a base class
TrueBrain Oct 29, 2016
1744b9f
An additional test-case to see if TypeVars are resolved with an extra…
TrueBrain Oct 30, 2016
150361a
Also handle TypeVars in, for example, a List
TrueBrain Oct 31, 2016
3871a23
Merge remote-tracking branch 'upstream/master' into class-variable
TrueBrain Nov 5, 2016
01224ad
Fix initial test-case
TrueBrain Nov 5, 2016
8a14697
Validate if type is defined with an incompatible value against the ty…
TrueBrain Nov 5, 2016
08cd704
Additional test case to ensure methods/variables don't overwrite each…
TrueBrain Nov 5, 2016
afab148
Also support properties
TrueBrain Nov 5, 2016
8ab3463
Add missing file
TrueBrain Nov 5, 2016
e20372e
Sync typeshed
TrueBrain Nov 6, 2016
007b303
Sync typeshed
TrueBrain Nov 8, 2016
479430e
Merge branch 'master' into class-variable
TrueBrain Nov 8, 2016
ac971f4
Merge branch 'master' into class-variable
TrueBrain Nov 12, 2016
8c0138a
Merge branch 'master' into class-variable
TrueBrain Nov 27, 2016
3518c94
Add test case to show class variable type can be overwritten with Any
TrueBrain Nov 27, 2016
90cc595
Ensure that the 'self' in methods is bound to our current class for b…
TrueBrain Nov 29, 2016
2c102d8
Fix mypy and lint errors in last commit
TrueBrain Nov 29, 2016
fbb9ed6
Support decorators correctly and ensure 'cls' in methods is bound to …
TrueBrain Nov 30, 2016
a5441a5
Additional test cases to ensure certain errors trigger
TrueBrain Nov 30, 2016
e0ebf90
Some refactoring and fixing mypy/lint errors
TrueBrain Nov 30, 2016
6f31bf3
Reworked how the type of base classes are validated, to correctly sup…
TrueBrain Dec 11, 2016
7d2ce8f
Merge branch 'master' into class-variable
TrueBrain Dec 11, 2016
71f8730
Use bind_self() for member functions
TrueBrain Dec 11, 2016
4df17e3
Removed comment about code that was added then removed from this patch
TrueBrain Dec 11, 2016
a9d619f
Minor refactoring
TrueBrain Dec 11, 2016
0de9059
Fix blank line at end of file
TrueBrain Dec 11, 2016
f95efbf
Code beautification and minimized patch size
TrueBrain Dec 18, 2016
ff1fdcd
Split the code is better named/scoped functions and ensure we work fr…
TrueBrain Dec 18, 2016
20efc80
Change the order of base class checks; the top base class type now wins
TrueBrain Dec 18, 2016
47c0573
Don't bind_self() when either base or compare are static members
TrueBrain Dec 18, 2016
d6d8ea4
Merge branch 'master' into class-variable
TrueBrain Dec 18, 2016
fec6ca9
Sync typeshed
TrueBrain Dec 19, 2016
eb83799
Merge branch 'master' into class-variable
TrueBrain Dec 20, 2016
1fd6946
When accepting rvalue indicate the type context from the base
TrueBrain Dec 21, 2016
4171721
Check all base classes for type-correctness; not just the top or bott…
TrueBrain Dec 21, 2016
afb89cb
__slots__ can be assigned Tuples of different elements without error
TrueBrain Dec 23, 2016
94ffd0c
Merge branch 'master' into class-variable
TrueBrain Dec 23, 2016
3af055b
Sync typeshed
TrueBrain Dec 23, 2016
eff4472
Use fullname() instead of name() where possible
TrueBrain Dec 23, 2016
a45a06d
Merge branch 'master' into class-variable
TrueBrain Dec 24, 2016
f70d650
Reverse the order in which class members are checked
TrueBrain Jan 15, 2017
59ec0d4
Merge branch 'master' into class-variable
TrueBrain Jan 15, 2017
355c21b
Only show one error per line of code about compatiblity errors
TrueBrain Jan 15, 2017
b2672cc
Add test-cases for last two fixes
TrueBrain Jan 23, 2017
a5f6597
Merge branch 'master' into class-variable
TrueBrain Jan 23, 2017
194d5d2
Mention in the commit message which base class defined the type of th…
TrueBrain Jan 23, 2017
edbb306
Stop checking the type at the first base class that defines the type
TrueBrain Jan 23, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 139 additions & 3 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
YieldFromExpr, NamedTupleExpr, TypedDictExpr, SetComprehension,
DictionaryComprehension, ComplexExpr, EllipsisExpr, TypeAliasExpr,
RefExpr, YieldExpr, BackquoteExpr, ImportFrom, ImportAll, ImportBase,
AwaitExpr, PromoteExpr,
ARG_POS,
AwaitExpr, PromoteExpr, Node,
ARG_POS, MDEF,
CONTRAVARIANT, COVARIANT)
from mypy import nodes
from mypy.types import (
Expand All @@ -44,7 +44,8 @@
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, 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, expand_type_by_instance
from mypy.visitor import NodeVisitor
Expand Down Expand Up @@ -1102,6 +1103,12 @@ 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 isinstance(lvalue, NameExpr):
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:
# Try to infer a proper type for a variable with a partial None type.
Expand Down Expand Up @@ -1156,6 +1163,123 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
self.infer_variable_type(inferred, lvalue, self.accept(rvalue),
rvalue)

def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Type,
rvalue: Expression) -> bool:
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):

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
# 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.fullname() != "builtins.object":
continue

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, base_type: Type, base_node: Node) -> bool:
lvalue_node = lvalue.node
assert isinstance(lvalue_node, Var)

# Do not check whether the rvalue is compatible if the
# lvalue had a type defined; this is handled by other
# parts, and all we have to worry about in that case is
# that lvalue is compatible with the base class.
compare_node = None # 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,
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)

if base_var:
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):
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):
# 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, base_node

return None, None

def check_assignment_to_multiple_lvalues(self, lvalues: List[Lvalue], rvalue: Expression,
context: Context,
infer_lvalue_type: bool = True) -> None:
Expand Down Expand Up @@ -2835,6 +2959,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]]
Expand Down
2 changes: 1 addition & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from mypy.checkstrformat import StringFormatterChecker
from mypy.expandtype import expand_type, expand_type_by_instance
from mypy.util import split_module_names
from mypy.semanal import fill_typevars
from mypy.typevars import fill_typevars
from mypy.visitor import ExpressionVisitor

from mypy import experiments
Expand Down
2 changes: 1 addition & 1 deletion mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
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
from mypy import messages
from mypy import subtypes
MYPY = False
Expand Down
19 changes: 1 addition & 18 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,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
Expand All @@ -79,7 +80,6 @@
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 import join

Expand Down Expand Up @@ -3165,19 +3165,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:])
Expand Down Expand Up @@ -3549,7 +3536,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))
24 changes: 24 additions & 0 deletions mypy/typevars.py
Original file line number Diff line number Diff line change
@@ -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))
Loading