feat(parser): enrich CALLS via typed-variable annotations#338
Conversation
1a3a83a to
51f91af
Compare
- Remove duplicate macOS-copy module files (analysis 2.py, enrich 2.py, enrich 3.py, exports 2.py, exports 3.py, graph_diff 2.py, jedi_resolver 2.py, memory 2.py, memory 3.py, token_benchmark 2.py) that tripped ruff N999. - Sort imports in main.py (ruff I001). - Drop unused parse_git_diff_ranges import in tools/review.py (ruff F401). - Add type-ignore for FastMCP._tool_manager private attribute access in main.py. CI on main has been red since PR tirth8205#94. This fixes it.
MCP tool calls were opening a fresh GraphStore (and SQLite connection) on every invocation. Cache one GraphStore per db_path and reuse it, falling back to a fresh connection if the cached one is dead. Thread-safe via a module-level lock.
Exact known-answer assertions for compute_risk_score and trace_flows, plus error-path coverage for parser on malformed input and module-cache eviction. - TestRiskScoreExact: 6 tests pinning risk score math (untested, tested, security keyword, caller fractions, 20-caller cap) - TestFlowExact: 4 tests for linear chain depth, cycles, single-file criticality, and non-test neighbors - TestParserErrorPaths: 8 tests for binary files, unknown extensions, syntax errors, empty files, CRLF, deeply nested AST, unicode - TestCacheEviction: oldest-half module cache eviction
Add three helpers: - `_resolve_star_imports`: walks top-level `import_from_statement` nodes, detects `wildcard_import`, resolves the source module to a file, and merges the exported names into the caller's import_map. - `_get_exported_names`: returns the public names a module exports, respecting `__all__` when present. Caches results per resolved path with a threading lock so concurrent parses share work safely. - `_extract_dunder_all`: parses `__all__ = [...]` at module scope. Wires star-import expansion into `parse_bytes` and the notebook concat path. Enables resolution of calls that come in via wildcard imports (e.g. `from constants import *` then `use_constant()`).
51f91af to
be5c6c0
Compare
When receiver filtering emits "Foo.bar" (uppercase receiver), resolve it to "/path/file::Foo.bar" by looking up the class entry in the file symbol table. Closes a resolution gap where class-qualified bare calls stayed unresolved after the receiver filter rewrite.
Add _resolve_cross_file_class_methods as a postprocess step that scans Class nodes, builds a name -> file::ClassName map (dropping colliding names), and rewrites bare ClassName.method edge targets to the qualified file::ClassName.method form. Complements the per-file resolver in parser.py which only sees symbols in the calling file.
The cross-file ClassName.method resolver dropped any class whose name appeared in more than one file. That was too strict: codebases commonly reuse short class names across feature packages (util/ClassA.kt and test/ClassA.kt, multiple platform-specific Impl files, etc.). The call site almost always means the version in the closest package. Rebuild class_candidates as a name -> [qn, ...] map. For each edge with a bare ClassName.method target, pick the class qn whose file path shares the deepest common directory prefix with the edge's calling file. If the top match is not unique, fall back to leaving the target bare (rather than mis-resolving).
…odes The parser already extracted decorator tuples for test detection but discarded them before emitting NodeInfo. flows._has_framework_decorator and refactor._is_entry_point expect this data on nodes[...].extra; with no source data, entry-point detection never fires and framework-exposed callables (FastAPI routes, Click commands, Spring handlers) are flagged as dead. Wire class-level decorators (Python decorated_definition, Java/Kotlin modifiers.annotation/marker_annotation) and function-level decorators (existing tuple) into NodeInfo.extra["decorators"]. flows.py patterns already cover FastAPI/Click/Spring/Angular/Celery/pytest.
_resolve_call_targets only consulted the same-file symbol table, so every
call to an imported library symbol (Column, useState, Depends, etc.) stayed
bare. For Python-heavy repos this is the dominant unresolved class and
halves the CALLS resolution rate.
Pass the file's import_map through to _resolve_call_targets and add a
third resolution arm: bare target -> imported module -> {module}::{name}.
Also matches ClassName.method when ClassName is imported. Same-file
symbols still take precedence.
Notebook parse path merges import_maps across per-language cell groups
before resolving, so Python/R notebooks see all imports.
- Dedupe callers/callees/inheritors by qualified_name (repeated CALLS edges were surfacing the same node multiple times). - tests_for: use store.get_transitive_tests (direct TESTED_BY + one CALLS hop) instead of only direct TESTED_BY edges. - Disambiguation: when multiple candidates match a bare name and exactly one is a production node (not is_test), prefer it silently instead of returning 'ambiguous'.
Webpack/rollup bundle outputs and AWS CDK synth artifacts are generated code that pollutes the graph. Add them to DEFAULT_IGNORE_PATTERNS.
Log duration of each postprocess stage (signatures, FTS, flows, communities, summaries) and include them in the build result under 'postprocess_timing'. Also log which summary table skipped on sqlite OperationalError (was silent rollback).
Add `_INSTANCE_METHOD_BLOCKLIST` (48 ubiquitous JS/TS/Python method names: push, map, toString, etc.) and receiver filtering for CALLS edges on member expressions (obj.method()). In production code, only emit CALLS when: - receiver is self/cls/this/super (always resolved to enclosing class) - receiver starts with uppercase (ClassName.method → "ClassName.method") - receiver is a namespace import (import * as X from Y) - receiver's leftmost ident is an imported module (module-qualified path) Other instance calls emit bare method names unless the name is in the blocklist, in which case the edge is dropped as unresolvable noise. Test files are exempted -- they still emit all method calls so TESTED_BY edges continue to resolve correctly.
Add `_enrich_typed_var_calls` plus per-language walkers for Python, Kotlin, Java, and JS/TS. Each walker tracks typed variables within a scope and, when a call is made against one, emits a CALLS edge to `Type::method` (or to the resolved cross-file target when the type is importable). Handles: - Python `service: AuthService = ...` then `service.method()` - Kotlin `val service: AuthService` and primary-constructor params - Java `FooService svc = ...` and `var x = new Foo()` - TS/JS typed locals, `new X()` inference, and constructor parameter properties (`constructor(private svc: AuthService)` → `this.svc.method()`) Invoked after `_extract_from_tree` and before `_resolve_call_targets` in `parse_bytes`, so the synthetic edges flow through the normal resolution pass.
be5c6c0 to
da2a80d
Compare
|
Thanks — typed-variable resolution is useful, but current head is a cumulative, conflicting stack and cannot merge as-is. Its unique 559-line walker has no focused tests; same-file typed calls can target Type::method while the actual node is file::Type.method, and Java parameters/fields are not collected reliably. Please rebuild this as one focused PR on current main, preserve lexical scope, collect parameters and fields before resolving calls, use current qualified-name conventions, deduplicate edges, and add Python, Kotlin, Java, and TypeScript tests for same-file, imported, generic, and shadowed types. Then run the full current CI suite. |
Safe replacement for #330 and #338. It resolves static-class and typed receiver calls only when repository-local type and import evidence identifies the target, while preserving uncertain calls instead of guessing or deleting them. Co-authored-by: Gideon Zenz <91069374+gzenz@users.noreply.github.com>
|
Final maintainer disposition: the safe typed-receiver behavior is now rebuilt and merged through replacement PR #649 at bfe1a13. The merged version:
It passed 37 focused typed-call plus Julia overlap tests, 565 broader parser/language tests, and all 11 GitHub checks including Windows. Gideon Zenz is retained as co-author on the merged commit. Closing this cumulative branch as superseded. |
Summary
When a variable has a type annotation (Python
x: Foo, Kotlinval x: Foo), resolvex.method(...)toFoo.method.Stacked PR approach
This is PR 13/16 in a stack that ports closed #158 into small, reviewable slices based on your feedback. Each branch contains this commit plus every earlier branch in the stack, so it applies cleanly on its own. The intended merge order is bottom-to-top.
Stack (merge order):
After you merge PR N, I rebase PRs N+1..16 on current main and force-push. Each PR ends up as a single commit on top of the previous one. Review only the newest commit in this PR; the rest will already be on main (or in earlier PRs in the stack).
All 16 branches are rebased on 072ab80. None re-attempts the closed lang-handler refactor.
Roadmap: restoring v7 loop-test parity
Measured impact on loop-test fixtures (Gadgetbridge Kotlin/Java, HealthAgent Python/TS):
Still deferred after this stack