Skip to content

Unstable AttributeError in script.goto() when Analyzing Decorated Methods #2077

@Hoblovski

Description

@Hoblovski

Summary

The jedi.Script.goto() function occasionally fails with an AttributeError: 'NoneType' object has no attribute 'type' when analyzing a method decorated by a dynamically chosen decorator.

This exception is inconsistent and unstable; it occurs intermittently, even with identical code and environment configurations.

Expected Behavior

In our case, script.goto() should consistently succeed and return the correct definition for the symbol at the target position.

Even in the case of failure, script.goto() should return an empty list ([]) instead of raising an unhandled exception.

Observed Behavior

  1. Intermittent Crash: Calling script.goto(line, column, follow_imports=True) repeatedly on a target symbol within a decorated method results in an intermittent AttributeError: 'NoneType' object has no attribute 'type'.

Environment

Linux. Clean conda environment just with the latest (master) jedi.

Reproduction

Just run the below code in a clean directory.

import jedi
import os
import sys

##############################################################################
# data
PREFIX = "testwork"
CACHE_FILE_PATH = PREFIX + "/" + "cache.py"
MAIN_FILE_PATH = PREFIX + "/" + "main.py"
CACHE_PY_CONTENT = """\
import os

def deco1(maxsize):
    def func_wrapper(func):
        def wrapper(*args, **kwargs):
            print('deco1')
            retval = func(*args, **kwargs)
            return retval
        return wrapper
    return func_wrapper


def deco2(func):
    print('deco2')
    return func

if os.getenv('CHOICE') == "deco1":
    deco = deco1(1000)
else:
    deco = deco2
"""
MAIN_PY_CONTENT = """\
from cache import deco

class Expr:
    flag = True

    __slots__ = []

    def __new__(cls, expr, *args, **kwargs):
        obj = object.__new__(cls)
        obj.args = args 
        return obj

    @property
    def func(self):
        return self.__class__

    @deco
    def contains(self, expr):
        if expr.flag:
            pass
        obj = self.func(expr, *self.args[1:])
        return self.contains(obj)

    def __contains__(self, other):
        result = self.contains(other)
        return result
"""
TARGET_POS = (19, 17)  # points to the get_data in process()

##############################################################################
# init
print(f"Creating files: {CACHE_FILE_PATH} and {MAIN_FILE_PATH}...")
project = jedi.Project(path=PREFIX, environment_path=None)
os.makedirs(PREFIX, exist_ok=True)
with open(CACHE_FILE_PATH, "w") as f:
    f.write(CACHE_PY_CONTENT.strip())
with open(MAIN_FILE_PATH, "w") as f:
    f.write(MAIN_PY_CONTENT.strip())
print("Files created successfully.")

##############################################################################
# run
script = jedi.Script(code=MAIN_PY_CONTENT, path=MAIN_FILE_PATH, project=project)
while True:
    try:
        definitions = script.goto(*TARGET_POS, follow_imports=True)
        definition = definitions[0]
    except AttributeError as e:
        raise e
    except Exception as e:
        continue
    print(definition)
    print(definition.full_name, definition.module_path, definition.line, definition.column)

It should fail like
(if the script runs fine, just try a couple more rounds and it'll fail. It's unstable.)

$ python main.py
Creating files: testwork/cache.py and testwork/main.py...
Files created successfully.
Traceback (most recent call last):
  File "/home/den/Programs/test/testjedi2/main.py", line 81, in <module>
    raise e
  File "/home/den/Programs/test/testjedi2/main.py", line 78, in <module>
    definitions = script.goto(*TARGET_POS, follow_imports=True)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/api/helpers.py", line 487, in wrapper
    return func(self, line, column, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/api/__init__.py", line 300, in goto
    names = list(name.goto())
                 ^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/names.py", line 205, in goto
    values = infer_call_of_leaf(context, name, cut_own_trailer=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/helpers.py", line 104, in infer_call_of_leaf
    values = context.infer_node(base)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/context.py", line 224, in infer_node
    return infer_node(self, node)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 157, in infer_node
    return _infer_node_if_inferred(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 170, in _infer_node_if_inferred
    return _infer_node_cached(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/cache.py", line 44, in wrapper
    rv = function(obj, *args, **kwargs)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 175, in _infer_node_cached
    return _infer_node(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/debug.py", line 81, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 83, in wrapper
    return func(context, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 185, in _infer_node
    return infer_atom(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 305, in infer_atom
    return context.py__getattribute__(atom, position=position)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/context.py", line 77, in py__getattribute__
    values = ValueSet.from_sets(name.infer() for name in names)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/base_value.py", line 430, in from_sets
    for set_ in sets:
                ^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/context.py", line 77, in <genexpr>
    values = ValueSet.from_sets(name.infer() for name in names)
                                ^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/plugins/__init__.py", line 21, in wrapper
    return built_functions[public_name](*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/plugins/pytest.py", line 76, in wrapper
    return func(param_name)
           ^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/names.py", line 528, in infer
    values = dynamic_param_lookup(self.function_value, param.position_index)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/debug.py", line 81, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/dynamic_params.py", line 47, in wrapper
    return func(function_value, param_index)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/dynamic_params.py", line 92, in dynamic_param_lookup
    values = ValueSet.from_sets(
             ^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/base_value.py", line 430, in from_sets
    for set_ in sets:
                ^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/dynamic_params.py", line 93, in <genexpr>
    get_executed_param_names(
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/param.py", line 245, in get_executed_param_names
    return get_executed_param_names_and_issues(function_value, arguments)[0]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/param.py", line 100, in get_executed_param_names_and_issues
    unpacked_va = list(arguments.unpack(funcdef))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/value/instance.py", line 610, in unpack
    yield from self._wrapped_arguments.unpack(func)
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/arguments.py", line 185, in unpack
    arrays = self.context.infer_node(el)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/context.py", line 224, in infer_node
    return infer_node(self, node)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 157, in infer_node
    return _infer_node_if_inferred(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 170, in _infer_node_if_inferred
    return _infer_node_cached(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/cache.py", line 44, in wrapper
    rv = function(obj, *args, **kwargs)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 175, in _infer_node_cached
    return _infer_node(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/debug.py", line 81, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 83, in wrapper
    return func(context, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 198, in _infer_node
    value_set = context.infer_node(first_child)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/context.py", line 224, in infer_node
    return infer_node(self, node)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 157, in infer_node
    return _infer_node_if_inferred(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 170, in _infer_node_if_inferred
    return _infer_node_cached(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/cache.py", line 44, in wrapper
    rv = function(obj, *args, **kwargs)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 175, in _infer_node_cached
    return _infer_node(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/debug.py", line 81, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 83, in wrapper
    return func(context, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 185, in _infer_node
    return infer_atom(context, element)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/syntax_tree.py", line 305, in infer_atom
    return context.py__getattribute__(atom, position=position)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/context.py", line 88, in py__getattribute__
    return self._check_for_additional_knowledge(name_or_str, name_context, position)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/context.py", line 102, in _check_for_additional_knowledge
    n = check_flow_information(name_context, flow_scope,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/inference/finder.py", line 66, in check_flow_information
    if is_scope(flow):
       ^^^^^^^^^^^^^^
  File "/home/den/anaconda3/lib/python3.12/site-packages/jedi/parser_utils.py", line 208, in is_scope
    t = node.type
        ^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'type'
(jedi_bug_tset)
[~/Programs/test/testjedi2]   [  ]   [08:12:40 PM]   den   exited 1
$ pip list | grep jedi
jedi                                    0.19.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions