Skip to content
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

Generic type aliases #2378

Merged
merged 21 commits into from
Nov 3, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
29 changes: 7 additions & 22 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
TupleType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType,
PartialType, DeletedType, UnboundType, UninhabitedType, TypeType,
true_only, false_only, is_named_instance, function_type
true_only, false_only, is_named_instance, function_type,
get_typ_args, set_typ_args
Copy link
Member

Choose a reason for hiding this comment

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

Add trailing comma, remove following blank line.


)
from mypy.nodes import (
NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
Expand Down Expand Up @@ -1378,18 +1380,12 @@ def visit_type_application(self, tapp: TypeApplication) -> Type:
def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type:
""" Get type of a type alias (could be generic) in a runtime expression."""
Copy link
Member

Choose a reason for hiding this comment

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

No space before first word of sentence.

item = alias.type
if (isinstance(item, (Instance, TupleType, UnionType, CallableType))
and not alias.runtime):
if not alias.runtime:
item = self.replace_tvars_any(item)
if isinstance(item, Instance):
tp = type_object_type(item.type, self.named_type)
Copy link
Member

Choose a reason for hiding this comment

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

I'd like a comment here explaining that we'll generally get a callable type (or overloaded) with .is_type_obj() true, representing the class's constructor. This would explain that below we really just check for callable and overloaded.

else:
# TODO: Better error reporting: need to find line for
# unsubscribed generic aliases, that are invalid at runtime.
if alias.line > 0:
self.chk.fail('Invalid type alias in runtime expression: {}'
.format(item), alias)
return AnyType()
return alias.fback
if isinstance(tp, CallableType):
if len(tp.variables) != len(item.args):
self.msg.incompatible_type_application(len(tp.variables),
Expand All @@ -1411,11 +1407,7 @@ def replace_tvars_any(self, tp: Type) -> Type:
a runtime expression. Basically, this function finishes what could not be done
in similar funtion from typeanal.py.
Copy link
Member

Choose a reason for hiding this comment

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

Mind naming that similar function by name? (Also, typo -- funtion.)

Copy link
Member Author

Choose a reason for hiding this comment

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

@gvanrossum Yes, sure :-)

"""
if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)):
return tp
typ_args = (tp.args if isinstance(tp, Instance) else
tp.items if not isinstance(tp, CallableType) else
tp.arg_types + [tp.ret_type])
typ_args = get_typ_args(tp)
new_args = typ_args[:]
for i, arg in enumerate(typ_args):
if isinstance(arg, UnboundType):
Expand All @@ -1428,14 +1420,7 @@ def replace_tvars_any(self, tp: Type) -> Type:
new_args[i] = AnyType()
else:
new_args[i] = self.replace_tvars_any(arg)
if isinstance(tp, Instance):
return Instance(tp.type, new_args, tp.line)
if isinstance(tp, TupleType):
return tp.copy_modified(items=new_args)
if isinstance(tp, UnionType):
return UnionType.make_union(new_args, tp.line)
if isinstance(tp, CallableType):
return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1])
return set_typ_args(tp, new_args)

def visit_list_expr(self, e: ListExpr) -> Type:
"""Type check a list expression [...]."""
Expand Down
6 changes: 3 additions & 3 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1732,13 +1732,13 @@ class TypeAliasExpr(Expression):
"""Type alias expression (rvalue)."""

type = None # type: mypy.types.Type
line = None # type: int
fback = None # type: mypy.types.Type
Copy link
Member

Choose a reason for hiding this comment

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

Would be nice to have a comment that clarifies what "fback" means. (Apparently fallback? Why not spell it in full?)

Copy link
Member Author

Choose a reason for hiding this comment

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

@gvanrossum OK, changed to fallback and added comment.

runtime = False # type: bool
Copy link
Member

Choose a reason for hiding this comment

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

I'm beginning to wonder what 'runtime' really means. I notice you have 'runtime expression' in an error message. I think it's unclear what makes some expressions more "runtime" than other. Can you think of a better term?

Copy link
Member Author

Choose a reason for hiding this comment

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

@gvanrossum I changed this to in_runtime and added a detailed comment explaining that this marks things like Alias[T](42) in contrast to subscripting in type context x: Alias[T].


def __init__(self, type: 'mypy.types.Type', line: int = -1,
def __init__(self, type: 'mypy.types.Type', fback: 'mypy.types.Type' = None,
runtime: bool = False) -> None:
self.type = type
self.line = line
self.fback = fback
self.runtime = runtime

def accept(self, visitor: NodeVisitor[T]) -> T:
Expand Down
20 changes: 18 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
node.kind = TYPE_ALIAS
node.type_override = res
if isinstance(s.rvalue, IndexExpr):
s.rvalue.analyzed = TypeAliasExpr(res)
s.rvalue.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res))
if s.type:
# Store type into nodes.
for lvalue in s.lvalues:
Expand Down Expand Up @@ -1213,6 +1213,20 @@ def analyze_simple_literal_type(self, rvalue: Expression) -> Optional[Type]:
return self.named_type_or_none('builtins.unicode')
return None

def alias_fallback(self, tp: Type) -> Instance:
"""Make a dummy Instance with no methods. It is used as a fallback type
to detect errors for non-Instance aliases (i.e. Unions, Tuples, Callables).
"""

Copy link
Member

Choose a reason for hiding this comment

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

You don't need a blank line here (the stand-alone """ serves as enough of a visual separator).

kind = (' to Callable' if isinstance(tp, CallableType) else
' to Tuple' if isinstance(tp, TupleType) else
' to Union' if isinstance(tp, UnionType) else '')
cdef = ClassDef('Type alias{}'.format(kind), Block([]))
Copy link
Member

Choose a reason for hiding this comment

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

You can just use 'a' + b instead of 'a{}'.format(b). :-)

fb_info = TypeInfo(SymbolTable(), cdef, self.cur_mod_id)
fb_info.bases = [self.object_type()]
fb_info.mro = [fb_info, self.object_type().type]
return Instance(fb_info, [])

def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None:
"""Check if assignment creates a type alias and set it up as needed."""
# For now, type aliases only work at the top level of a module.
Expand Down Expand Up @@ -2370,7 +2384,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None:
self.lookup_qualified,
self.lookup_fully_qualified,
self.fail)
expr.analyzed = TypeAliasExpr(res, line=expr.line, runtime=True)
expr.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res), runtime=True)
elif refers_to_class_or_function(expr.base):
# Special form -- type application.
# Translate index to an unanalyzed type.
Expand Down Expand Up @@ -3061,6 +3075,8 @@ def visit_decorator(self, dec: Decorator) -> None:

def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
self.analyze(s.type)
if isinstance(s.rvalue, IndexExpr) and isinstance(s.rvalue.analyzed, TypeAliasExpr):
self.analyze(s.rvalue.analyzed.type)
super().visit_assignment_stmt(s)

def visit_cast_expr(self, e: CastExpr) -> None:
Expand Down
24 changes: 4 additions & 20 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from mypy.types import (
Type, UnboundType, TypeVarType, TupleType, UnionType, Instance,
AnyType, CallableType, Void, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor,
StarType, PartialType, EllipsisType, UninhabitedType, TypeType
StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args
Copy link
Member

Choose a reason for hiding this comment

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

Trailing comma.

)
from mypy.nodes import (
BOUND_TVAR, UNBOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED,
Expand Down Expand Up @@ -209,11 +209,7 @@ def get_type_var_names(self, tp: Type) -> List[str]:
in order of textual appearance (recursively, if needed).
"""
tvars = [] # type: List[str]
if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)):
return tvars
typ_args = (tp.args if isinstance(tp, Instance) else
tp.items if not isinstance(tp, CallableType) else
tp.arg_types + [tp.ret_type])
typ_args = get_typ_args(tp)
for arg in typ_args:
tvar = self.get_tvar_name(arg)
if tvar:
Expand Down Expand Up @@ -243,11 +239,7 @@ def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Ty
""" Replace type variables in a generic type alias tp with substitutions subs.
Length of subs should be already checked.
"""
if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)) or not subs:
return tp
typ_args = (tp.args if isinstance(tp, Instance) else
tp.items if not isinstance(tp, CallableType) else
tp.arg_types + [tp.ret_type])
typ_args = get_typ_args(tp)
new_args = typ_args[:]
for i, arg in enumerate(typ_args):
tvar = self.get_tvar_name(arg)
Expand All @@ -257,15 +249,7 @@ def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Ty
else:
# ...recursively, if needed.
new_args[i] = self.replace_alias_tvars(arg, vars, subs)
# Create a copy with type vars replaced.
if isinstance(tp, Instance):
return Instance(tp.type, new_args, tp.line)
if isinstance(tp, TupleType):
return tp.copy_modified(items=new_args)
if isinstance(tp, UnionType):
return UnionType.make_union(new_args, tp.line)
if isinstance(tp, CallableType):
return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1])
return set_typ_args(tp, new_args)

def visit_any(self, t: AnyType) -> Type:
return t
Expand Down
19 changes: 19 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1524,3 +1524,22 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike
name,
implicit=True,
)

def get_typ_args(tp: Type) -> List[Type]:
if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)):
return []
typ_args = (tp.args if isinstance(tp, Instance) else
tp.items if not isinstance(tp, CallableType) else
tp.arg_types + [tp.ret_type])
return typ_args

def set_typ_args(tp: Type, args: List[Type]) -> Type:
if isinstance(tp, Instance):
return Instance(tp.type, new_args, tp.line)
if isinstance(tp, TupleType):
return tp.copy_modified(items=new_args)
if isinstance(tp, UnionType):
return UnionType.make_union(new_args, tp.line)
if isinstance(tp, CallableType):
return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1])
return tp