Skip to content

Commit f952541

Browse files
committed
[GR-15078] Throw syntax error if nonlocal statement is used after local declaration.
PullRequest: graalpython/674
2 parents 784abee + 2123bf6 commit f952541

File tree

6 files changed

+44
-9
lines changed

6 files changed

+44
-9
lines changed

doc/CONTRIBUTING.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ debugger or Chromium, respectively.
149149
graalpython/com.oracle.graal.python.test/src/tests/test_tagged_unittests.py \
150150
-k NAME-OF-CPYTHON-UNITTEST
151151

152+
A tag file can be regenerated with
153+
154+
mx python graalpython/com.oracle.graal.python.test/src/tests/test_tagged_unittests.py \
155+
--retag NAME-OF-CPYTHON-UNITTEST
156+
152157
There's also multiple other gates that may fail with changes. One of these is
153158
our *style* gate, which checks formatting rules and copyrights. To auto-fix most
154159
issues, run the following command. Anything that's reported as error after this

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/parser/AssignmentTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,16 @@ public void assignToLiteral05() throws Exception {
196196
public void assignToKeyword01() throws Exception {
197197
checkSyntaxErrorMessage("True = 1", "SyntaxError: can't assign to keyword");
198198
}
199+
200+
@Test
201+
public void nonLocal01() throws Exception {
202+
checkSyntaxErrorMessage(
203+
"def outer():\n" +
204+
" x = 'local in outer'\n" +
205+
" def inner():\n" +
206+
" x = 10\n" +
207+
" nonlocal x\n" +
208+
" inner()\n",
209+
"SyntaxError: name 'x' is assigned to before nonlocal declaration");
210+
}
199211
}

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_fstring.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*TestCase.test_del
88
*TestCase.test_dict
99
*TestCase.test_double_braces
10+
*TestCase.test_empty_format_specifier
1011
*TestCase.test_if_conditional
1112
*TestCase.test_invalid_expressions
1213
*TestCase.test_literal

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
import com.oracle.graal.python.runtime.PythonParser;
169169
import com.oracle.graal.python.runtime.PythonParser.ParserMode;
170170
import com.oracle.graal.python.runtime.exception.PException;
171+
import com.oracle.graal.python.runtime.formatting.ErrorMessageFormatter;
171172
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
172173
import com.oracle.truffle.api.CallTarget;
173174
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
@@ -679,7 +680,15 @@ public RuntimeException raiseInvalidSyntax(Node location, String message, Object
679680
instance.setAttribute("text", section.isAvailable() ? source.getCharacters(section.getStartLine()) : "");
680681
instance.setAttribute("lineno", section.getStartLine());
681682
instance.setAttribute("offset", section.getStartColumn());
682-
instance.setAttribute("msg", section.getCharIndex() == source.getLength() ? "unexpected EOF while parsing" : message != null ? message : "invalid syntax");
683+
String msg;
684+
if (section.getCharIndex() == source.getLength()) {
685+
msg = "unexpected EOF while parsing";
686+
} else if (message != null) {
687+
msg = (new ErrorMessageFormatter()).format(message, arguments);
688+
} else {
689+
msg = "invalid syntax";
690+
}
691+
instance.setAttribute("msg", msg);
683692
throw PException.fromObject(instance, location);
684693
}
685694
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/PythonNodeFactory.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@
4141

4242
package com.oracle.graal.python.parser;
4343

44-
import com.oracle.graal.python.PythonLanguage;
45-
4644
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
4745
import com.oracle.graal.python.builtins.objects.function.PArguments;
4846
import com.oracle.graal.python.builtins.objects.function.Signature;
@@ -86,9 +84,11 @@ public final class PythonNodeFactory {
8684
private final NodeFactory nodeFactory;
8785
private final ScopeEnvironment scopeEnvironment;
8886
private final Source source;
87+
private final PythonParser.ParserErrorCallback errors;
8988

90-
public PythonNodeFactory(PythonLanguage language, Source source) {
91-
this.nodeFactory = NodeFactory.create(language);
89+
public PythonNodeFactory(PythonParser.ParserErrorCallback errors, Source source) {
90+
this.errors = errors;
91+
this.nodeFactory = NodeFactory.create(errors.getLanguage());
9292
this.scopeEnvironment = new ScopeEnvironment(nodeFactory);
9393
this.source = source;
9494
}
@@ -135,6 +135,11 @@ public SSTNode registerGlobal(String[] names, int startOffset, int endOffset) {
135135
public SSTNode registerNonLocal(String[] names, int startOffset, int endOffset) {
136136
ScopeInfo scopeInfo = scopeEnvironment.getCurrentScope();
137137
for (String name : names) {
138+
if (scopeInfo.findFrameSlot(name) != null) {
139+
// the expectation is that in the local context the variable can not have slot yet.
140+
// The slot is created by assignment or declaration
141+
throw errors.raiseInvalidSyntax(source, createSourceSection(startOffset, endOffset), "name '%s' is assigned to before nonlocal declaration", name);
142+
}
138143
scopeInfo.addExplicitNonlocalVariable(name);
139144
}
140145
return new SimpleSSTNode(SimpleSSTNode.Type.EMPTY, startOffset, endOffset);
@@ -210,7 +215,7 @@ public YieldExpressionSSTNode createYieldExpressionSSTNode(SSTNode value, boolea
210215
return new YieldExpressionSSTNode(value, isFrom, startOffset, endOffset);
211216
}
212217

213-
public Node createParserResult(SSTNode parserSSTResult, PythonParser.ParserMode mode, PythonParser.ParserErrorCallback errors, Frame currentFrame) {
218+
public Node createParserResult(SSTNode parserSSTResult, PythonParser.ParserMode mode, Frame currentFrame) {
214219
Node result;
215220
boolean isGen = false;
216221
Frame useFrame = currentFrame;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/PythonParserImpl.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private static Python3NewParser getPython3NewParser(Source source, ParserErrorCa
7272
lexer.addErrorListener(Builder.ERROR_LISTENER);
7373
Python3NewParser parser = new Python3NewParser(new CommonTokenStream(lexer));
7474
parser.setBuildParseTree(false);
75-
parser.factory = new PythonNodeFactory(errors.getLanguage(), source);
75+
parser.factory = new PythonNodeFactory(errors, source);
7676
parser.removeErrorListeners();
7777
parser.addErrorListener(Builder.ERROR_LISTENER);
7878
parser.setErrorHandler(new PythonErrorStrategy());
@@ -145,7 +145,7 @@ public Node parseN(ParserMode mode, ParserErrorCallback errors, Source source, F
145145
FrameDescriptor inlineLocals = mode == ParserMode.InlineEvaluation ? currentFrame.getFrameDescriptor() : null;
146146
// ANTLR parsing
147147
Python3NewParser parser = getPython3NewParser(source, errors);
148-
parser.factory = new PythonNodeFactory(errors.getLanguage(), source);
148+
parser.factory = new PythonNodeFactory(errors, source);
149149
SSTNode parserSSTResult = null;
150150

151151
try {
@@ -181,7 +181,7 @@ public Node parseN(ParserMode mode, ParserErrorCallback errors, Source source, F
181181
}
182182

183183
lastGlobalScope = parser.factory.getScopeEnvironment().getGlobalScope();
184-
return parser.factory.createParserResult(parserSSTResult, mode, errors, currentFrame);
184+
return parser.factory.createParserResult(parserSSTResult, mode, currentFrame);
185185

186186
}
187187

@@ -258,6 +258,9 @@ public String unescapeJavaString(String str) {
258258
}
259259

260260
private static PException handleParserError(ParserErrorCallback errors, Source source, Exception e) {
261+
if (e instanceof PException) {
262+
return (PException) e;
263+
}
261264
SourceSection section = PythonErrorStrategy.getPosition(source, e);
262265
// from parser we are getting RuntimeExceptions
263266
String message = e instanceof RuntimeException && e.getMessage() != null ? e.getMessage() : "invalid syntax";

0 commit comments

Comments
 (0)