-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Generic type aliases #2378
Changes from 1 commit
5dc0090
f6d862b
b0790d4
7ddd6ab
633e3d1
07baa51
56b7ae7
cbcb2c0
5c92b12
13e1fff
ee1c25c
2edcf48
64fc96e
8c64111
c106204
feb9413
d402a68
8cea156
382c53e
863c576
544ff66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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), | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mind naming that similar function by name? (Also, typo -- funtion.) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
|
@@ -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 [...].""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gvanrossum OK, changed to |
||
runtime = False # type: bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gvanrossum I changed this to |
||
|
||
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: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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). | ||
""" | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need a blank line here (the stand-alone |
||
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([])) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can just use |
||
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: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
@@ -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 | ||
|
There was a problem hiding this comment.
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.