diff --git a/mypy/checker.py b/mypy/checker.py index 570ae7e2d2a0..166ecbd1c7b0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -18,7 +18,7 @@ Decorator, SetExpr, PassStmt, TypeVarExpr, UndefinedExpr, PrintStmt, LITERAL_TYPE, BreakStmt, ContinueStmt, ComparisonExpr, StarExpr, YieldFromExpr, YieldFromStmt, NamedTupleExpr, SetComprehension, - DictionaryComprehension, ComplexExpr + DictionaryComprehension, ComplexExpr, EllipsisNode ) from mypy.nodes import function_type, method_type from mypy import nodes @@ -1722,6 +1722,9 @@ def visit_float_expr(self, e: FloatExpr) -> Type: def visit_complex_expr(self, e: ComplexExpr) -> Type: return self.expr_checker.visit_complex_expr(e) + def visit_ellipsis(self, e: EllipsisNode) -> Type: + return self.expr_checker.visit_ellipsis(e) + def visit_op_expr(self, e: OpExpr) -> Type: return self.expr_checker.visit_op_expr(e) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d9c76487ccc3..de10bcdc2661 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -13,7 +13,7 @@ TupleExpr, DictExpr, FuncExpr, SuperExpr, ParenExpr, SliceExpr, Context, ListComprehension, GeneratorExpr, SetExpr, MypyFile, Decorator, UndefinedExpr, ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, - DictionaryComprehension, ComplexExpr, LITERAL_TYPE + DictionaryComprehension, ComplexExpr, EllipsisNode, LITERAL_TYPE ) from mypy.errors import Errors from mypy.nodes import function_type, method_type @@ -708,6 +708,10 @@ def visit_complex_expr(self, e: ComplexExpr) -> Type: """Type check a complex literal.""" return self.named_type('builtins.complex') + def visit_ellipsis(self, e: EllipsisNode) -> Type: + """Type check ...""" + return self.named_type('builtins.ellipsis') + def visit_op_expr(self, e: OpExpr) -> Type: """Type check a binary operator expression.""" if e.op == 'and' or e.op == 'or': diff --git a/mypy/lex.py b/mypy/lex.py index 17fdb0f261f4..0c2e9e9201bb 100644 --- a/mypy/lex.py +++ b/mypy/lex.py @@ -117,6 +117,10 @@ class Colon(Token): pass +class EllipsisToken(Token): + pass + + class Op(Token): """Operator (e.g. '+' or 'in')""" @@ -291,11 +295,14 @@ class Lexer: # newlines within parentheses/brackets. open_brackets = Undefined(List[str]) + pyversion = 3 + def __init__(self, pyversion: int = 3) -> None: self.map = [self.unknown_character] * 256 self.tok = [] self.indents = [0] self.open_brackets = [] + self.pyversion = pyversion # Fill in the map from valid character codes to relevant lexer methods. for seq, method in [('ABCDEFGHIJKLMNOPQRSTUVWXYZ', self.lex_name), ('abcdefghijklmnopqrstuvwxyz_', self.lex_name), @@ -363,10 +370,13 @@ def lex(self, s: str, first_line: int) -> None: def lex_number_or_dot(self) -> None: """Analyse a token starting with a dot. - It can be the member access operator or a float literal such as '.123'. + It can be the member access operator, a float literal such as '.123', + or an ellipsis (for Python 3) """ if self.is_at_number(): self.lex_number() + elif self.is_at_ellipsis() and self.pyversion >= 3: + self.lex_ellipsis() else: self.lex_misc() @@ -376,6 +386,12 @@ def is_at_number(self) -> bool: """Is the current location at a numeric literal?""" return self.match(self.number_exp) != '' + ellipsis_exp = re.compile(r'\.\.\.') + + def is_at_ellipsis(self) -> bool: + """Is the current location at a ellipsis '...'""" + return self.match(self.ellipsis_exp) != '' + # Regexps used by lex_number # Decimal/hex/octal literal or integer complex literal @@ -415,6 +431,9 @@ def lex_number(self) -> None: # Complex literal. self.add_token(ComplexLit(sc)) + def lex_ellipsis(self) -> None: + self.add_token(EllipsisToken('...')) + name_exp = re.compile('[a-zA-Z_][a-zA-Z0-9_]*') def lex_name(self) -> None: diff --git a/mypy/noderepr.py b/mypy/noderepr.py index b3a704496157..7b9c52d13b44 100644 --- a/mypy/noderepr.py +++ b/mypy/noderepr.py @@ -222,6 +222,11 @@ def __init__(self, complex: Any) -> None: self.complex = complex +class EllipsisNodeRepr: + def __init__(self, ellipsis_tok) -> None: + self.ellipsis = ellipsis_tok + + class ParenExprRepr: def __init__(self, lparen: Any, rparen: Any) -> None: self.lparen = lparen diff --git a/mypy/nodes.py b/mypy/nodes.py index afa912e4d917..0fde4c451bd2 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1412,6 +1412,16 @@ def accept(self, visitor: NodeVisitor[T]) -> T: return visitor.visit_disjointclass_expr(self) +# Constants + + +class EllipsisNode(Node): + """Ellipsis (...)""" + + def accept(self, visitor: NodeVisitor[T]) -> T: + return visitor.visit_ellipsis(self) + + class TempNode(Node): """Temporary dummy node used during type checking. diff --git a/mypy/output.py b/mypy/output.py index fe2bd1a745d4..d829bf0a8e7e 100644 --- a/mypy/output.py +++ b/mypy/output.py @@ -341,6 +341,9 @@ def visit_float_expr(self, o): def visit_complex_expr(self, o): self.token(o.repr.complex) + def visit_ellipsis(self, o): + self.token(o.repr.ellipsis) + def visit_paren_expr(self, o): self.token(o.repr.lparen) self.node(o.expr) diff --git a/mypy/parse.py b/mypy/parse.py index f06ab3d265d3..378095eb36d4 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -11,7 +11,8 @@ from mypy import lex from mypy.lex import ( Token, Eof, Bom, Break, Name, Colon, Dedent, IntLit, StrLit, BytesLit, - UnicodeLit, FloatLit, Op, Indent, Keyword, Punct, LexError, ComplexLit + UnicodeLit, FloatLit, Op, Indent, Keyword, Punct, LexError, ComplexLit, + EllipsisToken ) import mypy.types from mypy.nodes import ( @@ -25,7 +26,7 @@ FloatExpr, CallExpr, SuperExpr, MemberExpr, IndexExpr, SliceExpr, OpExpr, UnaryExpr, FuncExpr, TypeApplication, PrintStmt, ImportBase, ComparisonExpr, StarExpr, YieldFromStmt, YieldFromExpr, NonlocalDecl, DictionaryComprehension, - SetComprehension, ComplexExpr + SetComprehension, ComplexExpr, EllipsisNode ) from mypy import nodes from mypy import noderepr @@ -831,6 +832,12 @@ def parse_yield_from_expr(self) -> YieldFromExpr: pass return node + def parse_ellipsis(self) -> EllipsisNode: + ellipsis_tok = self.expect('...') + node = EllipsisNode() + self.set_repr(node, noderepr.EllipsisNodeRepr(ellipsis_tok)) + return node + def parse_del_stmt(self) -> DelStmt: del_tok = self.expect('del') expr = self.parse_expression() @@ -1122,6 +1129,8 @@ def parse_expression(self, prec: int = 0, star_expr_allowed: bool = False) -> No expr = self.parse_complex_expr() elif isinstance(t, Keyword) and s == "yield": expr = self.parse_yield_from_expr() # The expression yield from and yield to assign + elif isinstance(t, EllipsisToken): + expr = self.parse_ellipsis() else: # Invalid expression. self.parse_error() diff --git a/mypy/strconv.py b/mypy/strconv.py index a609c46ef776..fb7feac762de 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -300,6 +300,9 @@ def visit_float_expr(self, o): def visit_complex_expr(self, o): return 'ComplexExpr({})'.format(o.value) + def visit_ellipsis(self, o): + return 'Ellipsis' + def visit_paren_expr(self, o): return self.dump([o.expr], o) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index f37bdc9e7a65..16d5f8217786 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -1276,3 +1276,29 @@ class B: class C(B): pass [out] + + +-- Ellipsis +-- -------- + + +[case testEllipsis] +from typing import Undefined +a = Undefined # type: A +a = ... # E: Incompatible types in assignment (expression has type "ellipsis", variable has type "A") +b = ... +c = ... +b = c +....__class__ +....a # E: "ellipsis" has no attribute "a" + +class A: pass +[file builtins.py] +class object: + def __init__(self): pass +class ellipsis: + def __init__(self): pass + __class__ = object() +class type: pass +class function: pass +[out] diff --git a/mypy/test/data/lib-stub/builtins.py b/mypy/test/data/lib-stub/builtins.py index 49b9743b9a5e..853955b37635 100644 --- a/mypy/test/data/lib-stub/builtins.py +++ b/mypy/test/data/lib-stub/builtins.py @@ -17,4 +17,8 @@ class str: pass class tuple: pass class function: pass +@builtinclass +class ellipsis: + def __init__(self) -> None: pass + # Definition of None is implicit diff --git a/mypy/test/data/output.test b/mypy/test/data/output.test index e49e36f90e58..cd1b16bd10db 100644 --- a/mypy/test/data/output.test +++ b/mypy/test/data/output.test @@ -320,6 +320,9 @@ with bar() as x: with f() as x, y() as z: pass +[case testEllipsis] +... + -- Function definitions -- -------------------- diff --git a/mypy/test/data/parse-errors.test b/mypy/test/data/parse-errors.test index 6352599ba646..376a7278ffc9 100644 --- a/mypy/test/data/parse-errors.test +++ b/mypy/test/data/parse-errors.test @@ -374,3 +374,12 @@ def while(): pass [out] file, line 1: Parse error before "while" file, line 1: Parse error before end of line + +[case testInvalidEllipsis1] +...0 +..._ +...a +[out] +file, line 1: Parse error before numeric literal +file, line 2: Parse error before "_" +file, line 3: Parse error before "a" diff --git a/mypy/test/data/parse.test b/mypy/test/data/parse.test index a374d26e2144..9af1d1460d50 100644 --- a/mypy/test/data/parse.test +++ b/mypy/test/data/parse.test @@ -2946,3 +2946,23 @@ MypyFile:1( NameExpr(p)) NameExpr(q)) NameExpr(z)))) + +[case testEllipsis] +... +a[1,...,2] +....__class__ +[out] +MypyFile:1( + ExpressionStmt:1( + Ellipsis) + ExpressionStmt:2( + IndexExpr:2( + NameExpr(a) + TupleExpr:2( + IntExpr(1) + Ellipsis + IntExpr(2)))) + ExpressionStmt:3( + MemberExpr:3( + Ellipsis + __class__))) diff --git a/mypy/test/data/typexport-basic.test b/mypy/test/data/typexport-basic.test index aadf4d3312e9..bffaaf3c102b 100644 --- a/mypy/test/data/typexport-basic.test +++ b/mypy/test/data/typexport-basic.test @@ -62,6 +62,12 @@ NameExpr(6) : A NameExpr(7) : B NameExpr(10) : A +[case testEllipsis] +import typing +... +[out] +EllipsisNode(2) : builtins.ellipsis + [case testMemberAccess] ## MemberExpr|CallExpr from typing import Undefined diff --git a/mypy/visitor.py b/mypy/visitor.py index 6ac592000295..f4bdfca5f4c4 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -139,6 +139,9 @@ def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> T: def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> T: pass + def visit_ellipsis(self, o: 'mypy.nodes.EllipsisNode') -> T: + pass + def visit_paren_expr(self, o: 'mypy.nodes.ParenExpr') -> T: pass diff --git a/stubs/3.2/builtins.py b/stubs/3.2/builtins.py index 3a814c915bc8..ab98700ec2e4 100644 --- a/stubs/3.2/builtins.py +++ b/stubs/3.2/builtins.py @@ -717,6 +717,15 @@ def zip(iter1: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], def __import__(name: str, globals: Dict[str, Any] = {}, locals: Dict[str, Any] = {}, fromlist: List[str] = [], level: int = -1) -> Any: pass +# Ellipsis + +@builtinclass +class ellipsis: + # TODO not defined in builtins! + def __init__(self) -> None: pass + +Ellipsis = ellipsis() + # Exceptions @builtinclass