Skip to content

gh-124960: Fixed barry_as_FLUFL future flag does not work in new REPL #124999

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

Merged
merged 9 commits into from
Oct 14, 2024
10 changes: 8 additions & 2 deletions Lib/_pyrepl/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,13 @@ def _excepthook(self, typ, value, tb):

def runsource(self, source, filename="<input>", symbol="single"):
try:
tree = ast.parse(source)
tree = self.compile.compiler(
source,
filename,
"exec",
ast.PyCF_ONLY_AST,
incomplete_input=False,
)
except (SyntaxError, OverflowError, ValueError):
self.showsyntaxerror(filename, source=source)
return False
Expand All @@ -185,7 +191,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
the_symbol = symbol if stmt is last_stmt else "exec"
item = wrapper([stmt])
try:
code = self.compile.compiler(item, filename, the_symbol, dont_inherit=True)
code = self.compile.compiler(item, filename, the_symbol)
except SyntaxError as e:
if e.args[0] == "'await' outside function":
python = os.path.basename(sys.executable)
Expand Down
7 changes: 5 additions & 2 deletions Lib/codeop.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
# Caveat emptor: These flags are undocumented on purpose and depending
# on their effect outside the standard library is **unsupported**.
PyCF_DONT_IMPLY_DEDENT = 0x200
PyCF_ONLY_AST = 0x400
PyCF_ALLOW_INCOMPLETE_INPUT = 0x4000

def _maybe_compile(compiler, source, filename, symbol):
Expand Down Expand Up @@ -109,12 +110,14 @@ class Compile:
def __init__(self):
self.flags = PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT

def __call__(self, source, filename, symbol, **kwargs):
flags = self.flags
def __call__(self, source, filename, symbol, flags=0, **kwargs):
flags |= self.flags
if kwargs.get('incomplete_input', True) is False:
flags &= ~PyCF_DONT_IMPLY_DEDENT
flags &= ~PyCF_ALLOW_INCOMPLETE_INPUT
codeob = compile(source, filename, symbol, flags, True)
if flags & PyCF_ONLY_AST:
return codeob # this is an ast.Module in this case
for feature in _features:
if codeob.co_flags & feature.compiler_flag:
self.flags |= feature.compiler_flag
Expand Down
27 changes: 26 additions & 1 deletion Lib/test/test_pyrepl/test_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,38 @@ def test_runsource_shows_syntax_error_for_failed_compilation(self):

def test_no_active_future(self):
console = InteractiveColoredConsole()
source = "x: int = 1; print(__annotate__(1))"
source = dedent("""\
x: int = 1
print(__annotate__(1))
""")
f = io.StringIO()
with contextlib.redirect_stdout(f):
result = console.runsource(source)
self.assertFalse(result)
self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n")

def test_future_annotations(self):
console = InteractiveColoredConsole()
source = dedent("""\
from __future__ import annotations
def g(x: int): ...
print(g.__annotations__)
""")
f = io.StringIO()
with contextlib.redirect_stdout(f):
result = console.runsource(source)
self.assertFalse(result)
self.assertEqual(f.getvalue(), "{'x': 'int'}\n")

def test_future_barry_as_flufl(self):
console = InteractiveColoredConsole()
f = io.StringIO()
with contextlib.redirect_stdout(f):
result = console.runsource("from __future__ import barry_as_FLUFL\n")
result = console.runsource("""print("black" <> 'blue')\n""")
Comment on lines +148 to +150
Copy link
Contributor

Choose a reason for hiding this comment

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

This is required because the parser can't deal with the future import changing the flags inside a run. This is true now:

❯ ./python.exe -c "from __future__ import barry_as_FLUFL; 1<>0"
  File "<string>", line 1
    from __future__ import barry_as_FLUFL; 1<>0
                                            ^^
SyntaxError: invalid syntax

but was also true in the old parser:

python3.9 -Xoldparser -c "from __future__ import barry_as_FLUFL; 1<>0"
  File "<string>", line 1
    from __future__ import barry_as_FLUFL; 1<>0
                                             ^
SyntaxError: invalid syntax

The consequence of that with PyREPL is that you cannot paste multiline blocks with that future and make them run, because PyREPL executes everything save for the last line in one go.

PyREPL isn't doing anything wrong here, it's just exposing the bug that was always there. Therefore, I'm not sure we should be working around this pasting thing for this one future. Other futures work fine, as you can see in the test added above.

self.assertFalse(result)
self.assertEqual(f.getvalue(), "True\n")


class TestMoreLines(unittest.TestCase):
def test_invalid_syntax_single_line(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix support for the ``barry_as_FLUFL`` future flag in the new REPL.
Loading