From 64fc96ee7c7a57dae8047557d6a3f62ae9896529 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 1 Nov 2016 18:41:41 +0100 Subject: [PATCH] First part of response to comments (bigger things) --- mypy/checkexpr.py | 29 +++++++---------------------- mypy/nodes.py | 6 +++--- mypy/semanal.py | 20 ++++++++++++++++++-- mypy/typeanal.py | 24 ++++-------------------- mypy/types.py | 19 +++++++++++++++++++ 5 files changed, 51 insertions(+), 47 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index db9264bfe62e..35e4e5ccd17e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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 + ) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, @@ -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.""" 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) 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), @@ -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. """ - 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): @@ -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 [...].""" diff --git a/mypy/nodes.py b/mypy/nodes.py index 49f02c051a17..c71c3235b607 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -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 runtime = False # type: bool - 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: diff --git a/mypy/semanal.py b/mypy/semanal.py index f9b5ad9d02ef..9b62ebe46a0d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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: @@ -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). + """ + + 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([])) + 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. @@ -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. @@ -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: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 36156db41c4e..b3a70839a2c5 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -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 ) from mypy.nodes import ( BOUND_TVAR, UNBOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, @@ -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: @@ -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) @@ -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 diff --git a/mypy/types.py b/mypy/types.py index 9c80b590cd38..c6377ce656d6 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -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