Skip to content
Merged
Prev Previous commit
Next Next commit
types: change type of CallableType.variables
Creates a number of TODOs that I shall get to.
  • Loading branch information
hauntsaninja
hauntsaninja committed Sep 6, 2020
commit 335fdf614354c900a4248f3c0cb08b420e20d53c
9 changes: 4 additions & 5 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1389,15 +1389,14 @@ def expand_typevars(self, defn: FuncItem,
typ: CallableType) -> List[Tuple[FuncItem, CallableType]]:
# TODO use generator
subst = [] # type: List[List[Tuple[TypeVarId, Type]]]
tvars = typ.variables or []
tvars = tvars[:]
tvars = list(typ.variables) or []
if defn.info:
# Class type variables
tvars += defn.info.defn.type_vars or []
# TODO(shantanu): audit for paramspec
for tvar in tvars:
if tvar.values:
subst.append([(tvar.id, value)
for value in tvar.values])
if isinstance(tvar, TypeVarDef) and tvar.values:
subst.append([(tvar.id, value) for value in tvar.values])
# Make a copy of the function to check for each combination of
# value restricted type variables. (Except when running mypyc,
# where we need one canonical version of the function.)
Expand Down
2 changes: 2 additions & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4311,6 +4311,8 @@ def merge_typevars_in_callables_by_name(
for tvdef in target.variables:
name = tvdef.fullname
if name not in unique_typevars:
# TODO(shantanu): fix for ParamSpecDef
assert isinstance(tvdef, TypeVarDef)
unique_typevars[name] = TypeVarType(tvdef)
variables.append(tvdef)
rename[tvdef.id] = unique_typevars[name]
Expand Down
12 changes: 6 additions & 6 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Type checking of attribute access"""

from typing import cast, Callable, Optional, Union, List
from typing import cast, Callable, Optional, Union, Sequence
from typing_extensions import TYPE_CHECKING

from mypy.types import (
Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef,
Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType,
Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike,
TypeVarLikeDef, Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType,
DeletedType, NoneType, TypeType, has_type_vars, get_proper_type, ProperType
)
from mypy.nodes import (
Expand Down Expand Up @@ -676,7 +676,7 @@ def analyze_class_attribute_access(itype: Instance,
name: str,
mx: MemberContext,
override_info: Optional[TypeInfo] = None,
original_vars: Optional[List[TypeVarDef]] = None
original_vars: Optional[Sequence[TypeVarLikeDef]] = None
) -> Optional[Type]:
"""Analyze access to an attribute on a class object.

Expand Down Expand Up @@ -839,7 +839,7 @@ def analyze_enum_class_attribute_access(itype: Instance,
def add_class_tvars(t: ProperType, isuper: Optional[Instance],
is_classmethod: bool,
original_type: Type,
original_vars: Optional[List[TypeVarDef]] = None) -> Type:
original_vars: Optional[Sequence[TypeVarLikeDef]] = None) -> Type:
"""Instantiate type variables during analyze_class_attribute_access,
e.g T and Q in the following:

Expand Down Expand Up @@ -883,7 +883,7 @@ class B(A[str]): pass
assert isuper is not None
t = cast(CallableType, expand_type_by_instance(t, isuper))
freeze_type_vars(t)
return t.copy_modified(variables=tvars + t.variables)
return t.copy_modified(variables=list(tvars) + list(t.variables))
elif isinstance(t, Overloaded):
return Overloaded([cast(CallableType, add_class_tvars(item, isuper,
is_classmethod, original_type,
Expand Down
2 changes: 2 additions & 0 deletions mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def freshen_function_type_vars(callee: F) -> F:
tvdefs = []
tvmap = {} # type: Dict[TypeVarId, Type]
for v in callee.variables:
# TODO(shantanu): fix for ParamSpecDef
assert isinstance(v, TypeVarDef)
tvdef = TypeVarDef.new_unification_variable(v)
tvdefs.append(tvdef)
tvmap[v.id] = TypeVarType(tvdef)
Expand Down
12 changes: 7 additions & 5 deletions mypy/fixup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from mypy.types import (
CallableType, Instance, Overloaded, TupleType, TypedDictType,
TypeVarType, UnboundType, UnionType, TypeVisitor, LiteralType,
TypeType, NOT_READY, TypeAliasType, AnyType, TypeOfAny)
TypeType, NOT_READY, TypeAliasType, AnyType, TypeOfAny, TypeVarDef
)
from mypy.visitor import NodeVisitor
from mypy.lookup import lookup_fully_qualified

Expand Down Expand Up @@ -183,10 +184,11 @@ def visit_callable_type(self, ct: CallableType) -> None:
if ct.ret_type is not None:
ct.ret_type.accept(self)
for v in ct.variables:
if v.values:
for val in v.values:
val.accept(self)
v.upper_bound.accept(self)
if isinstance(v, TypeVarDef):
if v.values:
for val in v.values:
val.accept(self)
v.upper_bound.accept(self)
for arg in ct.bound_args:
if arg:
arg.accept(self)
Expand Down
24 changes: 14 additions & 10 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from mypy.errors import Errors
from mypy.types import (
Type, CallableType, Instance, TypeVarType, TupleType, TypedDictType, LiteralType,
UnionType, NoneType, AnyType, Overloaded, FunctionLike, DeletedType, TypeType,
UnionType, NoneType, AnyType, Overloaded, FunctionLike, DeletedType, TypeType, TypeVarDef,
UninhabitedType, TypeOfAny, UnboundType, PartialType, get_proper_type, ProperType,
get_proper_types
)
Expand Down Expand Up @@ -1862,16 +1862,20 @@ def [T <: int] f(self, x: int, y: T) -> None
if tp.variables:
tvars = []
for tvar in tp.variables:
upper_bound = get_proper_type(tvar.upper_bound)
if (isinstance(upper_bound, Instance) and
upper_bound.type.fullname != 'builtins.object'):
tvars.append('{} <: {}'.format(tvar.name, format_type_bare(upper_bound)))
elif tvar.values:
tvars.append('{} in ({})'
.format(tvar.name, ', '.join([format_type_bare(tp)
for tp in tvar.values])))
if isinstance(tvar, TypeVarDef):
upper_bound = get_proper_type(tvar.upper_bound)
if (isinstance(upper_bound, Instance) and
upper_bound.type.fullname != 'builtins.object'):
tvars.append('{} <: {}'.format(tvar.name, format_type_bare(upper_bound)))
elif tvar.values:
tvars.append('{} in ({})'
.format(tvar.name, ', '.join([format_type_bare(tp)
for tp in tvar.values])))
else:
tvars.append(tvar.name)
else:
tvars.append(tvar.name)
# For other TypeVarLikeDefs, just use the repr
tvars.append(repr(tvar))
s = '[{}] {}'.format(', '.join(tvars), s)
return 'def {}'.format(s)

Expand Down
4 changes: 3 additions & 1 deletion mypy/plugins/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from mypy.plugins.common import try_getting_str_literals
from mypy.types import (
Type, Instance, AnyType, TypeOfAny, CallableType, NoneType, TypedDictType,
TypeVarType, TPDICT_FB_NAMES, get_proper_type, LiteralType
TypeVarDef, TypeVarType, TPDICT_FB_NAMES, get_proper_type, LiteralType
)
from mypy.subtypes import is_subtype
from mypy.typeops import make_simplified_union
Expand Down Expand Up @@ -204,6 +204,7 @@ def typed_dict_get_signature_callback(ctx: MethodSigContext) -> CallableType:
# Tweak the signature to include the value type as context. It's
# only needed for type inference since there's a union with a type
# variable that accepts everything.
assert isinstance(signature.variables[0], TypeVarDef)
tv = TypeVarType(signature.variables[0])
return signature.copy_modified(
arg_types=[signature.arg_types[0],
Expand Down Expand Up @@ -269,6 +270,7 @@ def typed_dict_pop_signature_callback(ctx: MethodSigContext) -> CallableType:
# Tweak the signature to include the value type as context. It's
# only needed for type inference since there's a union with a type
# variable that accepts everything.
assert isinstance(signature.variables[0], TypeVarDef)
tv = TypeVarType(signature.variables[0])
typ = make_simplified_union([value_type, tv])
return signature.copy_modified(
Expand Down
7 changes: 4 additions & 3 deletions mypy/server/astmerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,10 @@ def visit_callable_type(self, typ: CallableType) -> None:
if typ.fallback is not None:
typ.fallback.accept(self)
for tv in typ.variables:
tv.upper_bound.accept(self)
for value in tv.values:
value.accept(self)
if isinstance(tv, TypeVarDef):
tv.upper_bound.accept(self)
for value in tv.values:
value.accept(self)

def visit_overloaded(self, t: Overloaded) -> None:
for item in t.items():
Expand Down
6 changes: 3 additions & 3 deletions mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

from abc import abstractmethod
from mypy.ordered_dict import OrderedDict
from typing import Generic, TypeVar, cast, Any, List, Callable, Iterable, Optional, Set
from typing import Generic, TypeVar, cast, Any, List, Callable, Iterable, Optional, Set, Sequence
from mypy_extensions import trait

T = TypeVar('T')

from mypy.types import (
Type, AnyType, CallableType, Overloaded, TupleType, TypedDictType, LiteralType,
RawExpressionType, Instance, NoneType, TypeType,
UnionType, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef,
UnionType, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarLikeDef,
UnboundType, ErasedType, StarType, EllipsisType, TypeList, CallableArgument,
PlaceholderType, TypeAliasType, get_proper_type
)
Expand Down Expand Up @@ -218,7 +218,7 @@ def translate_types(self, types: Iterable[Type]) -> List[Type]:
return [t.accept(self) for t in types]

def translate_variables(self,
variables: List[TypeVarDef]) -> List[TypeVarDef]:
variables: Sequence[TypeVarLikeDef]) -> Sequence[TypeVarLikeDef]:
return variables

def visit_overloaded(self, t: Overloaded) -> Type:
Expand Down
37 changes: 22 additions & 15 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor,
StarType, PartialType, EllipsisType, UninhabitedType, TypeType,
CallableArgument, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType,
PlaceholderType, Overloaded, get_proper_type, TypeAliasType
PlaceholderType, Overloaded, get_proper_type, TypeAliasType, TypeVarLikeDef
)

from mypy.nodes import (
Expand Down Expand Up @@ -205,6 +205,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
' to define generic alias'.format(t.name), t)
return AnyType(TypeOfAny.from_error)
if isinstance(sym.node, TypeVarExpr) and tvar_def is not None:
assert isinstance(tvar_def, TypeVarDef)
if len(t.args) > 0:
self.fail('Type variable "{}" used with arguments'.format(t.name), t)
return TypeVarType(tvar_def, t.line)
Expand Down Expand Up @@ -813,8 +814,9 @@ def infer_type_variables(self,
tvars.append(tvar_expr)
return list(zip(names, tvars))

def bind_function_type_variables(self,
fun_type: CallableType, defn: Context) -> List[TypeVarDef]:
def bind_function_type_variables(
self, fun_type: CallableType, defn: Context
) -> Sequence[TypeVarLikeDef]:
"""Find the type variables of the function type and bind them in our tvar_scope"""
if fun_type.variables:
for var in fun_type.variables:
Expand All @@ -828,7 +830,7 @@ def bind_function_type_variables(self,
# Do not define a new type variable if already defined in scope.
typevars = [(name, tvar) for name, tvar in typevars
if not self.is_defined_type_var(name, defn)]
defs = [] # type: List[TypeVarDef]
defs = [] # type: List[TypeVarLikeDef]
for name, tvar in typevars:
if not self.tvar_scope.allow_binding(tvar.fullname):
self.fail("Type variable '{}' is bound by an outer class".format(name), defn)
Expand Down Expand Up @@ -860,17 +862,22 @@ def anal_type(self, t: Type, nested: bool = True) -> Type:
if nested:
self.nesting_level -= 1

def anal_var_defs(self, var_defs: List[TypeVarDef]) -> List[TypeVarDef]:
a = [] # type: List[TypeVarDef]
for vd in var_defs:
a.append(TypeVarDef(vd.name,
vd.fullname,
vd.id.raw_id,
self.anal_array(vd.values),
vd.upper_bound.accept(self),
vd.variance,
vd.line))
return a
def anal_var_def(self, var_def: TypeVarLikeDef) -> TypeVarLikeDef:
if isinstance(var_def, TypeVarDef):
return TypeVarDef(
var_def.name,
var_def.fullname,
var_def.id.raw_id,
self.anal_array(var_def.values),
var_def.upper_bound.accept(self),
var_def.variance,
var_def.line
)
else:
return var_def

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we construct a new instance and set the full name, at least?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, TypeAnalyser visit methods seem pretty happy to return their input, so not sure I see the point (unless we were to add upper_bound to ParamSpecDef or TypeVarLikeDef, in which case we'd need to visit it and reconstruct).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused -- no change is needed here.


def anal_var_defs(self, var_defs: Sequence[TypeVarLikeDef]) -> List[TypeVarLikeDef]:
return [self.anal_var_def(vd) for vd in var_defs]

def named_type_with_normalized_str(self, fully_qualified_name: str) -> Instance:
"""Does almost the same thing as `named_type`, except that we immediately
Expand Down
10 changes: 7 additions & 3 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import sys

from mypy.types import (
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded,
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, TypeVarLikeDef, Overloaded,
TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType, TypedDictType,
AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types,
copy_type, TypeAliasType, TypeQuery
Expand Down Expand Up @@ -113,7 +113,7 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance,
special_sig: Optional[str],
is_new: bool, orig_self_type: Optional[Type] = None) -> CallableType:
"""Create a type object type based on the signature of __init__."""
variables = [] # type: List[TypeVarDef]
variables = [] # type: List[TypeVarLikeDef]
variables.extend(info.defn.type_vars)
variables.extend(init_type.variables)

Expand Down Expand Up @@ -227,6 +227,8 @@ class B(A): pass
# TODO: infer bounds on the type of *args?
return cast(F, func)
self_param_type = get_proper_type(func.arg_types[0])

variables = [] # type: Sequence[TypeVarLikeDef]
if func.variables and supported_self_type(self_param_type):
if original_type is None:
# TODO: type check method override (see #7861).
Expand Down Expand Up @@ -472,7 +474,9 @@ def true_or_false(t: Type) -> ProperType:
return new_t


def erase_def_to_union_or_bound(tdef: TypeVarDef) -> Type:
def erase_def_to_union_or_bound(tdef: TypeVarLikeDef) -> Type:
# TODO(shantanu): fix for ParamSpecDef
assert isinstance(tdef, TypeVarDef)
if tdef.values:
return make_simplified_union(tdef.values)
else:
Expand Down
22 changes: 13 additions & 9 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,7 @@ def __init__(self,
fallback: Instance,
name: Optional[str] = None,
definition: Optional[SymbolNode] = None,
variables: Optional[List[TypeVarDef]] = None,
variables: Optional[Sequence[TypeVarLikeDef]] = None,
line: int = -1,
column: int = -1,
is_ellipsis_args: bool = False,
Expand Down Expand Up @@ -1067,7 +1067,7 @@ def copy_modified(self,
fallback: Bogus[Instance] = _dummy,
name: Bogus[Optional[str]] = _dummy,
definition: Bogus[SymbolNode] = _dummy,
variables: Bogus[List[TypeVarDef]] = _dummy,
variables: Bogus[Sequence[TypeVarLikeDef]] = _dummy,
line: Bogus[int] = _dummy,
column: Bogus[int] = _dummy,
is_ellipsis_args: Bogus[bool] = _dummy,
Expand Down Expand Up @@ -2096,15 +2096,19 @@ def visit_callable_type(self, t: CallableType) -> str:

if t.variables:
vs = []
# We reimplement TypeVarDef.__repr__ here in order to support id_mapper.
for var in t.variables:
if var.values:
vals = '({})'.format(', '.join(val.accept(self) for val in var.values))
vs.append('{} in {}'.format(var.name, vals))
elif not is_named_instance(var.upper_bound, 'builtins.object'):
vs.append('{} <: {}'.format(var.name, var.upper_bound.accept(self)))
if isinstance(var, TypeVarDef):
# We reimplement TypeVarDef.__repr__ here in order to support id_mapper.
if var.values:
vals = '({})'.format(', '.join(val.accept(self) for val in var.values))
vs.append('{} in {}'.format(var.name, vals))
elif not is_named_instance(var.upper_bound, 'builtins.object'):
vs.append('{} <: {}'.format(var.name, var.upper_bound.accept(self)))
else:
vs.append(var.name)
else:
vs.append(var.name)
# For other TypeVarLikeDefs, just use the repr
vs.append(repr(var))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a TODO comment about actually implementing suitable repr? In the longer term, it would make sense to use a similar way of constructing the repr of both cases.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeVarLikeDef already implements a suitable repr.
The code here is just a reimplementation of TypeVarDef's repr that additionally visits values / upper_bound. We'd only need to do something here if we added upper_bound to ParamSpecDef or TypeVarLikeDef.

s = '{} {}'.format('[{}]'.format(', '.join(vs)), s)

return 'def {}'.format(s)
Expand Down