support icon indicating copy to clipboard operation
support copied to clipboard

[Bug] f-string with hasattr causes an import error

Open BertLindeman opened this issue 1 year ago • 12 comments

Describe the bug Using hasattr in an f-string causes an import error (There is no import error). The same data in a .format does not produce that error.

To reproduce Steps to reproduce the behavior:

  1. copy this program:
import mission_run_1
missions = [("1", irrelevant_name, "first"), ("2", "stop", "stop"),]
print(f"hasattr f-string {hasattr(missions[0][1], "run")}")  # import error
# print("hasattr format  {}".format(hasattr(missions[0][1], "run")))  # OK name error
  1. make sure you have a program loaded with the module name of the import
  2. run the problem code and see ImportError: no module named 'mission_run_1'
  3. comment the first print and uncomment the second
  4. Run again and see no ImportError, but the correct NameError

Expected behavior The same correct behavior for the f-string and the formatted one. And most of all: No incorrect flagging the import as error.

Took me quiet some time to assure that the import statement was correct 😄

Screenshots There is a saying that a picture is worth a 1000 words. Screenshots really help to identify and solve problems. None sofar.

Extra info

  1. Have looked at pybricks issues and micropython issues could not find something similar.
  2. I think the f-strings are re-written during compile, so the problem might be in that phase. Yet strange that the format I show does not have the problem.
  3. Tested on beta and stable versions.

BertLindeman avatar Nov 28 '24 09:11 BertLindeman

I assume this works with pybricksdev instead of Pybricks Code?

Sounds like a bug with the Python parser we use for import detection in Pybricks Code.

dlech avatar Nov 29 '24 15:11 dlech

I assume this works with pybricksdev instead of Pybricks Code?

Will try tonight.

[EDIT] Why not now.... @dlech

pybricksdev v1.0.0a52
pybricksdev run -n "prime beta" ble issue_1954_fstring_problem.py
Searching for prime beta...
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Python311\Scripts\pybricksdev.exe\__main__.py", line 7, in <module>
  File "C:\Python311\Lib\site-packages\pybricksdev\cli\__init__.py", line 446, in main
    asyncio.run(subparsers.choices[args.tool].tool.run(args))
  File "C:\Python311\Lib\asyncio\runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\pybricksdev\cli\__init__.py", line 204, in run
    await hub.run(script_path, args.wait)
  File "C:\Python311\Lib\site-packages\pybricksdev\connections\pybricks.py", line 572, in run
    mpy = await compile_multi_file(py_path, abi)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\pybricksdev\compile.py", line 121, in compile_multi_file
    finder.run_script(path)
  File "C:\Python311\Lib\modulefinder.py", line 153, in run_script
    self.load_module('__main__', fp, pathname, stuff)
  File "C:\Python311\Lib\modulefinder.py", line 332, in load_module
    co = compile(fp.read(), pathname, 'exec')
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "issue_1954_fstring_problem.py", line 4
    print(f"hasattr f-string {hasattr(missions[0][1], "run")}")  # import error
                                                       ^^^
SyntaxError: f-string: unmatched '('

BertLindeman avatar Nov 29 '24 16:11 BertLindeman

So in the end a user syntax error. But a nasty way of showing it. Fixed the quotes and so "fixed" the error.

BertLindeman avatar Nov 29 '24 17:11 BertLindeman

Thanks for checking. So more a bug in MicroPython for not catching the syntax error.

dlech avatar Nov 29 '24 21:11 dlech

Thanks for checking. So more a bug in MicroPython for not catching the syntax error.

Running MicroPython v1.22.1+ds-1build2 on 2024-04-01; linux [GCC 13.2.0] version this program (removed the comments)


import mission_run_1
missions = [("1", irrelevant_name, "first"), ("2", "stop", "stop"),]
print(f"hasattr f-string {hasattr(missions[0][1], "run")}")

Shows:

Traceback (most recent call last):
  File "issue_1954_fstring_problem_syntax.py", line 2, in <module>
  File "/home/bert/py/pybricks/issue/mission_run_1.py", line 3, in <module>
ImportError: no module named 'pybricks'

I do not know the phases but I would expect the syntax being found before the import being done.

Will look if I can get the "source" more simple to show that the syntax error is not catched.

Bert

BertLindeman avatar Nov 29 '24 21:11 BertLindeman

I see. The code works fine in MicroPython if there are no imports since MicroPython seems to allow " withing f-string with f" while standard Python does not.

pybricksdev reports a syntax error because it uses the CPython compiler to find imports.

Pybricks Code uses static analysis tool find imports and silently fails on syntax errors assuming that any syntax error it detects would also be a syntax error in MicroPython. But since this is not a syntax error in MicroPython, the program compiles and runs but the proper imports were not included because the import analyzer detected the syntax error.

dlech avatar Nov 29 '24 22:11 dlech

minimal test case:

import a
print(f"{"x"}")

dlech avatar Nov 29 '24 22:11 dlech

Simplified to:

print(f"{"run"}")
print("{"run"}")

The first line should report a syntax error, but in these versions the second line does.

Tested on micropython v1.22.1+ds-1build2 on 2024-04-01 and the same error I see on python3.12.3 I see no issue on Cpython. Will try to make one there.

BertLindeman avatar Nov 29 '24 22:11 BertLindeman

Added cpython issue 127428 Unexpected behavior in f-strings with nested quotes

BertLindeman avatar Nov 29 '24 22:11 BertLindeman

JelleZijlstra pointed to PEP 701 – Syntactic formalization of f-strings So in python 3.12 this is correct syntax.

[EDIT] With this in mind I re-tested the f-string on MicroPython v1.22.1+ds-1build2 on 2024-04-01; linux [GCC 13.2.0] version

>>> print(f"{"run"}")
run

So leave this until pybricks uses micropython 1.22? The circumvention to use a string.format is there.

BertLindeman avatar Nov 30 '24 08:11 BertLindeman

I don't see how updating the MicroPython version will make a difference. The problem is that the Python parser for finding imports in Pybricks Code hasn't been updated to support the Python 3.12 syntax. MicroPython isn't involved in this part of the code at all.

dlech avatar Nov 30 '24 16:11 dlech

I stand corrected 😄

BertLindeman avatar Nov 30 '24 17:11 BertLindeman