Language-Specific Type Inference in code-graph-rag: Python, Rust, and Java Explained
The code-graph-rag system implements dedicated type-inference engines for Python, Rust, and Java—PythonTypeInferenceEngine, RustTypeInferenceEngine, and JavaTypeInferenceEngine—each translating Tree-Sitter AST nodes into identifier-to-type maps that power call-edge resolution and cross-language retrieval.
This article examines how code-graph-rag performs language-specific type inference across three major programming languages. The system extracts a language-agnostic code graph (CG) from repositories, then enriches it with precise type information through specialized engines that understand each language's unique type system characteristics.
Python Type Inference Engine
The Python type inference implementation resides in [codebase_rag/parsers/py/type_inference.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/py/type_inference.py). The PythonTypeInferenceEngine class handles variable, expression, and AST mix-ins to produce accurate type maps.
Core Inference Pipeline
The engine executes six sequential transformations (lines 88–104):
| Step | Method | Purpose |
|---|---|---|
| 1 | _infer_parameter_types |
Walks function signatures, stores annotated parameter types |
| 2 | _traverse_single_pass |
Single-pass body traversal propagating types through assignments and attribute accesses |
| 3 | _infer_instance_attributes_from_init |
Extracts self.x = … assignments inside __init__ methods |
| 4 | _infer_property_return_types |
Follows property decorators to assign return types to virtual attributes |
| 5 | _infer_class_annotation_types |
Reads class-level __annotations__ dictionaries |
| 6 | _expand_chained_attribute_types |
Expands locals with aliasing and chained attribute resolution |
The single-pass traversal avoids O(5·N) rescans, a critical performance optimization for large Python codebases.
from pathlib import Path
from codebase_rag.parsers.py.type_inference import PythonTypeInferenceEngine
engine = PythonTypeInferenceEngine(
import_processor, # ImportProcessor instance
function_registry, # FunctionRegistryTrieProtocol implementation
Path("/my/repo"), # repository root
"my_project", # project name
ast_cache, # ASTCacheProtocol
queries, # LanguageQueries mapping
module_qn_to_path, # dict[str, Path]
class_inheritance, # dict[str, list[str]]
simple_name_lookup, # SimpleNameLookup
lambda: js_type_inference_engine, # deferred JS engine for mixed projects
)
# caller_node is a Tree-Sitter node representing the function definition
var_map = engine.build_local_variable_type_map(caller_node, "my_module")
print(var_map) # {'cfg': 'Config', 'service': 'UserService', ...}
Rust Type Inference Engine
The Rust type inference system is defined in [codebase_rag/parsers/rs/type_inference.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/rs/type_inference.py). The RustTypeInferenceEngine handles Rust's ownership system, generics, and iterator chains through specialized collection structures.
Key Data Structures and Methods
| Function | Lines | Purpose |
|---|---|---|
build_local_variable_type_map |
19–26 | Entry point: parameters first, then body traversal for let bindings |
_collect_parameters |
52–66 | Maps parameters to bare type names via _bare_type_name |
_collect_bindings / _collect_let_binding |
68–79 | Processes let x: T = … and struct literal initializations |
build_field_type_map |
28–47 | Maps struct fields to bare types for receiver dispatch |
collect_element_entries |
93–110 | Records collection element types (e.g., Vec<Worker> → Worker) |
collect_closure_param_bindings |
78–89 | Generates bindings for closure parameters in generic pipelines |
collect_generic_bounds |
68–84 | Extracts trait bounds enabling trait-based method dispatch |
The engine provides deref-unwrapping via _guard_inner_type and generic return-type lookup, ensuring calls like cmd.apply() bind to correct implementation methods.
from codebase_rag.parsers.rs.type_inference import RustTypeInferenceEngine
rust_engine = RustTypeInferenceEngine()
var_map = rust_engine.build_local_variable_type_map(caller_node, module_qn)
# var_map => {'cmd': 'Command', 'workers': 'Vec<Worker>', ...}
# Collect iterator element types for chain analysis
element_entries = rust_engine.collect_element_entries(fn_node)
# element_entries -> [('workers', 'Worker', 0, 150, 30), ...]
Java Type Inference Engine
The Java type inference implementation lives in [codebase_rag/parsers/java/type_inference.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/java/type_inference.py). The JavaTypeInferenceEngine leverages fully-qualified names (FQN) for cross-file resolution.
Three-Stage Resolution Architecture
-
FQN lookup map construction –
_build_fqn_lookup_map(lines 72–97) creates a reverse index from simple class names to fully-qualified module QNs, supporting bothsrc/main/java/...and flat project layouts. -
Variable type mapping –
build_variable_type_map(lines 99–111) gathers local variable types usingJavaVariableAnalyzerMixin. -
Method call resolution –
resolve_java_method_call(lines 13–22) delegates to_do_resolve_java_method_call, which usesJavaMethodResolverMixinto locate static and instance methods considering imports, inheritance, and the FQN map.
The mix-in architecture (JavaTypeResolverMixin, JavaMethodResolverMixin) encapsulates Java-specific resolution logic: type resolution respects import statements, package scopes, and inheritance hierarchies.
from pathlib import Path
from codebase_rag.parsers.java.type_inference import JavaTypeInferenceEngine
java_engine = JavaTypeInferenceEngine(
import_processor,
function_registry,
Path("/my/repo"),
"my_project",
ast_cache,
queries,
module_qn_to_path,
class_inheritance,
simple_name_lookup,
)
var_map = java_engine.build_variable_type_map(scope_node, "com.example.service")
target = java_engine.resolve_java_method_call(
call_node, var_map, "com.example.service", caller_qn
)
# target => ('com.example.service.UserService', 'findUser')
Integration with the Code Graph Construction Pipeline
During CG construction in [codebase_rag/graph_loader.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/graph_loader.py) and graph_updater.py, each parser registers its engine in the language-specific LanguageHandler registry. When traversal encounters a function node, the corresponding engine produces a local type map attached to node metadata.
This type map enables three downstream capabilities:
- Call-edge resolution – selecting correct callees based on inferred receiver types
- Cross-language linking – Python calls to Rust-implemented interfaces resolve when Rust engine supplies method signatures
- RAG retrieval enhancement – [
codebase_rag/embedder.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/embedder.py) uses inferred types for semantic search relevance scoring
Summary
-
Python engine (
PythonTypeInferenceEngine): Six-phase pipeline with single-pass traversal, handling parameters, instance attributes, properties, class annotations, and chained attributes in [codebase_rag/parsers/py/type_inference.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/py/type_inference.py) -
Rust engine (
RustTypeInferenceEngine): Specialized collection for bindings, fields, guard inner types, iterator elements, generic bounds, and closure parameters in [codebase_rag/parsers/rs/type_inference.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/rs/type_inference.py) -
Java engine (
JavaTypeInferenceEngine): FQN-based cross-file resolution with variable analysis and method resolution mix-ins in [codebase_rag/parsers/java/type_inference.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/java/type_inference.py) -
Shared contract: All engines receive Tree-Sitter
Nodeand module QN, returndict[str, str]mapping identifiers to type strings -
Pipeline integration: Engines register via
LanguageHandler, produce local type maps for call-edge resolution and RAG retrieval
Frequently Asked Questions
How does the Python engine avoid expensive re-traversals?
The Python engine implements _traverse_single_pass (lines 88–104), which walks the function body once to propagate types through assignments and attribute accesses. This design eliminates O(5·N) rescans that would occur with separate passes for each inference category.
What makes Rust type inference particularly complex in code-graph-rag?
Rust's ownership system, generics, and iterator chains require specialized handling. The RustTypeInferenceEngine maintains separate collections for guard inner types, iterator element types, generic bounds, and closure parameter bindings—structures unnecessary in Python or Java due to Rust's unique type system characteristics.
How does Java cross-file type resolution work?
The Java engine constructs a fully-qualified name (FQN) lookup map via _build_fqn_lookup_map (lines 72–97). This reverse index maps simple class names to their complete module QNs, enabling resolution across both standard Maven/Gradle layouts (src/main/java/...) and flat project structures while respecting import statements and package scopes.
Can these engines handle mixed-language repositories?
Yes. The LanguageHandler registry allows each file's AST to be processed by its appropriate engine. The deferred engine pattern—seen in PythonTypeInferenceEngine's lambda: js_type_inference_engine parameter—enables cross-language linking when Python calls JavaScript or other supported languages.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →