Skip to content

Add support for union types as X | Y (PEP 604) #9647

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

Merged
merged 10 commits into from
Nov 14, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Fixed check for type comments
  • Loading branch information
cdce8p committed Nov 1, 2020
commit 533202eabb3c256969890799da54ea3dcb3829b7
19 changes: 14 additions & 5 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from mypy.types import (
Type, CallableType, AnyType, UnboundType, TupleType, TypeList, EllipsisType, CallableArgument,
TypeOfAny, Instance, RawExpressionType, ProperType,
UnionType)
UnionType, Pep604Syntax)
from mypy import defaults
from mypy import message_registry, errorcodes as codes
from mypy.errors import Errors
Expand Down Expand Up @@ -241,7 +241,8 @@ def parse_type_comment(type_comment: str,
converted = TypeConverter(errors,
line=line,
override_column=column,
assume_str_is_unicode=assume_str_is_unicode).visit(typ.body)
assume_str_is_unicode=assume_str_is_unicode
).visit(typ.body, is_type_comment=True)
return ignored, converted


Expand Down Expand Up @@ -1318,7 +1319,13 @@ def visit(self, node: ast3.expr) -> ProperType: ...
@overload
def visit(self, node: Optional[AST]) -> Optional[ProperType]: ...

def visit(self, node: Optional[AST]) -> Optional[ProperType]:
@overload
def visit(self, node: ast3.expr, is_type_comment: bool) -> ProperType: ...

@overload
def visit(self, node: Optional[AST], is_type_comment: bool) -> Optional[ProperType]: ...

def visit(self, node: Optional[AST], is_type_comment: bool = False) -> Optional[ProperType]:
"""Modified visit -- keep track of the stack of nodes"""
if node is None:
return None
Expand All @@ -1327,6 +1334,8 @@ def visit(self, node: Optional[AST]) -> Optional[ProperType]:
method = 'visit_' + node.__class__.__name__
visitor = getattr(self, method, None)
if visitor is not None:
if visitor == self.visit_BinOp:
return visitor(node, is_type_comment)
return visitor(node)
else:
return self.invalid_type(node)
Expand Down Expand Up @@ -1422,7 +1431,7 @@ def _extract_argument_name(self, n: ast3.expr) -> Optional[str]:
def visit_Name(self, n: Name) -> Type:
return UnboundType(n.id, line=self.line, column=self.convert_column(n.col_offset))

def visit_BinOp(self, n: ast3.BinOp) -> Type:
def visit_BinOp(self, n: ast3.BinOp, is_type_comment: bool = False) -> Type:
if not isinstance(n.op, ast3.BitOr):
return self.invalid_type(n)

Expand All @@ -1431,7 +1440,7 @@ def visit_BinOp(self, n: ast3.BinOp) -> Type:
return UnionType([left, right],
line=self.line,
column=self.convert_column(n.col_offset),
uses_pep604_syntax=True)
pep604_syntax=Pep604Syntax(True, is_type_comment))

def visit_NameConstant(self, n: NameConstant) -> Type:
if isinstance(n.value, bool):
Expand Down
4 changes: 3 additions & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,9 @@ def visit_star_type(self, t: StarType) -> Type:
return StarType(self.anal_type(t.type), t.line)

def visit_union_type(self, t: UnionType) -> Type:
if (t.uses_pep604_syntax is True
if (t.pep604_syntax is not None
and t.pep604_syntax.uses_pep604_syntax is True
and t.pep604_syntax.is_type_comment is False
and self.api.is_stub_file is False
and self.options.python_version < (3, 10)
and self.api.is_future_flag_set('annotations') is False):
Expand Down
11 changes: 8 additions & 3 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1719,18 +1719,23 @@ def serialize(self) -> JsonDict:
assert False, "Synthetic types don't serialize"


Pep604Syntax = NamedTuple('Pep604Syntax', [
('uses_pep604_syntax', bool),
('is_type_comment', bool)])


class UnionType(ProperType):
"""The union type Union[T1, ..., Tn] (at least one type argument)."""

__slots__ = ('items', 'uses_pep604_syntax')
__slots__ = ('items', 'pep604_syntax')

def __init__(self, items: Sequence[Type], line: int = -1, column: int = -1,
uses_pep604_syntax: bool = False) -> None:
pep604_syntax: Optional[Pep604Syntax] = None) -> None:
super().__init__(line, column)
self.items = flatten_nested_unions(items)
self.can_be_true = any(item.can_be_true for item in items)
self.can_be_false = any(item.can_be_false for item in items)
self.uses_pep604_syntax = uses_pep604_syntax
self.pep604_syntax = pep604_syntax

def __hash__(self) -> int:
return hash(frozenset(self.items))
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-union-or-syntax.test
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ y: 42 | int # E: Invalid type: try using Literal[42] instead?
z: str | 42 | int # E: Invalid type: try using Literal[42] instead?

[case testUnionOrSyntaxInComment]
# flags: --python-version 3.10
# flags: --python-version 3.6
x = 1 # type: int | str

[case testUnionOrSyntaxFutureImport]
Expand Down