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
6 changes: 3 additions & 3 deletions docs/source/kinds_of_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,9 @@ Following previous examples:

A type alias does not create a new type. It's just a shorthand notation for
another type -- it's equivalent to the target type. For generic type aliases
this means that variance or constraints of type variables used for alias
definition don't apply to aliases. Parameterized generic alias is treated
simply as an original type with corresponding type variables substituted.
this means that variance of type variables used for alias definition does not
apply to aliases. Parameterized generic alias is treated simply as an original
type with corresponding type variables substituted.

.. _newtypes:

Expand Down
16 changes: 9 additions & 7 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,15 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
act_len = len(an_args)
if exp_len > 0 and act_len == 0:
# Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...]
return self.replace_alias_tvars(override, all_vars, [AnyType()] * exp_len)
return self.replace_alias_tvars(override, all_vars, [AnyType()] * exp_len,
t.line, t.column)
if exp_len == 0 and act_len == 0:
return override
if act_len != exp_len:
self.fail('Bad number of arguments for type alias, expected: %s, given: %s'
% (exp_len, act_len), t)
return t
return self.replace_alias_tvars(override, all_vars, an_args)
return self.replace_alias_tvars(override, all_vars, an_args, t.line, t.column)
elif not isinstance(sym.node, TypeInfo):
name = sym.fullname
if name is None:
Expand Down Expand Up @@ -233,9 +234,10 @@ def get_tvar_name(self, t: Type) -> Optional[str]:
return t.name
return None

def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Type:
"""Replace type variables in a generic type alias tp with substitutions subs.
Length of subs should be already checked.
def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type],
newline: int, newcolumn: int) -> Type:
"""Replace type variables in a generic type alias tp with substitutions subs
resetting context. Length of subs should be already checked.
"""
typ_args = get_typ_args(tp)
new_args = typ_args[:]
Expand All @@ -246,8 +248,8 @@ def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Ty
new_args[i] = subs[vars.index(tvar)]
else:
# ...recursively, if needed.
new_args[i] = self.replace_alias_tvars(arg, vars, subs)
return set_typ_args(tp, new_args)
new_args[i] = self.replace_alias_tvars(arg, vars, subs, newline, newcolumn)
return set_typ_args(tp, new_args, newline, newcolumn)

def visit_any(self, t: AnyType) -> Type:
return t
Expand Down
11 changes: 7 additions & 4 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1536,14 +1536,17 @@ def get_typ_args(tp: Type) -> List[Type]:
return typ_args


def set_typ_args(tp: Type, new_args: List[Type]) -> Type:
def set_typ_args(tp: Type, new_args: List[Type], line: int = -1, column: int = -1) -> Type:
"""Return a copy of a parameterizable Type with arguments set to new_args."""
line = line if line > 0 else tp.line
column = column if column > 0 else tp.column
if isinstance(tp, Instance):
return Instance(tp.type, new_args, tp.line, tp.column)
return Instance(tp.type, new_args, line, column)
if isinstance(tp, TupleType):
return tp.copy_modified(items=new_args)
if isinstance(tp, UnionType):
return UnionType.make_simplified_union(new_args, tp.line, tp.column)
return UnionType.make_simplified_union(new_args, line, column)
if isinstance(tp, CallableType):
return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1])
return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1],
line=line, column=column)
return tp
10 changes: 7 additions & 3 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ E = Node[Node[T, T], List[T]]
F = Node[List[T, T], S] # E: "list" expects 1 type argument, but 2 given
G = Callable[..., List[T, T]] # E: "list" expects 1 type argument, but 2 given
H = Union[int, Tuple[T, Node[T]]] # E: "Node" expects 2 type arguments, but 1 given
h = None # type: H
h = None # type: H # E: "Node" expects 2 type arguments, but 1 given
h1 = None # type: H[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2

x = None # type: D[int, str]
Expand Down Expand Up @@ -945,13 +945,17 @@ S = TypeVar('S', int, list)
class A(Generic[T, S]):
def __init__(self, x: T, y: S) -> None: ...

BadA = A[str, T] # E: Bad ...
BadA = A[str, T] # This error is reported twice (but it actually looks useful)
SameA = A[T, T]

x = None # type: SameA[int]
y = None # type: SameA[str] # E: Bad ...
y = None # type: SameA[str] # E: Invalid type argument value for "A"

[builtins fixtures/list.pyi]
[out]
main:8: error: Invalid type argument value for "A"
main:8: error: Type argument 1 of "A" has incompatible value "str"
Copy link
Member

Choose a reason for hiding this comment

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

I don't like getting this twice. The first error is too vague since it doesn't tell you which position has the error.

(Unless you turn on column numbers? Which reminds me, it would be nice to turn on --show-column-numbers for at least some of the tests, to verify that they're being passed along.)

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, I updated the error messages, only the more detailed one is shown wherever possible.

Concerning the column numbers I discovered that a lot of code does not copy them. For example, exprtotype.py was simply throwing them away (I fixed this) while this file is the key, if column for a type is ignored here it is lost forever. I investigated this more and I found that this is a limitation of old parser and type comments. I don't know how to fix this (and think it will be difficult), but for fast parser and type annotations it is just one line and we get column for free.

I used --show-column-numbers in two tests (one with old parser and one with fast parser). And indeed fast parser works better. So this is another argument to make the fast parser default.



-- Multiple assignment with lists
-- ------------------------------
Expand Down