Skip to content

Support for Ellipsis and ... #528

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 7 commits into from
Dec 18, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
6 changes: 5 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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':
Expand Down
21 changes: 20 additions & 1 deletion mypy/lex.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ class Colon(Token):
pass


class EllipsisToken(Token):
pass


class Op(Token):
"""Operator (e.g. '+' or 'in')"""

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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()

Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions mypy/noderepr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
3 changes: 3 additions & 0 deletions mypy/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 11 additions & 2 deletions mypy/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions mypy/strconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
26 changes: 26 additions & 0 deletions mypy/test/data/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
4 changes: 4 additions & 0 deletions mypy/test/data/lib-stub/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions mypy/test/data/output.test
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ with bar() as x:
with f() as x, y() as z:
pass

[case testEllipsis]
...


-- Function definitions
-- --------------------
Expand Down
9 changes: 9 additions & 0 deletions mypy/test/data/parse-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -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"
20 changes: 20 additions & 0 deletions mypy/test/data/parse.test
Original file line number Diff line number Diff line change
Expand Up @@ -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__)))
6 changes: 6 additions & 0 deletions mypy/test/data/typexport-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions mypy/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions stubs/3.2/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add comment mentioning that ellipsis isn't actually defined in Python builtins (only Ellipsis is), so strictly speaking we shouldn't include it in builtins. However, there are other cases like this (e.g., module) and all similar issues can be fixed separately, so this is fine for now.

class ellipsis:
# TODO not defined in builtins!
def __init__(self) -> None: pass

Ellipsis = ellipsis()

# Exceptions

@builtinclass
Expand Down