Skip to content

Commit 8f01312

Browse files
committed
[GR-23226] More syntax checks
PullRequest: graalpython/1029
2 parents 3944116 + d184016 commit 8f01312

File tree

16 files changed

+1338
-1247
lines changed

16 files changed

+1338
-1247
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ public void not01() throws Exception {
554554

555555
@Test
556556
public void nonlocal02() throws Exception {
557-
checkTreeResult("nonlocal x");
557+
checkSyntaxError("nonlocal x");
558558
}
559559

560560
@Test

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -768,11 +768,6 @@ public void moduleDocTest() throws Exception {
768768
"\"\"\"MODULE A DOC\"\"\"\n" + "print(\"module A\")");
769769
}
770770

771-
@Test
772-
public void nonlocalTest() throws Exception {
773-
checkSerialization("nonlocal x");
774-
}
775-
776771
@Test
777772
public void numberBinTest() throws Exception {
778773
checkSerialization("0b101");
@@ -1084,7 +1079,7 @@ public void withTest() throws Exception {
10841079
}
10851080

10861081
@Test
1087-
public void yeildTest() throws Exception {
1082+
public void yieldTest() throws Exception {
10881083
checkSerialization("def f(): yield 1");
10891084
checkSerialization("def f(): yield");
10901085
checkSerialization("def f(): x += yield");

graalpython/com.oracle.graal.python.test/testData/goldenFiles/BasicTests/nonlocal02.tast

Lines changed: 0 additions & 8 deletions
This file was deleted.

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import java.util.Map.Entry;
4141
import java.util.ServiceLoader;
4242
import java.util.logging.Level;
43+
import java.util.regex.Matcher;
44+
import java.util.regex.Pattern;
4345

4446
import org.graalvm.nativeimage.ImageInfo;
4547

@@ -209,6 +211,8 @@ public final class Python3Core implements PythonCore {
209211
private static final TruffleLogger LOGGER = PythonLanguage.getLogger(Python3Core.class);
210212
private final String[] coreFiles;
211213

214+
public static final Pattern MISSING_PARENTHESES_PATTERN = Pattern.compile("^(print|exec) +([^(][^;]*).*");
215+
212216
private static final String[] initializeCoreFiles() {
213217
// Order matters!
214218
List<String> coreFiles = new ArrayList<>(Arrays.asList(
@@ -774,6 +778,22 @@ public RuntimeException raiseInvalidSyntax(PythonParser.ErrorType type, Node loc
774778
} else {
775779
msg = "invalid syntax";
776780
}
781+
if (section.isAvailable() && type == PythonParser.ErrorType.Generic) {
782+
Matcher matcher = MISSING_PARENTHESES_PATTERN.matcher(source.getCharacters(section.getStartLine()));
783+
if (matcher.matches()) {
784+
String fn = matcher.group(1);
785+
if (fn.equals("print")) {
786+
String arg = matcher.group(2).trim();
787+
String maybeEnd = "";
788+
if (arg.endsWith(",")) {
789+
maybeEnd = " end=\" \"";
790+
}
791+
msg = (new ErrorMessageFormatter()).format("Missing parentheses in call to 'print'. Did you mean print(%s%s)?", arg, maybeEnd);
792+
} else {
793+
msg = "Missing parentheses in call to 'exec'";
794+
}
795+
}
796+
}
777797
instance.setAttribute("msg", msg);
778798
throw PException.fromObject(instance, location);
779799
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public abstract class ErrorMessages {
166166
public static final String COUNT_FUNC_MATH = "count function in Math";
167167
public static final String COVERAGE_TRACKER_NOT_RUNNING = "coverage tracker not running";
168168
public static final String CREATING_SOCKETS_NOT_ALLOWED = "creating sockets not allowed";
169+
public static final String DEFAULT_EXCEPT_MUST_BE_LAST = "default 'except:' must be last";
169170
public static final String DESC_S_FOR_S_DOESNT_APPLY_TO_S = "descriptor '%s' for '%s' objects doesn't apply to '%s' object";
170171
public static final String DESCRIPTOR_REQUIRES_OBJ = "descriptor '%s' requires a '%s' object but received a '%p'";
171172
public static final String DICT_CHANGED_DURING_COMPARISON = "dictionary changed during comparison operation";
@@ -215,6 +216,7 @@ public abstract class ErrorMessages {
215216
public static final String FUNC_CONSTRUCTION_NOT_SUPPORTED = "function construction not supported for (%p, %p, %p, %p, %p, %p)";
216217
public static final String FUNC_TAKES_AT_LEAST_D_ARGS = "function takes at least %d arguments (%d given)";
217218
public static final String FUNC_TAKES_EXACTLY_D_ARGS = "function takes exaclty %d arguments (%d given)";
219+
public static final String GENERATOR_EXPR_MUST_BE_PARENTHESIZED = "Generator expression must be parenthesized";
218220
public static final String GENERATOR_IGNORED_EXIT = "generator ignored GeneratorExit";
219221
public static final String GENERATOR_RAISED_STOPITER = "generator raised StopIteration";
220222
public static final String GENERATOR_ALREADY_EXECUTING = "generator already executing";
@@ -329,6 +331,7 @@ public abstract class ErrorMessages {
329331
public static final String MUST_BE_STRINGS_NOT_P = "%s must be strings, not %p";
330332
public static final String MUST_SPECIFY_FILTERS = "Must specify filters for FORMAT_RAW";
331333
public static final String MUTATED_DURING_UPDATE = "%s mutated during update";
334+
public static final String NAME_IS_ASSIGNED_BEFORE_GLOBAL = "name '%s' is assigned to before global declaration";
332335
public static final String NAME_IS_ASSIGNED_BEFORE_NONLOCAL = "name '%s' is assigned to before nonlocal declaration";
333336
public static final String NAME_NOT_DEFINED = "name '%s' is not defined";
334337
public static final String NEED_BYTELIKE_OBJ = "decoding to str: need a bytes-like object, %p found";
@@ -343,6 +346,8 @@ public abstract class ErrorMessages {
343346
public static final String NO_CURRENT_FRAME = "%s: no current frame";
344347
public static final String NO_FUNCTION_FOUND = "no function %s%s found in %s";
345348
public static final String NO_SUCH_FILE_OR_DIR = "No such file or directory: '%s:/%s'";
349+
public static final String NONLOCAL_AND_GLOBAL = "name '%s' is nonlocal and global";
350+
public static final String NONLOCAL_AT_MODULE_LEVEL = "nonlocal declaration not allowed at module level";
346351
public static final String NON_HEX_DIGIT_FOUND = "Non-hexadecimal digit found";
347352
public static final String NON_STRING_IN_CODE_SLOT = "non-string found in code slot";
348353
public static final String NOT_A_ZIP_FILE = "not a Zip file: '%s'";

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ public SSTNode registerGlobal(String[] names, int startOffset, int endOffset) {
129129
ScopeInfo scopeInfo = scopeEnvironment.getCurrentScope();
130130
ScopeInfo globalScope = scopeEnvironment.getGlobalScope();
131131
for (String name : names) {
132+
if (scopeInfo.isExplicitNonlocalVariable(name)) {
133+
throw errors.raiseInvalidSyntax(source, createSourceSection(startOffset, endOffset), ErrorMessages.NONLOCAL_AND_GLOBAL, name);
134+
}
135+
if (scopeInfo.findFrameSlot(name) != null) {
136+
// The expectation is that in the local context the variable can not have slot yet.
137+
// The slot is created by assignment or declaration
138+
throw errors.raiseInvalidSyntax(source, createSourceSection(startOffset, endOffset), ErrorMessages.NAME_IS_ASSIGNED_BEFORE_GLOBAL, name);
139+
}
132140
scopeInfo.addExplicitGlobalVariable(name);
133141
globalScope.createSlotIfNotPresent(name);
134142
}
@@ -137,7 +145,13 @@ public SSTNode registerGlobal(String[] names, int startOffset, int endOffset) {
137145

138146
public SSTNode registerNonLocal(String[] names, int startOffset, int endOffset) {
139147
ScopeInfo scopeInfo = scopeEnvironment.getCurrentScope();
148+
if (scopeInfo.getScopeKind() == ScopeKind.Module) {
149+
throw errors.raiseInvalidSyntax(source, createSourceSection(startOffset, endOffset), ErrorMessages.NONLOCAL_AT_MODULE_LEVEL);
150+
}
140151
for (String name : names) {
152+
if (scopeInfo.isExplicitGlobalVariable(name)) {
153+
throw errors.raiseInvalidSyntax(source, createSourceSection(startOffset, endOffset), ErrorMessages.NONLOCAL_AND_GLOBAL, name);
154+
}
141155
if (scopeInfo.findFrameSlot(name) != null) {
142156
// the expectation is that in the local context the variable can not have slot yet.
143157
// The slot is created by assignment or declaration
@@ -173,6 +187,9 @@ public SSTNode createAssignment(SSTNode[] lhs, SSTNode rhs, int start, int stop)
173187
}
174188

175189
public SSTNode createAnnAssignment(SSTNode lhs, SSTNode type, SSTNode rhs, int start, int end) {
190+
if (lhs instanceof CollectionSSTNode) {
191+
throw errors.raiseInvalidSyntax(source, createSourceSection(lhs.getStartOffset(), lhs.getEndOffset()), "only single target (not tuple) can be annotated");
192+
}
176193
declareVar(lhs);
177194
if (!scopeEnvironment.getCurrentScope().hasAnnotations()) {
178195
scopeEnvironment.getCurrentScope().setHasAnnotations(true);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/antlr/DescriptiveBailErrorListener.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ public class DescriptiveBailErrorListener extends BaseErrorListener {
6565
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
6666
int line, int charPositionInLine,
6767
String msg, RecognitionException e) {
68-
68+
// If we already constructed the error, keep it, it should be the most specific one
69+
if (e instanceof EmptyRecognitionException) {
70+
throw e;
71+
}
6972
String entireMessage = e == null || e.getMessage() == null ? "invalid syntax" : e.getMessage();
7073

7174
IntervalSet expectedTokens = null;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/antlr/Python3.g4

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,11 @@ typedargslist [ArgDefListBuilder args]
544544
( ',' ( kwargsparameter[args] ','? )? )?
545545
| kwargsparameter[args] ','?
546546
)
547+
{
548+
if (!args.validateArgumentsAfterSplat()) {
549+
throw new PythonRecognitionException("named arguments must follow bare *", this, _input, $ctx, getCurrentToken());
550+
}
551+
}
547552
;
548553

549554
defparameter [ArgDefListBuilder args]
@@ -609,6 +614,11 @@ varargslist returns [ArgDefListBuilder result]
609614
( ',' (vkwargsparameter[args] ','? )? )?
610615
| vkwargsparameter[args] ','?
611616
)
617+
{
618+
if (!args.validateArgumentsAfterSplat()) {
619+
throw new PythonRecognitionException("named arguments must follow bare *", this, _input, $ctx, getCurrentToken());
620+
}
621+
}
612622
{ $result = args; }
613623
;
614624

@@ -768,15 +778,15 @@ flow_stmt
768778
b='break'
769779
{
770780
if (loopState == null) {
771-
throw new PythonRecognitionException("'break' outside loop", this, _input, _localctx, getCurrentToken());
781+
throw new PythonRecognitionException("'break' outside loop", this, _input, _localctx, $b);
772782
}
773783
push(new SimpleSSTNode(SimpleSSTNode.Type.BREAK, getStartIndex($b), getStopIndex($b)));
774784
loopState.containsBreak = true;
775785
}
776786
| c='continue'
777787
{
778788
if (loopState == null) {
779-
throw new PythonRecognitionException("'continue' not properly in loop", this, _input, _localctx, getCurrentToken());
789+
throw new PythonRecognitionException("'continue' not properly in loop", this, _input, _localctx, $c);
780790
}
781791
push(new SimpleSSTNode(SimpleSSTNode.Type.CONTINUE, getStartIndex($c), getStopIndex($c)));
782792
loopState.containsContinue = true;
@@ -1612,7 +1622,7 @@ argument [ArgListBuilder args] returns [SSTNode result]
16121622
}
16131623
test comp_for[$test.result, null, PythonBuiltinClassType.PGenerator, 0]
16141624
{
1615-
args.addArg($comp_for.result);
1625+
args.addNakedForComp($comp_for.result);
16161626
scopeEnvironment.popScope();
16171627
}
16181628
|

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/antlr/Python3.interp

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)