Skip to content

Commit

Permalink
Merge branch 'generic-func-subtyping2'
Browse files Browse the repository at this point in the history
  • Loading branch information
JukkaL committed Oct 25, 2014
2 parents c5b1166 + fd22ca2 commit f5bb183
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 212 deletions.
64 changes: 64 additions & 0 deletions mypy/applytype.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from typing import List, Dict

import mypy.subtypes
from mypy.expandtype import expand_type
from mypy.types import Type, Callable, AnyType
from mypy.messages import MessageBuilder
from mypy.nodes import Context


def apply_generic_arguments(callable: Callable, types: List[Type],
msg: MessageBuilder, context: Context) -> Type:
"""Apply generic type arguments to a callable type.
For example, applying [int] to 'def [T] (T) -> T' results in
'def [-1:int] (int) -> int'. Here '[-1:int]' is an implicit bound type
variable.
Note that each type can be None; in this case, it will not be applied.
"""
tvars = callable.variables
if len(tvars) != len(types):
msg.incompatible_type_application(len(tvars), len(types), context)
return AnyType()

# Check that inferred type variable values are compatible with allowed
# values. Also, promote subtype values to allowed values.
types = types[:]
for i, type in enumerate(types):
values = callable.variables[i].values
if values and type:
if isinstance(type, AnyType):
continue
for value in values:
if mypy.subtypes.is_subtype(type, value):
types[i] = value
break
else:
msg.incompatible_typevar_value(callable, i + 1, type, context)

# Create a map from type variable id to target type.
id_to_type = {} # type: Dict[int, Type]
for i, tv in enumerate(tvars):
if types[i]:
id_to_type[tv.id] = types[i]

# Apply arguments to argument types.
arg_types = [expand_type(at, id_to_type) for at in callable.arg_types]

bound_vars = [(tv.id, id_to_type[tv.id])
for tv in tvars
if tv.id in id_to_type]

# The callable may retain some type vars if only some were applied.
remaining_tvars = [tv for tv in tvars if tv.id not in id_to_type]

return Callable(arg_types,
callable.arg_kinds,
callable.arg_names,
expand_type(callable.ret_type, id_to_type),
callable.fallback,
callable.name,
remaining_tvars,
callable.bound_vars + bound_vars,
callable.line, callable.repr)
77 changes: 39 additions & 38 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@
import mypy.checkexpr
from mypy import messages
from mypy.subtypes import (
is_subtype, is_equivalent, map_instance_to_supertype, is_proper_subtype,
is_subtype, is_equivalent, is_proper_subtype,
is_more_precise, restrict_subtype_away
)
from mypy.maptype import map_instance_to_supertype
from mypy.semanal import self_type, set_callable_name, refers_to_fullname
from mypy.erasetype import erase_typevars
from mypy.expandtype import expand_type_by_instance, expand_type
Expand Down Expand Up @@ -914,7 +915,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> Type:
Handle all kinds of assignment statements (simple, indexed, multiple).
"""
self.check_assignment(s.lvalues[-1], s.rvalue, s.type == None)

if len(s.lvalues) > 1:
# Chained assignment (e.g. x = y = ...).
# Make sure that rvalue type will not be reinferred.
Expand All @@ -930,33 +931,33 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool =
elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr):
ltuple = cast(Union[TupleExpr, ListExpr], lvalue)
rvalue = self.remove_parens(rvalue)

self.check_assignment_to_multiple_lvalues(ltuple.items, rvalue, lvalue, infer_lvalue_type)
else:
lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue)

if lvalue_type:
rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue)

if rvalue_type and infer_lvalue_type:
self.binder.assign_type(lvalue, rvalue_type)
elif index_lvalue:
self.check_indexed_assignment(index_lvalue, rvalue, rvalue)

if inferred:
self.infer_variable_type(inferred, lvalue, self.accept(rvalue),
rvalue)

def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node, context: Context,
infer_lvalue_type: bool = True) -> None:
if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr):
# Recursively go into Tuple or List expression rhs instead of
# using the type of rhs, because this allowed more fine grained
# control in cases like: a, b = [int, str] where rhs would get
# type List[object]
rtuple = cast(Union[TupleExpr, ListExpr], rvalue)

rtuple = cast(Union[TupleExpr, ListExpr], rvalue)

if len(rtuple.items) != len(lvalues):
self.msg.incompatible_value_count_in_assignment(
len(lvalues), len(rtuple.items), context)
Expand All @@ -965,13 +966,13 @@ def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node
self.check_assignment(lv, rv, infer_lvalue_type)
else:
self.check_multi_assignment(lvalues, rvalue, context, infer_lvalue_type)

def remove_parens(self, node: Node) -> Node:
if isinstance(node, ParenExpr):
return self.remove_parens(node.expr)
else:
return node

def check_multi_assignment(self, lvalues: List[Node],
rvalue: Node,
context: Context,
Expand All @@ -980,75 +981,75 @@ def check_multi_assignment(self, lvalues: List[Node],
"""Check the assignment of one rvalue to a number of lvalues
for example from a ListExpr or TupleExpr.
"""

if not msg:
msg = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT

# First handle case where rvalue is of form Undefined, ...
rvalue_type = get_undefined_tuple(rvalue, self.named_type('builtins.tuple'))
undefined_rvalue = True
if not rvalue_type:
# Infer the type of an ordinary rvalue expression.
rvalue_type = self.accept(rvalue) # TODO maybe elsewhere; redundant
undefined_rvalue = False

if isinstance(rvalue_type, AnyType):
for lv in lvalues:
self.check_assignment(lv, self.temp_node(AnyType(), context), infer_lvalue_type)
elif isinstance(rvalue_type, TupleType):
self.check_multi_assignment_from_tuple(lvalues, rvalue, cast(TupleType, rvalue_type),
self.check_multi_assignment_from_tuple(lvalues, rvalue, cast(TupleType, rvalue_type),
context, undefined_rvalue, infer_lvalue_type)
else:
self.check_multi_assignment_from_iterable(lvalues, rvalue_type,
self.check_multi_assignment_from_iterable(lvalues, rvalue_type,
context, infer_lvalue_type)
def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node,
rvalue_type: TupleType, context: Context,

def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node,
rvalue_type: TupleType, context: Context,
undefined_rvalue: bool, infer_lvalue_type: bool = True) -> None:
if len(rvalue_type.items) != len(lvalues):
self.msg.wrong_number_values_to_unpack(len(rvalue_type.items), len(lvalues), context)
else:
if not undefined_rvalue:
# Create lvalue_type for type inference

type_parameters = [] # type: List[Type]
for i in range(len(lvalues)):
sub_lvalue_type, index_expr, inferred = self.check_lvalue(lvalues[i])

if sub_lvalue_type:
type_parameters.append(sub_lvalue_type)
else: # index lvalue
# TODO Figure out more precise type context, probably
# based on the type signature of the _set method.
type_parameters.append(rvalue_type.items[i])
type_parameters.append(rvalue_type.items[i])

lvalue_type = TupleType(type_parameters, self.named_type('builtins.tuple'))

# Infer rvalue again, now in the correct type context.
rvalue_type = cast(TupleType, self.accept(rvalue, lvalue_type))

for lv, rv_type in zip(lvalues, rvalue_type.items):
self.check_assignment(lv, self.temp_node(rv_type, context), infer_lvalue_type)

def type_is_iterable(self, type: Type) -> bool:
return (is_subtype(type, self.named_generic_type('typing.Iterable',
[AnyType()])) and
isinstance(type, Instance))
def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Type,

def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Type,
context: Context, infer_lvalue_type: bool = True) -> None:
if self.type_is_iterable(rvalue_type):
item_type = self.iterable_item_type(cast(Instance,rvalue_type))
for lv in lvalues:
self.check_assignment(lv, self.temp_node(item_type, context), infer_lvalue_type)
else:
else:
self.msg.type_not_iterable(rvalue_type, context)

def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]:
lvalue_type = None # type: Type
index_lvalue = None # type: IndexExpr
inferred = None # type: Var

if self.is_definition(lvalue):
if isinstance(lvalue, NameExpr):
inferred = cast(Var, lvalue.node)
Expand All @@ -1073,9 +1074,9 @@ def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]:
return self.check_lvalue(lvalue.expr)
else:
lvalue_type = self.accept(lvalue)

return lvalue_type, index_lvalue, inferred

def is_definition(self, s: Node) -> bool:
if isinstance(s, NameExpr):
if s.is_def:
Expand Down Expand Up @@ -1144,8 +1145,8 @@ def narrow_type_from_binder(self, expr: Node, known_type: Type) -> Type:
return ans
return known_type

def check_simple_assignment(self, lvalue_type: Type, rvalue: Node,
context: Node,
def check_simple_assignment(self, lvalue_type: Type, rvalue: Node,
context: Node,
msg: str = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT) -> Type:
"""Checks the assignment of rvalue to a lvalue of type lvalue_type"""
if refers_to_fullname(rvalue, 'typing.Undefined'):
Expand All @@ -1156,7 +1157,7 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Node,
else:
rvalue_type = self.accept(rvalue, lvalue_type)
self.check_subtype(rvalue_type, lvalue_type, context, msg,
'expression has type', 'variable has type')
'expression has type', 'variable has type')
return rvalue_type

def check_indexed_assignment(self, lvalue: IndexExpr,
Expand Down Expand Up @@ -1452,14 +1453,14 @@ def analyse_iterable_item_type(self, expr: Node) -> Type:
return echk.check_call(method, [], [], expr)[0]

def analyse_index_variables(self, index: List[Node],
item_type: Type, context: Context) -> None:
item_type: Type, context: Context) -> None:
"""Type check or infer for loop or list comprehension index vars."""
# Create a temporary copy of variables with Node item type.
# TODO this is ugly
node_index = [] # type: List[Node]
for i in index:
node_index.append(i)

if len(node_index) == 1:
self.check_assignment(node_index[0], self.temp_node(item_type, context))
else:
Expand Down
58 changes: 3 additions & 55 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from mypy import join
from mypy.expandtype import expand_type, expand_caller_var_args
from mypy.subtypes import is_subtype
from mypy import applytype
from mypy import erasetype
from mypy.checkmember import analyse_member_access, type_object_type
from mypy.semanal import self_type
Expand Down Expand Up @@ -639,61 +640,8 @@ def match_signature_types(self, arg_types: List[Type], is_var_arg: bool,

def apply_generic_arguments(self, callable: Callable, types: List[Type],
context: Context) -> Type:
"""Apply generic type arguments to a callable type.
For example, applying [int] to 'def [T] (T) -> T' results in
'def [-1:int] (int) -> int'. Here '[-1:int]' is an implicit bound type
variable.
Note that each type can be None; in this case, it will not be applied.
"""
tvars = callable.variables
if len(tvars) != len(types):
self.msg.incompatible_type_application(len(tvars), len(types),
context)
return AnyType()

# Check that inferred type variable values are compatible with allowed
# values. Also, promote subtype values to allowed values.
types = types[:]
for i, type in enumerate(types):
values = callable.variables[i].values
if values and type:
if isinstance(type, AnyType):
continue
for value in values:
if is_subtype(type, value):
types[i] = value
break
else:
self.msg.incompatible_typevar_value(
callable, i + 1, type, context)

# Create a map from type variable id to target type.
id_to_type = {} # type: Dict[int, Type]
for i, tv in enumerate(tvars):
if types[i]:
id_to_type[tv.id] = types[i]

# Apply arguments to argument types.
arg_types = [expand_type(at, id_to_type) for at in callable.arg_types]

bound_vars = [(tv.id, id_to_type[tv.id])
for tv in tvars
if tv.id in id_to_type]

# The callable may retain some type vars if only some were applied.
remaining_tvars = [tv for tv in tvars if tv.id not in id_to_type]

return Callable(arg_types,
callable.arg_kinds,
callable.arg_names,
expand_type(callable.ret_type, id_to_type),
callable.fallback,
callable.name,
remaining_tvars,
callable.bound_vars + bound_vars,
callable.line, callable.repr)
"""Simple wrapper around mypy.applytype.apply_generic_arguments."""
return applytype.apply_generic_arguments(callable, types, self.msg, context)

def apply_generic_arguments2(self, overload: Overloaded, types: List[Type],
context: Context) -> Type:
Expand Down
2 changes: 1 addition & 1 deletion mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context
from mypy.nodes import ARG_POS, function_type, Decorator
from mypy.messages import MessageBuilder
from mypy.subtypes import map_instance_to_supertype
from mypy.maptype import map_instance_to_supertype
from mypy.expandtype import expand_type_by_instance
from mypy.nodes import method_type
from mypy.semanal import self_type
Expand Down
8 changes: 4 additions & 4 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

from mypy.types import (
Callable, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVar,
Instance, TupleType, UnionType, Overloaded, ErasedType
Instance, TupleType, UnionType, Overloaded, ErasedType, is_named_instance
)
from mypy.expandtype import expand_caller_var_args
from mypy.subtypes import map_instance_to_supertype, is_named_instance
from mypy.maptype import map_instance_to_supertype
from mypy import nodes


SUBTYPE_OF = 0
SUPERTYPE_OF = 1
SUBTYPE_OF = 0 # type: int
SUPERTYPE_OF = 1 # type: int


class Constraint:
Expand Down
3 changes: 2 additions & 1 deletion mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
ErrorType, TypeVar, Callable, TupleType, ErasedType, TypeList,
UnionType, FunctionLike
)
from mypy.subtypes import is_subtype, is_equivalent, map_instance_to_supertype
from mypy.maptype import map_instance_to_supertype
from mypy.subtypes import is_subtype, is_equivalent


def join_simple(declaration: Type, s: Type, t: Type) -> Type:
Expand Down
Loading

0 comments on commit f5bb183

Please sign in to comment.