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

Generator fixup #1137

Merged
merged 8 commits into from
Jan 20, 2016
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
279 changes: 149 additions & 130 deletions mypy/checker.py

Large diffs are not rendered by default.

6 changes: 0 additions & 6 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1192,12 +1192,6 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
args,
[nodes.ARG_POS] * len(args), e)[0]

def visit_yield_expr(self, e: YieldExpr) -> Type:
# TODO: Implement proper type checking of yield expressions.
if e.expr:
self.accept(e.expr)
return AnyType()

def visit_func_expr(self, e: FuncExpr) -> Type:
"""Type check lambda expression."""
inferred_type = self.infer_lambda_type_using_context(e)
Expand Down
7 changes: 4 additions & 3 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@
BOOLEAN_EXPECTED_FOR_NOT = 'Boolean value expected for not operand'
INVALID_EXCEPTION = 'Exception must be derived from BaseException'
INVALID_EXCEPTION_TYPE = 'Exception type must be derived from BaseException'
INVALID_RETURN_TYPE_FOR_YIELD = \
INVALID_RETURN_TYPE_FOR_GENERATOR = \
'The return type of a generator function should be "Generator" or one of its supertypes'
INVALID_RETURN_TYPE_FOR_YIELD_FROM = \
'Iterable function return type expected for "yield from"'
INVALID_GENERATOR_RETURN_ITEM_TYPE = \
'The return type of a generator function must be None in its third type parameter in Python 2'
YIELD_VALUE_EXPECTED = 'Yield value expected'
INCOMPATIBLE_TYPES = 'Incompatible types'
INCOMPATIBLE_TYPES_IN_ASSIGNMENT = 'Incompatible types in assignment'
INCOMPATIBLE_REDEFINITION = 'Incompatible redefinition'
Expand Down
21 changes: 0 additions & 21 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,6 @@ class FuncItem(FuncBase):
# Is this an overload variant of function with more than one overload variant?
is_overload = False
is_generator = False # Contains a yield statement?
is_coroutine = False # Contains @coroutine or yield from Future
is_static = False # Uses @staticmethod?
is_class = False # Uses @classmethod?
# Variants of function with type variables with values expanded
Expand Down Expand Up @@ -634,26 +633,6 @@ def accept(self, visitor: NodeVisitor[T]) -> T:
return visitor.visit_assert_stmt(self)


class YieldStmt(Node):
expr = None # type: Node

def __init__(self, expr: Node) -> None:
self.expr = expr

def accept(self, visitor: NodeVisitor[T]) -> T:
return visitor.visit_yield_stmt(self)


class YieldFromStmt(Node):
expr = None # type: Node

def __init__(self, expr: Node) -> None:
self.expr = expr

def accept(self, visitor: NodeVisitor[T]) -> T:
return visitor.visit_yield_from_stmt(self)


class DelStmt(Node):
expr = None # type: Node

Expand Down
39 changes: 12 additions & 27 deletions mypy/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
MypyFile, Import, Node, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef,
ClassDef, Decorator, Block, Var, OperatorAssignmentStmt,
ExpressionStmt, AssignmentStmt, ReturnStmt, RaiseStmt, AssertStmt,
YieldStmt, DelStmt, BreakStmt, ContinueStmt, PassStmt, GlobalDecl,
DelStmt, BreakStmt, ContinueStmt, PassStmt, GlobalDecl,
WhileStmt, ForStmt, IfStmt, TryStmt, WithStmt, CastExpr,
TupleExpr, GeneratorExpr, ListComprehension, ListExpr, ConditionalExpr,
DictExpr, SetExpr, NameExpr, IntExpr, StrExpr, BytesExpr, UnicodeExpr,
FloatExpr, CallExpr, SuperExpr, MemberExpr, IndexExpr, SliceExpr, OpExpr,
UnaryExpr, FuncExpr, TypeApplication, PrintStmt, ImportBase, ComparisonExpr,
StarExpr, YieldFromStmt, YieldFromExpr, NonlocalDecl, DictionaryComprehension,
StarExpr, YieldFromExpr, NonlocalDecl, DictionaryComprehension,
SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, ExecStmt, Argument,
BackquoteExpr
)
Expand Down Expand Up @@ -879,8 +879,6 @@ def parse_statement(self) -> Tuple[Node, bool]:
stmt = self.parse_nonlocal_decl()
elif ts == 'assert':
stmt = self.parse_assert_stmt()
elif ts == 'yield':
stmt = self.parse_yield_stmt()
elif ts == 'del':
stmt = self.parse_del_stmt()
elif ts == 'with':
Expand Down Expand Up @@ -963,34 +961,21 @@ def parse_assert_stmt(self) -> AssertStmt:
node = AssertStmt(expr)
return node

def parse_yield_stmt(self) -> Union[YieldStmt, YieldFromStmt]:
self.expect('yield')
def parse_yield_or_yield_from_expr(self) -> Union[YieldFromExpr, YieldExpr]:
self.expect("yield")
expr = None
node = YieldStmt(expr)
node = YieldExpr(expr) # type: Union[YieldFromExpr, YieldExpr]
if not isinstance(self.current(), Break):
if self.current_str() == "from":
self.expect("from")
expr = self.parse_expression() # Here comes when yield from is not assigned
node_from = YieldFromStmt(expr)
return node_from # return here, we've gotted the type
expr = self.parse_expression() # when yield from is assigned to a variable
node = YieldFromExpr(expr)
else:
expr = self.parse_expression()
node = YieldStmt(expr)
return node

def parse_yield_or_yield_from_expr(self) -> Union[YieldFromExpr, YieldExpr]:
self.expect("yield")
node = None # type: Union[YieldFromExpr, YieldExpr]
if self.current_str() == "from":
self.expect("from")
expr = self.parse_expression() # Here comes when yield from is assigned to a variable
node = YieldFromExpr(expr)
else:
if self.current_str() == ')':
node = YieldExpr(None)
else:
expr = self.parse_expression()
node = YieldExpr(expr)
if self.current_str() == ')':
node = YieldExpr(None)
else:
expr = self.parse_expression()
node = YieldExpr(expr)
return node

def parse_ellipsis(self) -> EllipsisExpr:
Expand Down
25 changes: 8 additions & 17 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@
ClassDef, Var, GDEF, MODULE_REF, FuncItem, Import,
ImportFrom, ImportAll, Block, LDEF, NameExpr, MemberExpr,
IndexExpr, TupleExpr, ListExpr, ExpressionStmt, ReturnStmt,
RaiseStmt, YieldStmt, AssertStmt, OperatorAssignmentStmt, WhileStmt,
RaiseStmt, AssertStmt, OperatorAssignmentStmt, WhileStmt,
ForStmt, BreakStmt, ContinueStmt, IfStmt, TryStmt, WithStmt, DelStmt,
GlobalDecl, SuperExpr, DictExpr, CallExpr, RefExpr, OpExpr, UnaryExpr,
SliceExpr, CastExpr, TypeApplication, Context, SymbolTable,
SymbolTableNode, BOUND_TVAR, UNBOUND_TVAR, ListComprehension, GeneratorExpr,
FuncExpr, MDEF, FuncBase, Decorator, SetExpr, TypeVarExpr,
StrExpr, PrintStmt, ConditionalExpr, PromoteExpr,
ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, MroError, type_aliases,
YieldFromStmt, YieldFromExpr, NamedTupleExpr, NonlocalDecl,
YieldFromExpr, NamedTupleExpr, NonlocalDecl,
SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr,
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, COVARIANT, CONTRAVARIANT,
INVARIANT, UNBOUND_IMPORTED
Expand Down Expand Up @@ -1447,7 +1447,6 @@ def visit_decorator(self, dec: Decorator) -> None:
self.check_decorated_function_is_method('abstractmethod', dec)
elif refers_to_fullname(d, 'asyncio.tasks.coroutine'):
removed.append(i)
dec.func.is_coroutine = True
elif refers_to_fullname(d, 'builtins.staticmethod'):
removed.append(i)
dec.func.is_static = True
Expand Down Expand Up @@ -1504,20 +1503,6 @@ def visit_raise_stmt(self, s: RaiseStmt) -> None:
if s.from_expr:
s.from_expr.accept(self)

def visit_yield_stmt(self, s: YieldStmt) -> None:
if not self.is_func_scope():
self.fail("'yield' outside function", s)
else:
self.function_stack[-1].is_generator = True
if s.expr:
s.expr.accept(self)

def visit_yield_from_stmt(self, s: YieldFromStmt) -> None:
if not self.is_func_scope():
self.fail("'yield from' outside function", s)
if s.expr:
s.expr.accept(self)

def visit_assert_stmt(self, s: AssertStmt) -> None:
if s.expr:
s.expr.accept(self)
Expand Down Expand Up @@ -1681,6 +1666,8 @@ def visit_star_expr(self, expr: StarExpr) -> None:
def visit_yield_from_expr(self, e: YieldFromExpr) -> None:
if not self.is_func_scope(): # not sure
self.fail("'yield from' outside function", e)
else:
self.function_stack[-1].is_generator = True
if e.expr:
e.expr.accept(self)

Expand Down Expand Up @@ -1909,6 +1896,10 @@ def visit__promote_expr(self, expr: PromoteExpr) -> None:
expr.type = self.anal_type(expr.type)

def visit_yield_expr(self, expr: YieldExpr) -> None:
if not self.is_func_scope():
self.fail("'yield' outside function", expr)
else:
self.function_stack[-1].is_generator = True
if expr.expr:
expr.expr.accept(self)

Expand Down
29 changes: 29 additions & 0 deletions mypy/test/data/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1322,8 +1322,10 @@ class str: pass
def f(x: int) -> None:
x = yield f('')
x = 1
[builtins fixtures/for.py]
[out]
main: note: In function "f":
main:1: error: The return type of a generator function should be "Generator" or one of its supertypes
Copy link
Member

Choose a reason for hiding this comment

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

I would rather fix up f()'s declaration to define the right return type? (e.g. Iterator[int])

Copy link
Member

Choose a reason for hiding this comment

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

Unless you're aiming for two birds with one stone?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As it stands, it's checking both at once. A common idiom in the tests seems to be purposefully introducing type errors to check the error message. Testing the Generator function return type enforcement is important, but I don't have much of an opinion on whether it should be done in this function or a separate one.

main:2: error: Argument 1 to "f" has incompatible type "str"; expected "int"

[case testYieldExpressionWithNone]
Expand All @@ -1334,6 +1336,33 @@ def f(x: int) -> Iterator[None]:
[out]


-- Yield from expression
-- ----------------



[case testYieldFromIteratorHasNoValue]
from typing import Iterator
def f() -> Iterator[int]:
yield 5
def g() -> Iterator[int]:
a = yield from f()
[out]
main: note: In function "g":
main:5: error: Function does not return a value

[case testYieldFromGeneratorHasValue]
from typing import Iterator, Generator
def f() -> Generator[int, None, str]:
yield 5
return "ham"
def g() -> Iterator[int]:
a = "string"
a = yield from f()
[out]



-- dict(x=y, ...)
-- --------------

Expand Down
68 changes: 60 additions & 8 deletions mypy/test/data/check-statements.test
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,42 @@ def g() -> None:
[out]
main: note: In function "g":

[case testReturnInGenerator]
from typing import Generator
def f() -> Generator[int, None, str]:
yield 1
return "foo"
[out]

[case testEmptyReturnInGenerator]
from typing import Generator
def f() -> Generator[int, None, str]:
yield 1
return # E: Return value expected
[out]
main: note: In function "f":

[case testEmptyReturnInNoneTypedGenerator]
from typing import Generator
def f() -> Generator[int, None, None]:
yield 1
return
[out]

[case testNonEmptyReturnInNoneTypedGenerator]
from typing import Generator
def f() -> Generator[int, None, None]:
yield 1
return 42 # E: No return value expected
[out]
main: note: In function "f":

[case testReturnInIterator]
from typing import Iterator
def f() -> Iterator[int]:
yield 1
return "foo"
[out]
Copy link
Member

Choose a reason for hiding this comment

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

I guess there are also cases where the return type is Generator[..., ... None] and there's a plain return in the body (no error) or a return 42 (error)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, thanks for catching that! Added.


-- If statement
-- ------------
Expand Down Expand Up @@ -675,8 +711,8 @@ def f() -> Any:

[case testYieldInFunctionReturningFunction]
from typing import Callable
def f() -> Callable[[], None]:
yield object() # E: The return type of a generator function should be "Generator" or one of its supertypes
def f() -> Callable[[], None]: # E: The return type of a generator function should be "Generator" or one of its supertypes
yield object()
[out]
main: note: In function "f":

Expand All @@ -687,8 +723,8 @@ def f():

[case testWithInvalidInstanceReturnType]
import typing
def f() -> int:
yield 1 # E: The return type of a generator function should be "Generator" or one of its supertypes
def f() -> int: # E: The return type of a generator function should be "Generator" or one of its supertypes
yield 1
[builtins fixtures/for.py]
[out]
main: note: In function "f":
Expand All @@ -715,6 +751,22 @@ def f() -> Iterator[None]:
yield
[builtins fixtures/for.py]

[case testYieldWithNoValueWhenValueRequired]
from typing import Iterator
def f() -> Iterator[int]:
yield # E: Yield value expected
[builtins fixtures/for.py]
[out]
main: note: In function "f":

[case testYieldWithExplicitNone]
from typing import Iterator
def f() -> Iterator[None]:
yield None # E: Incompatible types in yield (actual type None, expected type None)
[builtins fixtures/for.py]
[out]
main: note: In function "f":


-- Yield from statement
-- --------------------
Expand Down Expand Up @@ -746,17 +798,17 @@ def f() -> Any:
from typing import Iterator, Callable
def g() -> Iterator[int]:
yield 42
def f() -> Callable[[], None]:
yield from g() # E: Iterable function return type expected for "yield from"
def f() -> Callable[[], None]: # E: The return type of a generator function should be "Generator" or one of its supertypes
yield from g()
[out]
main: note: In function "f":

[case testYieldFromNotIterableReturnType]
from typing import Iterator
def g() -> Iterator[int]:
yield 42
def f() -> int:
yield from g() # E: Iterable function return type expected for "yield from"
def f() -> int: # E: The return type of a generator function should be "Generator" or one of its supertypes
yield from g()
[out]
main: note: In function "f":

Expand Down
3 changes: 3 additions & 0 deletions mypy/test/data/lib-stub/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def throw(self, typ: Any, val: Any=None, tb=None) -> None: pass
@abstractmethod
def close(self) -> None: pass

@abstractmethod
def __iter__(self) -> 'Generator[T, U, V]': pass

class Sequence(Generic[T]):
@abstractmethod
def __getitem__(self, n: Any) -> T: pass
Loading