You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
AttributeError: 'NoneType' object has no attribute 'file' — module-level async def with nested def / async for / async with #31
codegraph (1.2.0, current PyPI release) crashes with
AttributeError: 'NoneType' object has no attribute 'file'
File ".../codegraph/parser.py", line 74, in _nest_function
newfunc = Function(func_name, ob.file, lineno, ob)
whenever a module-level async def contains any of:
a nested def / async def
an async for statement or comprehension
an async with statement
Minimal repro
Three lines, no third-party code involved:
asyncdeffoo():
return [xasyncforxinbar()]
$ codegraph repro.py
AttributeError: 'NoneType' object has no attribute 'file'
Control cases that do not crash (only the module-level async def variant does):
source
result
async def + async for
crash
def + async for
ok
class method async def + async for
ok
async def + async with
crash
async def + nested def
crash
def + nested def
ok
Impact
The whole tree is parsed in one pass, so a single offending file makes codegraph <dir> fail for the entire directory. On a real-world Python project this made 16 of 326 modules unparseable, i.e. any directory containing one of them could not be analysed at all.
Root cause
create_objects_array() uses tokenize plus an indentation stack to resolve parent/child relations. Two defects in the async branch (codegraph/parser.py:199-218):
The token after async is never validated. It is discarded via _, _, _ = next(g)[0:3] (comment: "next will be def") and the next NAME is used as the function name, so async for / async with produce a bogus function — in async for x in ... the "name" becomes x.
None is pushed on the stack for a module-level async def. The else branch only does tree.append(AsyncFunction(...)) without assigning cur_func, then stack.append((cur_func, thisindent)) pushes None unconditionally. The synchronous def branch assigns cur_func and guards the push with if cur_func:.
The None stays at the bottom of the stack for the entire function body (indent 0 is never popped by anything at a deeper indent), so the next nested def / async token calls _nest_function(None, ...) → ob.file → AttributeError.
Neither defect crashes on its own — only the combination does, which is why the failure is partial rather than total.
Environment
codegraph 1.2.0 (also reproduced on 1.1.0)
Python 3.13 / 3.14, macOS
Additional context
The in-flight Release/1.3.0 (#26) replaces the tokenizer parser with an AST-based one; I verified that branch parses all 326 modules of the affected project without error, so 1.3.0 should resolve this on the default path. Two follow-ups:
users on the released 1.2.x still hit this today — I opened a PR with a minimal patch for that line;
the legacy codegraph/parser.py on the release/1.3.0 branch still carries both defects, so it is worth patching there as well if that path stays reachable.
Follow-up: while fixing this on a real project I hit two more defects in the same 1.2.0 line, both now covered by PR #30 (#30).
2. TypeError: '<=' not supported between instances of 'int' and 'NoneType' in usage_graph()
Scopes popped by a sibling def / class (a one-line body such as def f(self) -> None: ... emits no INDENT/DEDENT) or still open at EOF never reach the DEDENT handler, so their endno stays None, and core.py then evaluates entity[0] <= line <= entity[1] with entity[1] is None. On the project this affected 74 entities — after the fix, 0.
3. usage_graph() silently loses every import-derived edge on a second call
get_imports_and_entities_lines() does code_objects[path].pop(-1) on the Import object, so a second call runs with an empty import set. The CLI builds the graph before searching it, hence --file-path / --distance always printed an empty dependency list. Additionally get_dependencies() compared a relative --file-path against absolute graph keys, and never followed an edge past distance 1 because edges carry bare "<module>.py" names.
Both are fixed in PR #30 with regression tests (46 tests pass, flake8 clean).
Note: the in-flight Release/1.3.0 (#26) AST parser resolves defect 1 (and likely 2) on the default path — I verified that branch parses all 326 modules of the affected project. Defects 1 and 2 are still present in the legacy codegraph/parser.py on that branch.
Activity
nanami7777777 commentedon Oct 30, 2026
Follow-up: while fixing this on a real project I hit two more defects in the same 1.2.0 line, both now covered by PR #30 (#30).
2.
TypeError: '<=' not supported between instances of 'int' and 'NoneType'inusage_graph()Scopes popped by a sibling
def/class(a one-line body such asdef f(self) -> None: ...emits no INDENT/DEDENT) or still open at EOF never reach the DEDENT handler, so theirendnostaysNone, andcore.pythen evaluatesentity[0] <= line <= entity[1]withentity[1] is None. On the project this affected 74 entities — after the fix, 0.3.
usage_graph()silently loses every import-derived edge on a second callget_imports_and_entities_lines()doescode_objects[path].pop(-1)on theImportobject, so a second call runs with an empty import set. The CLI builds the graph before searching it, hence--file-path/--distancealways printed an empty dependency list. Additionallyget_dependencies()compared a relative--file-pathagainst absolute graph keys, and never followed an edge past distance 1 because edges carry bare"<module>.py"names.Both are fixed in PR #30 with regression tests (46 tests pass, flake8 clean).
Note: the in-flight
Release/1.3.0(#26) AST parser resolves defect 1 (and likely 2) on the default path — I verified that branch parses all 326 modules of the affected project. Defects 1 and 2 are still present in the legacycodegraph/parser.pyon that branch.