Skip to content

Commit

Permalink
Review changes
Browse files Browse the repository at this point in the history
* Added tests
* Rename/refactor variables
* Fixed issue with type strings e.g. "int | str"
  • Loading branch information
cdce8p committed Nov 2, 2020
1 parent 533202e commit ba75620
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 27 deletions.
27 changes: 12 additions & 15 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
)
from mypy.types import (
Type, CallableType, AnyType, UnboundType, TupleType, TypeList, EllipsisType, CallableArgument,
TypeOfAny, Instance, RawExpressionType, ProperType,
UnionType, Pep604Syntax)
TypeOfAny, Instance, RawExpressionType, ProperType, UnionType,
)
from mypy import defaults
from mypy import message_registry, errorcodes as codes
from mypy.errors import Errors
Expand Down Expand Up @@ -241,8 +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, is_type_comment=True)
assume_str_is_unicode=assume_str_is_unicode,
is_type_comment=True).visit(typ.body)
return ignored, converted


Expand All @@ -269,6 +269,8 @@ def parse_type_string(expr_string: str, expr_fallback_name: str,
node.original_str_expr = expr_string
node.original_str_fallback = expr_fallback_name
return node
elif isinstance(node, UnionType):
return node
else:
return RawExpressionType(expr_string, expr_fallback_name, line, column)
except (SyntaxError, ValueError):
Expand Down Expand Up @@ -1277,12 +1279,14 @@ def __init__(self,
line: int = -1,
override_column: int = -1,
assume_str_is_unicode: bool = True,
is_type_comment: bool = False,
) -> None:
self.errors = errors
self.line = line
self.override_column = override_column
self.node_stack = [] # type: List[AST]
self.assume_str_is_unicode = assume_str_is_unicode
self.is_type_comment = is_type_comment

def convert_column(self, column: int) -> int:
"""Apply column override if defined; otherwise return column.
Expand Down Expand Up @@ -1319,13 +1323,7 @@ def visit(self, node: ast3.expr) -> ProperType: ...
@overload
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]:
def visit(self, node: Optional[AST]) -> Optional[ProperType]:
"""Modified visit -- keep track of the stack of nodes"""
if node is None:
return None
Expand All @@ -1334,8 +1332,6 @@ def visit(self, node: Optional[AST], is_type_comment: bool = False) -> Optional[
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 @@ -1431,7 +1427,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, is_type_comment: bool = False) -> Type:
def visit_BinOp(self, n: ast3.BinOp) -> Type:
if not isinstance(n.op, ast3.BitOr):
return self.invalid_type(n)

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

def visit_NameConstant(self, n: NameConstant) -> Type:
if isinstance(n.value, bool):
Expand Down
5 changes: 2 additions & 3 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,9 +605,8 @@ 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.pep604_syntax is not None
and t.pep604_syntax.uses_pep604_syntax is True
and t.pep604_syntax.is_type_comment is False
if (t.uses_pep604_syntax is True
and t.is_evaluated is True
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
14 changes: 6 additions & 8 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1719,23 +1719,21 @@ 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', 'pep604_syntax')
__slots__ = ('items', 'is_evaluated', 'uses_pep604_syntax')

def __init__(self, items: Sequence[Type], line: int = -1, column: int = -1,
pep604_syntax: Optional[Pep604Syntax] = None) -> None:
is_evaluated: bool = True, uses_pep604_syntax: bool = False) -> 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.pep604_syntax = pep604_syntax
# is_evaluated should be set to false for type comments and string literals
self.is_evaluated = is_evaluated
# uses_pep604_syntax is True if Union uses OR syntax (X | Y)
self.uses_pep604_syntax = uses_pep604_syntax

def __hash__(self) -> int:
return hash(frozenset(self.items))
Expand Down
34 changes: 33 additions & 1 deletion test-data/unit/check-union-or-syntax.test
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ def f(x: int | str) -> int | str:
reveal_type(f) # N: Revealed type is 'def (x: Union[builtins.int, builtins.str]) -> Union[builtins.int, builtins.str]'
[builtins fixtures/tuple.pyi]


[case testUnionOrSyntaxWithThreeBuiltinsTypes]
# flags: --python-version 3.10
def f(x: int | str | float) -> int | str | float:
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str, builtins.float]'
z: int | str | float = 0
reveal_type(z) # N: Revealed type is 'Union[builtins.int, builtins.str, builtins.float]'
return x

reveal_type(f) # N: Revealed type is 'def (x: Union[builtins.int, builtins.str, builtins.float]) -> Union[builtins.int, builtins.str, builtins.float]'


[case testUnionOrSyntaxWithTwoTypes]
# flags: --python-version 3.10
class A: pass
Expand All @@ -32,6 +33,7 @@ def f(x: A | B) -> A | B:
return x
reveal_type(f) # N: Revealed type is 'def (x: Union[__main__.A, __main__.B]) -> Union[__main__.A, __main__.B]'


[case testUnionOrSyntaxWithThreeTypes]
# flags: --python-version 3.10
class A: pass
Expand All @@ -44,36 +46,66 @@ def f(x: A | B | C) -> A | B | C:
return x
reveal_type(f) # N: Revealed type is 'def (x: Union[__main__.A, __main__.B, __main__.C]) -> Union[__main__.A, __main__.B, __main__.C]'


[case testUnionOrSyntaxWithLiteral]
# flags: --python-version 3.10
from typing_extensions import Literal
reveal_type(Literal[4] | str) # N: Revealed type is 'Any'
[builtins fixtures/tuple.pyi]


[case testUnionOrSyntaxWithBadOperator]
# flags: --python-version 3.10
x: 1 + 2 # E: Invalid type comment or annotation


[case testUnionOrSyntaxWithBadOperands]
# flags: --python-version 3.10
x: int | 42 # E: Invalid type: try using Literal[42] instead?
y: 42 | int # E: Invalid type: try using Literal[42] instead?
z: str | 42 | int # E: Invalid type: try using Literal[42] instead?


[case testUnionOrSyntaxWithGenerics]
# flags: --python-version 3.10
from typing import List
x: List[int | str]
reveal_type(x) # N: Revealed type is 'builtins.list[Union[builtins.int, builtins.str]]'
[builtins fixtures/list.pyi]


[case testUnionOrSyntaxWithQuotedTypes]
# flags: --python-version 3.10
from typing import Union
def f(x: 'Union[int, str, None]') -> 'Union[int, None]':
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str, None]'
return 42
reveal_type(f) # N: Revealed type is 'def (x: Union[builtins.int, builtins.str, None]) -> Union[builtins.int, None]'

# flags: --python-version 3.10
def g(x: "int | str | None") -> "int | None":
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str, None]'
return 42
reveal_type(g) # N: Revealed type is 'def (x: Union[builtins.int, builtins.str, None]) -> Union[builtins.int, None]'


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


[case testUnionOrSyntaxFutureImport]
# flags: --python-version 3.7
from __future__ import annotations
x: int | None
[builtins fixtures/tuple.pyi]


[case testUnionOrSyntaxMissingFutureImport]
# flags: --python-version 3.9
x: int | None # E: X | Y syntax for unions requires Python 3.10


[case testUnionOrSyntaxInStubFile]
# flags: --python-version 3.6
from lib import x
Expand Down

0 comments on commit ba75620

Please sign in to comment.