rope icon indicating copy to clipboard operation
rope copied to clipboard

MismatchedTokenError when parsing TypeVar with constraints in Python 3.12

Open plutonium-94 opened this issue 6 months ago • 0 comments

Describe the bug When using Python 3.12’s new syntax for TypeVar with constraints, rope fails to correctly parse the function definition and raises a MismatchedTokenError.

To Reproduce Run the code below:

from rope.base.project import Project
from rope.refactor.inline import InlineVariable

project = Project('.', ropefolder=None)
file = project.root.create_file('file.py')
file.write('''\
s = None
print(s)

def _[T: (A, B)](x):
    pass
''')
try:
    changes = InlineVariable(project, file, 0).get_changes('s')
    project.do(changes)
    print(file.read())
finally:
    file.remove()
    project.close()

And get the following error:

Traceback (most recent call last):
  File "Lib\site-packages\rope\refactor\patchedast.py", line 837, in consume
    new_offset = self.source.index(token, self.offset)
ValueError: substring not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "rope_bug.py", line 14, in <module>
    changes = InlineVariable(project, file, 0).get_changes('s')
              ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\refactor\inline.py", line 241, in __init__
    super().__init__(*args, **kwds)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\refactor\inline.py", line 82, in __init__
    self.pyname = _get_pyname(project, resource, offset)
                  ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\refactor\inline.py", line 684, in _get_pyname
    pyname = evaluate.eval_location(pymodule, offset)
  File "Lib\site-packages\rope\base\evaluate.py", line 22, in eval_location
    return eval_location2(pymodule, offset)[1]
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\base\evaluate.py", line 28, in eval_location2
    return pyname_finder.get_primary_and_pyname_at(offset)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "Lib\site-packages\rope\base\evaluate.py", line 95, in get_primary_and_pyname_at
    holding_scope = self.module_scope.get_inner_scope_for_offset(offset)
  File "Lib\site-packages\rope\base\pyscopes.py", line 156, in get_inner_scope_for_offset
    return self._scope_finder.get_holding_scope_for_offset(self, offset)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\base\pyscopes.py", line 310, in get_holding_scope_for_offset
    if inner_scope.in_region(offset):
       ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "Lib\site-packages\rope\base\pyscopes.py", line 114, in in_region
    region = self.get_region()
  File "Lib\site-packages\rope\base\pyscopes.py", line 103, in get_region
    self._calculate_scope_regions_for_module()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "Lib\site-packages\rope\base\pyscopes.py", line 109, in _calculate_scope_regions_for_module
    self._get_global_scope()._calculate_scope_regions()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "Lib\site-packages\rope\base\utils\__init__.py", line 12, in _wrapper
    setattr(self, name, func(self, *args, **kwds))
                        ~~~~^^^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\base\pyscopes.py", line 140, in _calculate_scope_regions
    patchedast.patch_ast(self.pyobject.get_ast(), source)
    ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\refactor\patchedast.py", line 37, in patch_ast
    walker(node)
    ~~~~~~^^^^^^
  File "Lib\site-packages\rope\refactor\patchedast.py", line 80, in __call__
    return method(node)
  File "Lib\site-packages\rope\refactor\patchedast.py", line 625, in _Module
    self._handle(node, list(node.body), eat_spaces=True)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\refactor\patchedast.py", line 113, in _handle
    self(child)
    ~~~~^^^^^^^
  File "Lib\site-packages\rope\refactor\patchedast.py", line 80, in __call__
    return method(node)
  File "Lib\site-packages\rope\refactor\patchedast.py", line 500, in _FunctionDef
    self._handle_function_def_node(node, is_async=False)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\refactor\patchedast.py", line 497, in _handle_function_def_node
    self._handle(node, children)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "Lib\site-packages\rope\refactor\patchedast.py", line 142, in _handle
    start = self._handle_parens(children, start, formats)
  File "Lib\site-packages\rope\refactor\patchedast.py", line 164, in _handle_parens
    new_end = self.source.consume(")")[1]
              ~~~~~~~~~~~~~~~~~~~^^^^^
  File "Lib\site-packages\rope\refactor\patchedast.py", line 843, in consume
    raise MismatchedTokenError(
        f"Token <{token}> at {self._get_location()} cannot be matched"
    )
rope.refactor.patchedast.MismatchedTokenError: Token <)> at (5, 8) cannot be matched

It appears that the new parentheses in the TypeVar constraint are not handled correctly.

Editor information:

  • Project Python version: 3.13.3
  • Rope Python version: 3.13.3
  • Rope version: 1.13.0
  • Text editor/IDE and version: None (rope used as a library)

plutonium-94 avatar Jun 10 '25 21:06 plutonium-94