Skip to content

Commit 8a1506b

Browse files
author
Adam Hrbac
committed
[GR-46597] Make future annotations inherited by exec
PullRequest: graalpython/2893
2 parents ce7922c + ed42e07 commit 8a1506b

File tree

8 files changed

+248
-60
lines changed

8 files changed

+248
-60
lines changed

graalpython/com.oracle.graal.python.pegparser/src/com/oracle/graal/python/pegparser/FutureFeature.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -40,7 +40,35 @@
4040
*/
4141
package com.oracle.graal.python.pegparser;
4242

43+
import java.util.EnumSet;
44+
4345
public enum FutureFeature {
44-
ANNOTATIONS,
45-
BARRY_AS_BDFL
46+
ANNOTATIONS(0x1000000),
47+
BARRY_AS_BDFL(0x400000);
48+
49+
public final int flagValue;
50+
public static final int ALL_FLAGS;
51+
52+
private static final FutureFeature[] VALUES = FutureFeature.values();
53+
static {
54+
int flags = 0;
55+
for (FutureFeature feat : VALUES) {
56+
flags |= feat.flagValue;
57+
}
58+
ALL_FLAGS = flags;
59+
}
60+
61+
FutureFeature(int flagValue) {
62+
this.flagValue = flagValue;
63+
}
64+
65+
public static EnumSet<FutureFeature> fromFlags(int flags) {
66+
EnumSet<FutureFeature> set = EnumSet.noneOf(FutureFeature.class);
67+
for (FutureFeature feat : VALUES) {
68+
if ((feat.flagValue & flags) != 0) {
69+
set.add(feat);
70+
}
71+
}
72+
return set;
73+
}
4674
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.test.basic;
42+
43+
import static com.oracle.graal.python.test.PythonTests.assertPrints;
44+
45+
import org.junit.Test;
46+
47+
public class FutureAnnotationTests {
48+
@Test
49+
public void withoutEvaluates() {
50+
assertPrints("hello\n", "def f() -> print('hello'): pass");
51+
}
52+
53+
@Test
54+
public void withDoesNotEvaluate() {
55+
assertPrints("", "from __future__ import annotations\ndef f() -> print('hello'): pass");
56+
}
57+
58+
@Test
59+
public void worksInExec() {
60+
assertPrints("hello\n", "exec('def f() -> print(\\'hello\\'): pass')");
61+
assertPrints("", "exec('from __future__ import annotations\\ndef f() -> print(\\'hello\\'): pass')");
62+
}
63+
64+
@Test
65+
public void execInherits() {
66+
assertPrints("", "from __future__ import annotations\nexec('def f() -> print(\\'hello\\'): pass')");
67+
}
68+
69+
}

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/compiler/CompilerTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import com.oracle.graal.python.compiler.CompilationUnit;
5959
import com.oracle.graal.python.compiler.Compiler;
6060
import com.oracle.graal.python.pegparser.ErrorCallback;
61+
import com.oracle.graal.python.pegparser.FutureFeature;
6162
import com.oracle.graal.python.pegparser.InputType;
6263
import com.oracle.graal.python.pegparser.Parser;
6364
import com.oracle.graal.python.pegparser.sst.ModTy;
@@ -1096,7 +1097,7 @@ private static CodeUnit assemble(String src, InputType type) {
10961097
Parser parser = Compiler.createParser(src, errorCallback, type, false);
10971098
ModTy result = (ModTy) parser.parse();
10981099
Compiler compiler = new Compiler(errorCallback);
1099-
CompilationUnit cu = compiler.compile(result, EnumSet.noneOf(Compiler.Flags.class), 2);
1100+
CompilationUnit cu = compiler.compile(result, EnumSet.noneOf(Compiler.Flags.class), 2, EnumSet.noneOf(FutureFeature.class));
11001101
return cu.assemble();
11011102
}
11021103

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 101 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535

3636
import java.io.IOException;
3737
import java.nio.file.InvalidPathException;
38+
import java.util.ArrayList;
3839
import java.util.Arrays;
3940
import java.util.EnumSet;
41+
import java.util.HashSet;
4042
import java.util.List;
4143
import java.util.concurrent.ConcurrentHashMap;
4244
import java.util.concurrent.Semaphore;
@@ -74,6 +76,7 @@
7476
import com.oracle.graal.python.nodes.exception.TopLevelExceptionHandler;
7577
import com.oracle.graal.python.nodes.frame.GetFrameLocalsNode;
7678
import com.oracle.graal.python.nodes.frame.MaterializeFrameNode;
79+
import com.oracle.graal.python.pegparser.FutureFeature;
7780
import com.oracle.graal.python.pegparser.InputType;
7881
import com.oracle.graal.python.pegparser.NodeFactory;
7982
import com.oracle.graal.python.pegparser.Parser;
@@ -132,8 +135,11 @@
132135
implementationName = PythonLanguage.IMPLEMENTATION_NAME, //
133136
version = PythonLanguage.VERSION, //
134137
characterMimeTypes = {PythonLanguage.MIME_TYPE,
135-
PythonLanguage.MIME_TYPE_COMPILE0, PythonLanguage.MIME_TYPE_COMPILE1, PythonLanguage.MIME_TYPE_COMPILE2,
136-
PythonLanguage.MIME_TYPE_EVAL0, PythonLanguage.MIME_TYPE_EVAL1, PythonLanguage.MIME_TYPE_EVAL2}, //
138+
"text/x-python-\0\u0000-eval", "text/x-python-\0\u0000-compile", "text/x-python-\1\u0000-eval", "text/x-python-\1\u0000-compile", "text/x-python-\2\u0000-eval",
139+
"text/x-python-\2\u0000-compile", "text/x-python-\0\u0100-eval", "text/x-python-\0\u0100-compile", "text/x-python-\1\u0100-eval", "text/x-python-\1\u0100-compile",
140+
"text/x-python-\2\u0100-eval", "text/x-python-\2\u0100-compile", "text/x-python-\0\u0040-eval", "text/x-python-\0\u0040-compile", "text/x-python-\1\u0040-eval",
141+
"text/x-python-\1\u0040-compile", "text/x-python-\2\u0040-eval", "text/x-python-\2\u0040-compile", "text/x-python-\0\u0140-eval", "text/x-python-\0\u0140-compile",
142+
"text/x-python-\1\u0140-eval", "text/x-python-\1\u0140-compile", "text/x-python-\2\u0140-eval", "text/x-python-\2\u0140-compile"}, //
137143
byteMimeTypes = {PythonLanguage.MIME_TYPE_BYTECODE}, //
138144
defaultMimeType = PythonLanguage.MIME_TYPE, //
139145
dependentLanguages = {"nfi", "llvm"}, //
@@ -206,14 +212,49 @@ public final class PythonLanguage extends TruffleLanguage<PythonContext> {
206212
public static final int API_VERSION = 1013;
207213

208214
public static final String MIME_TYPE = "text/x-python";
209-
static final String MIME_TYPE_COMPILE0 = "text/x-python-compile0";
210-
static final String MIME_TYPE_COMPILE1 = "text/x-python-compile1";
211-
static final String MIME_TYPE_COMPILE2 = "text/x-python-compile2";
212-
static final String[] MIME_TYPE_COMPILE = {PythonLanguage.MIME_TYPE_COMPILE0, PythonLanguage.MIME_TYPE_COMPILE1, PythonLanguage.MIME_TYPE_COMPILE2};
213-
static final String[] MIME_TYPE_EVAL = {PythonLanguage.MIME_TYPE_EVAL0, PythonLanguage.MIME_TYPE_EVAL1, PythonLanguage.MIME_TYPE_EVAL2};
214-
static final String MIME_TYPE_EVAL0 = "text/x-python-eval0";
215-
static final String MIME_TYPE_EVAL1 = "text/x-python-eval1";
216-
static final String MIME_TYPE_EVAL2 = "text/x-python-eval2";
215+
216+
// the syntax for mime types is as follows
217+
// <mime> ::= "text/x-python-" <optlevel> <flags> "-" kind
218+
// <kind> ::= "compile" | "eval"
219+
// <optlevel> ::= "\0" | "\1" | "\2"
220+
// <flags> ::= "\u0040" | "\u0100" | "\u0140" | "\u0000"
221+
// where 0100 implies annotations, and 0040 implies barry_as_flufl
222+
static final String MIME_PREFIX = MIME_TYPE + "-";
223+
static final int OPT_FLAGS_LEN = 2; // 1 char is optlevel, 1 char is flags
224+
static final String MIME_KIND_COMPILE = "compile";
225+
static final String MIME_KIND_EVAL = "eval";
226+
// Since flags are greater than the highest unicode codepoint, we shift them into more
227+
// reasonable values in the mime type. 4 hex digits
228+
static final int MIME_FLAG_SHIFTBY = 4 * 4;
229+
// a dash follows after the opt flag pair
230+
static final int MIME_KIND_START = MIME_PREFIX.length() + OPT_FLAGS_LEN + 1;
231+
232+
private static boolean mimeTypesComplete(ArrayList<String> mimeJavaStrings) {
233+
ArrayList<String> mimeTypes = new ArrayList<>();
234+
FutureFeature[] all = FutureFeature.values();
235+
for (int flagset = 0; flagset < (1 << all.length); ++flagset) {
236+
int flags = 0;
237+
for (int i = 0; i < all.length; ++i) {
238+
if ((flagset & (1 << i)) != 0) {
239+
flags |= all[i].flagValue;
240+
}
241+
}
242+
for (int opt = 0; opt <= 2; opt++) {
243+
for (String typ : new String[]{MIME_KIND_EVAL, MIME_KIND_COMPILE}) {
244+
mimeTypes.add(MIME_PREFIX + optFlagsToMime(opt, flags) + "-" + typ);
245+
mimeJavaStrings.add(String.format("\"%s\\%d\\u%04x-%s\"", MIME_PREFIX, opt, flags >> MIME_FLAG_SHIFTBY, typ));
246+
}
247+
}
248+
}
249+
HashSet<String> currentMimeTypes = new HashSet<>(List.of(PythonLanguage.class.getAnnotation(Registration.class).characterMimeTypes()));
250+
return currentMimeTypes.containsAll(mimeTypes);
251+
}
252+
253+
static {
254+
ArrayList<String> mimeJavaStrings = new ArrayList<>();
255+
assert mimeTypesComplete(mimeJavaStrings) : "Expected all of {" + String.join(", ", mimeJavaStrings) + "} in the PythonLanguage characterMimeTypes";
256+
}
257+
217258
public static final String MIME_TYPE_BYTECODE = "application/x-python-bytecode";
218259

219260
public static final TruffleString[] T_DEFAULT_PYTHON_EXTENSIONS = new TruffleString[]{T_PY_EXTENSION, tsLiteral(".pyc")};
@@ -388,24 +429,26 @@ protected void initializeContext(PythonContext context) {
388429
context.initialize();
389430
}
390431

391-
public static String getCompileMimeType(int optimize) {
392-
if (optimize <= 0) {
393-
return MIME_TYPE_COMPILE0;
394-
} else if (optimize == 1) {
395-
return MIME_TYPE_COMPILE1;
396-
} else {
397-
return MIME_TYPE_COMPILE2;
432+
private static String optFlagsToMime(int optimize, int flags) {
433+
if (optimize < 0) {
434+
optimize = 0;
435+
} else if (optimize > 2) {
436+
optimize = 2;
398437
}
438+
String optField = new String(new byte[]{(byte) optimize});
439+
String flagField = new String(new int[]{(flags & FutureFeature.ALL_FLAGS) >> MIME_FLAG_SHIFTBY}, 0, 1);
440+
assert flagField.length() == 1 : "flags in mime type ended up a surrogate";
441+
return optField + flagField;
399442
}
400443

401-
public static String getEvalMimeType(int optimize) {
402-
if (optimize <= 0) {
403-
return MIME_TYPE_EVAL0;
404-
} else if (optimize == 1) {
405-
return MIME_TYPE_EVAL1;
406-
} else {
407-
return MIME_TYPE_EVAL2;
408-
}
444+
public static String getCompileMimeType(int optimize, int flags) {
445+
String optFlags = optFlagsToMime(optimize, flags);
446+
return MIME_PREFIX + optFlags + "-compile";
447+
}
448+
449+
public static String getEvalMimeType(int optimize, int flags) {
450+
String optFlags = optFlagsToMime(optimize, flags);
451+
return MIME_PREFIX + optFlags + "-eval";
409452
}
410453

411454
@Override
@@ -417,7 +460,7 @@ protected CallTarget parse(ParsingRequest request) {
417460
throw new IllegalStateException("parse with arguments not allowed for interactive sources");
418461
}
419462
InputType inputType = source.isInteractive() ? InputType.SINGLE : InputType.FILE;
420-
return parse(context, source, inputType, true, 0, source.isInteractive(), request.getArgumentNames());
463+
return parse(context, source, inputType, true, 0, source.isInteractive(), request.getArgumentNames(), EnumSet.noneOf(FutureFeature.class));
421464
}
422465
if (!request.getArgumentNames().isEmpty()) {
423466
throw new IllegalStateException("parse with arguments is only allowed for " + MIME_TYPE + " mime type");
@@ -452,19 +495,28 @@ protected CallTarget parse(ParsingRequest request) {
452495
PBytecodeRootNode rootNode = PBytecodeRootNode.create(this, code, source);
453496
return PythonUtils.getOrCreateCallTarget(rootNode);
454497
}
455-
for (int optimize = 0; optimize < MIME_TYPE_EVAL.length; optimize++) {
456-
if (MIME_TYPE_EVAL[optimize].equals(source.getMimeType())) {
457-
assert !source.isInteractive();
458-
return parse(context, source, InputType.EVAL, false, optimize, false, null);
459-
}
498+
499+
String mime = source.getMimeType();
500+
String prefix = mime.substring(0, MIME_PREFIX.length());
501+
if (!prefix.equals(MIME_PREFIX)) {
502+
throw CompilerDirectives.shouldNotReachHere("unknown mime type: " + mime);
460503
}
461-
for (int optimize = 0; optimize < MIME_TYPE_COMPILE.length; optimize++) {
462-
if (MIME_TYPE_COMPILE[optimize].equals(source.getMimeType())) {
463-
assert !source.isInteractive();
464-
return parse(context, source, InputType.FILE, false, optimize, false, null);
465-
}
504+
String kind = mime.substring(MIME_KIND_START);
505+
InputType type;
506+
if (kind.equals(MIME_KIND_COMPILE)) {
507+
type = InputType.FILE;
508+
} else if (kind.equals(MIME_KIND_EVAL)) {
509+
type = InputType.EVAL;
510+
} else {
511+
throw CompilerDirectives.shouldNotReachHere("unknown compilation kind: " + kind + " from mime type: " + mime);
512+
}
513+
int optimize = mime.codePointAt(MIME_PREFIX.length());
514+
int flags = mime.codePointAt(MIME_PREFIX.length() + 1) << MIME_FLAG_SHIFTBY;
515+
if (0 > optimize || optimize > 2 || (flags & ~FutureFeature.ALL_FLAGS) != 0) {
516+
throw CompilerDirectives.shouldNotReachHere("Invalid value for optlevel or flags: " + optimize + "," + flags + " from mime type: " + mime);
466517
}
467-
throw CompilerDirectives.shouldNotReachHere("unknown mime type: " + source.getMimeType());
518+
assert !source.isInteractive();
519+
return parse(context, source, type, false, optimize, false, null, FutureFeature.fromFlags(flags));
468520
}
469521

470522
private static Source tryLoadSource(PythonContext context, CodeUnit code, boolean internal, String path) {
@@ -475,13 +527,14 @@ private static Source tryLoadSource(PythonContext context, CodeUnit code, boolea
475527
}
476528
}
477529

478-
public RootCallTarget parse(PythonContext context, Source source, InputType type, boolean topLevel, int optimize, boolean interactiveTerminal, List<String> argumentNames) {
530+
public RootCallTarget parse(PythonContext context, Source source, InputType type, boolean topLevel, int optimize, boolean interactiveTerminal, List<String> argumentNames,
531+
EnumSet<FutureFeature> futureFeatures) {
479532
RaisePythonExceptionErrorCallback errorCb = new RaisePythonExceptionErrorCallback(source, PythonOptions.isPExceptionWithJavaStacktrace(this));
480533
try {
481534
Parser parser = Compiler.createParser(source.getCharacters().toString(), errorCb, type, interactiveTerminal);
482535
ModTy mod = (ModTy) parser.parse();
483536
assert mod != null;
484-
return compileForBytecodeInterpreter(context, mod, source, topLevel, optimize, argumentNames, errorCb);
537+
return compileForBytecodeInterpreter(context, mod, source, topLevel, optimize, argumentNames, errorCb, futureFeatures);
485538
} catch (PException e) {
486539
if (topLevel) {
487540
PythonUtils.getOrCreateCallTarget(new TopLevelExceptionHandler(this, e)).call();
@@ -492,7 +545,13 @@ public RootCallTarget parse(PythonContext context, Source source, InputType type
492545

493546
@TruffleBoundary
494547
public RootCallTarget compileForBytecodeInterpreter(PythonContext context, ModTy mod, Source source, boolean topLevel, int optimize, List<String> argumentNames,
495-
RaisePythonExceptionErrorCallback errorCallback) {
548+
RaisePythonExceptionErrorCallback errorCallback, int flags) {
549+
return compileForBytecodeInterpreter(context, mod, source, topLevel, optimize, argumentNames, errorCallback, FutureFeature.fromFlags(flags));
550+
}
551+
552+
@TruffleBoundary
553+
public RootCallTarget compileForBytecodeInterpreter(PythonContext context, ModTy mod, Source source, boolean topLevel, int optimize, List<String> argumentNames,
554+
RaisePythonExceptionErrorCallback errorCallback, EnumSet<FutureFeature> futureFeatures) {
496555
RaisePythonExceptionErrorCallback errorCb = errorCallback;
497556
if (errorCb == null) {
498557
errorCb = new RaisePythonExceptionErrorCallback(source, PythonOptions.isPExceptionWithJavaStacktrace(this));
@@ -503,7 +562,7 @@ public RootCallTarget compileForBytecodeInterpreter(PythonContext context, ModTy
503562
if (hasArguments) {
504563
mod = transformASTForExecutionWithArguments(argumentNames, mod);
505564
}
506-
CompilationUnit cu = compiler.compile(mod, EnumSet.noneOf(Compiler.Flags.class), optimize);
565+
CompilationUnit cu = compiler.compile(mod, EnumSet.noneOf(Compiler.Flags.class), optimize, futureFeatures);
507566
CodeUnit co = cu.assemble();
508567
RootNode rootNode = PBytecodeRootNode.create(this, co, source, errorCb);
509568
if (topLevel) {
@@ -588,7 +647,7 @@ public String getName() {
588647
@Override
589648
public ExecutableNode parse(InlineParsingRequest request) {
590649
PythonContext context = PythonContext.get(null);
591-
RootCallTarget callTarget = parse(context, request.getSource(), InputType.EVAL, false, 0, false, null);
650+
RootCallTarget callTarget = parse(context, request.getSource(), InputType.EVAL, false, 0, false, null, EnumSet.noneOf(FutureFeature.class));
592651
return new ExecutableNode(this) {
593652
@Child private GilNode gilNode = GilNode.create();
594653
@Child private GenericInvokeNode invokeNode = GenericInvokeNode.create();

0 commit comments

Comments
 (0)