diff --git a/Src/IronPython/Compiler/Ast/FunctionDefinition.cs b/Src/IronPython/Compiler/Ast/FunctionDefinition.cs index c621c6576..c9e9cd9fc 100644 --- a/Src/IronPython/Compiler/Ast/FunctionDefinition.cs +++ b/Src/IronPython/Compiler/Ast/FunctionDefinition.cs @@ -124,6 +124,8 @@ internal override int KwOnlyArgCount { /// public bool IsGenerator { get; set; } + internal bool GeneratorStop { get; set; } + /// /// Called by parser to mark that this function can set sys.exc_info(). /// An alternative technique would be to just walk the body after the parse and look for a except block. @@ -181,6 +183,10 @@ internal override FunctionAttributes Flags { fa |= FunctionAttributes.Generator; } + if (GeneratorStop) { + fa |= FunctionAttributes.GeneratorStop; + } + return fa; } } diff --git a/Src/IronPython/Compiler/Ast/PythonAst.cs b/Src/IronPython/Compiler/Ast/PythonAst.cs index b357d96d9..4f94217ff 100644 --- a/Src/IronPython/Compiler/Ast/PythonAst.cs +++ b/Src/IronPython/Compiler/Ast/PythonAst.cs @@ -317,6 +317,12 @@ internal override Microsoft.Scripting.Ast.LightLambdaExpression GetLambda() { #region Public API + internal bool GeneratorStop { + get { + return (_languageFeatures & ModuleOptions.GeneratorStop) != 0; + } + } + public Statement Body { get { return _body; } } diff --git a/Src/IronPython/Compiler/Parser.cs b/Src/IronPython/Compiler/Parser.cs index 74d2a24d2..3ad159ded 100644 --- a/Src/IronPython/Compiler/Parser.cs +++ b/Src/IronPython/Compiler/Parser.cs @@ -583,6 +583,7 @@ private Expression ParseYieldExpression() { FunctionDefinition current = CurrentFunction; if (current != null) { current.IsGenerator = true; + current.GeneratorStop = GeneratorStop; } var start = GetStart(); @@ -889,6 +890,9 @@ private FromImportStatement ParseFromImportStmt() { // Ignored in Python 3 } else if (name == "unicode_literals") { // Ignored in Python 3 + } else if (name == "generator_stop") { + // New in 3.5, mandatory in 3.7 + _languageFeatures |= ModuleOptions.GeneratorStop; } else if (name == "nested_scopes") { } else if (name == "generators") { } else { @@ -2500,6 +2504,7 @@ private Expression ParseGeneratorExpression(Expression expr) { Parameter parameter = new Parameter("__gen_$_parm__", 0); FunctionDefinition func = new FunctionDefinition(fname, new Parameter[] { parameter }, root); func.IsGenerator = true; + func.GeneratorStop = GeneratorStop; func.SetLoc(_globalParent, root.StartIndex, GetEnd()); func.HeaderIndex = root.EndIndex; @@ -3159,6 +3164,8 @@ private Token EatEndOfInput() { return PythonOps.BadSourceEncodingError(message, lineNum, _sourceUnit.Path); } + private bool GeneratorStop => (_languageFeatures & ModuleOptions.GeneratorStop) == ModuleOptions.GeneratorStop; + private void StartParsing() { if (_parsingStarted) throw new InvalidOperationException("Parsing already started. Use Restart to start again."); diff --git a/Src/IronPython/Compiler/PythonCompilerOptions.cs b/Src/IronPython/Compiler/PythonCompilerOptions.cs index 8279367d1..645ee7491 100644 --- a/Src/IronPython/Compiler/PythonCompilerOptions.cs +++ b/Src/IronPython/Compiler/PythonCompilerOptions.cs @@ -55,6 +55,16 @@ public int[] InitialIndent { } } + internal bool GeneratorStop { + get { + return (_module & ModuleOptions.GeneratorStop) != 0; + } + set { + if (value) _module |= ModuleOptions.GeneratorStop; + else _module &= ~ModuleOptions.GeneratorStop; + } + } + public bool Verbatim { get { return (_module & ModuleOptions.Verbatim) != 0; diff --git a/Src/IronPython/Modules/Builtin.cs b/Src/IronPython/Modules/Builtin.cs index 4dfe24e0e..e28dc7440 100644 --- a/Src/IronPython/Modules/Builtin.cs +++ b/Src/IronPython/Modules/Builtin.cs @@ -1642,6 +1642,9 @@ internal static PythonCompilerOptions GetRuntimeGeneratedCodeCompilerOptions(Cod if ((cflags & CompileFlags.CO_FUTURE_UNICODE_LITERALS) != 0) { // Ignored since Python 3 } + if ((cflags & CompileFlags.CO_FUTURE_GENERATOR_STOP) != 0) { + langFeat |= ModuleOptions.GeneratorStop; + } pco.Module |= langFeat; // The options created this way never creates @@ -1662,7 +1665,7 @@ private static CompileFlags GetCompilerFlags(int flags) { CompileFlags cflags = (CompileFlags)flags; if ((cflags & ~(CompileFlags.CO_NESTED | CompileFlags.CO_GENERATOR_ALLOWED | CompileFlags.CO_FUTURE_DIVISION | CompileFlags.CO_DONT_IMPLY_DEDENT | CompileFlags.CO_FUTURE_ABSOLUTE_IMPORT | CompileFlags.CO_FUTURE_WITH_STATEMENT | CompileFlags.CO_FUTURE_PRINT_FUNCTION | - CompileFlags.CO_FUTURE_UNICODE_LITERALS | CompileFlags.CO_FUTURE_BARRY_AS_BDFL)) != 0) { + CompileFlags.CO_FUTURE_UNICODE_LITERALS | CompileFlags.CO_FUTURE_BARRY_AS_BDFL | CompileFlags.CO_FUTURE_GENERATOR_STOP)) != 0) { throw PythonOps.ValueError("unrecognized flags"); } diff --git a/Src/IronPython/Runtime/CompileFlags.cs b/Src/IronPython/Runtime/CompileFlags.cs index f87efd724..b50bc0d75 100644 --- a/Src/IronPython/Runtime/CompileFlags.cs +++ b/Src/IronPython/Runtime/CompileFlags.cs @@ -7,14 +7,15 @@ namespace IronPython.Runtime { [Flags] public enum CompileFlags { - CO_NESTED = 0x0010, // nested_scopes - CO_DONT_IMPLY_DEDENT = 0x0200, // report errors if statement isn't dedented. - CO_GENERATOR_ALLOWED = 0x1000, // generators - CO_FUTURE_DIVISION = 0x2000, // division - CO_FUTURE_ABSOLUTE_IMPORT = 0x4000, // absolute imports by default + CO_NESTED = 0x0010, // nested_scopes + CO_DONT_IMPLY_DEDENT = 0x0200, // report errors if statement isn't dedented + CO_GENERATOR_ALLOWED = 0x1000, // generators + CO_FUTURE_DIVISION = 0x2000, // division + CO_FUTURE_ABSOLUTE_IMPORT = 0x4000, // perform absolute imports by default CO_FUTURE_WITH_STATEMENT = 0x8000, // with statement CO_FUTURE_PRINT_FUNCTION = 0x10000, // print function CO_FUTURE_UNICODE_LITERALS = 0x20000, // default unicode literals - CO_FUTURE_BARRY_AS_BDFL = 0x40000, // + CO_FUTURE_BARRY_AS_BDFL = 0x40000, // + CO_FUTURE_GENERATOR_STOP = 0x80000, // StopIteration becomes RuntimeError in generators } } diff --git a/Src/IronPython/Runtime/FunctionAttributes.cs b/Src/IronPython/Runtime/FunctionAttributes.cs index c55d8c154..6ffc0c85b 100644 --- a/Src/IronPython/Runtime/FunctionAttributes.cs +++ b/Src/IronPython/Runtime/FunctionAttributes.cs @@ -29,5 +29,6 @@ public enum FunctionAttributes { /// IronPython specific: Set if the function includes a try/finally block. /// ContainsTryFinally = 0x8000, + GeneratorStop = 0x80000, // TODO: delete me in 3.7 } } diff --git a/Src/IronPython/Runtime/Generator.cs b/Src/IronPython/Runtime/Generator.cs index 813068419..231721a5e 100644 --- a/Src/IronPython/Runtime/Generator.cs +++ b/Src/IronPython/Runtime/Generator.cs @@ -427,6 +427,14 @@ private object NextWorker() { FinalValue = CurrentValue; CurrentValue = OperationFailed.Value; } + } catch (StopIterationException e) { + if (GeneratorStop) { + var stopIterationError = e.GetPythonException(); + throw PythonExceptions.CreatePythonThrowable(PythonExceptions.RuntimeError, "generator raised StopIteration") + .CreateClrExceptionWithCause(stopIterationError, stopIterationError, true); + } else { + throw; + } } finally { // A generator restores the sys.exc_info() status after each yield point. PythonOps.CurrentExceptionState = _state.PrevException; @@ -561,6 +569,8 @@ internal bool ContainsTryFinally { } } + private bool GeneratorStop => (_function.Flags & FunctionAttributes.GeneratorStop) != 0; + #endregion #region ICodeFormattable Members diff --git a/Src/IronPython/Runtime/ModuleOptions.cs b/Src/IronPython/Runtime/ModuleOptions.cs index e5f28910b..9d051028c 100644 --- a/Src/IronPython/Runtime/ModuleOptions.cs +++ b/Src/IronPython/Runtime/ModuleOptions.cs @@ -53,6 +53,7 @@ public enum ModuleOptions { /// /// Generated code should support light exceptions /// - LightThrow = 0x8000 + LightThrow = 0x8000, + GeneratorStop = 0x10000, } } diff --git a/Src/StdLib/Lib/__future__.py b/Src/StdLib/Lib/__future__.py index 3b2d5ecb9..63b2be352 100644 --- a/Src/StdLib/Lib/__future__.py +++ b/Src/StdLib/Lib/__future__.py @@ -56,6 +56,7 @@ "print_function", "unicode_literals", "barry_as_FLUFL", + "generator_stop", ] __all__ = ["all_feature_names"] + all_feature_names @@ -72,6 +73,7 @@ CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals CO_FUTURE_BARRY_AS_BDFL = 0x40000 +CO_FUTURE_GENERATOR_STOP = 0x80000 # StopIteration becomes RuntimeError in generators class _Feature: def __init__(self, optionalRelease, mandatoryRelease, compiler_flag): @@ -132,3 +134,7 @@ def __repr__(self): barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2), (3, 9, 0, "alpha", 0), CO_FUTURE_BARRY_AS_BDFL) + +generator_stop = _Feature((3, 5, 0, "beta", 1), + (3, 7, 0, "alpha", 0), + CO_FUTURE_GENERATOR_STOP) diff --git a/Src/StdLib/Lib/test/test_pep479.py b/Src/StdLib/Lib/test/test_pep479.py new file mode 100644 index 000000000..bc235ceb0 --- /dev/null +++ b/Src/StdLib/Lib/test/test_pep479.py @@ -0,0 +1,34 @@ +from __future__ import generator_stop + +import unittest + + +class TestPEP479(unittest.TestCase): + def test_stopiteration_wrapping(self): + def f(): + raise StopIteration + def g(): + yield f() + with self.assertRaisesRegex(RuntimeError, + "generator raised StopIteration"): + next(g()) + + def test_stopiteration_wrapping_context(self): + def f(): + raise StopIteration + def g(): + yield f() + + try: + next(g()) + except RuntimeError as exc: + self.assertIs(type(exc.__cause__), StopIteration) + self.assertIs(type(exc.__context__), StopIteration) + self.assertTrue(exc.__suppress_context__) + else: + self.fail('__cause__, __context__, or __suppress_context__ ' + 'were not properly set') + + +if __name__ == '__main__': + unittest.main()