-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
208 lines (183 loc) · 6.88 KB
/
lib.rs
File metadata and controls
208 lines (183 loc) · 6.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
//! nex-graph: petgraph-based semantic code graph with diff capability.
//!
//! This crate owns:
//! - `CodeGraph` - a directed graph of semantic units with dependency edges
//! - Graph construction from `SemanticUnit` vectors
//! - Graph diff algorithm (match by `qualified_name`, compare hashes)
//! - Caller/dependency queries
//!
//! The `CodeGraph` API is specified in the Implementation Specification and
//! constitutes a contract. Codex implements the function bodies.
//!
//! Reviewing this file:
//! - `diff()` is intentionally name-based, not id-based; matching happens on
//! `qualified_name`.
//! - Dependency edges do not influence diff classification; they matter later
//! for coordination and validation only.
//! - If you change diff semantics, update `CORE_INVARIANTS.md` and the
//! property tests in `tests/diff_invariants.rs`.
use nex_core::{
ChangeKind, DepKind, ModifiedUnit, MovedUnit, SemanticDiff, SemanticId, SemanticUnit,
};
use petgraph::Direction;
use petgraph::graph::{DiGraph, NodeIndex};
use std::collections::HashMap;
/// A dependency edge in the code graph.
#[derive(Debug, Clone)]
pub struct DepEdge {
pub kind: DepKind,
}
/// A node wrapper in the code graph.
#[derive(Debug, Clone)]
pub struct SemanticNode {
pub unit: SemanticUnit,
}
/// Semantic code graph backed by petgraph.
///
/// Provides O(1) lookups by `SemanticId`, directed dependency edges,
/// and a `diff()` method that compares two graphs to produce a `SemanticDiff`.
pub struct CodeGraph {
graph: DiGraph<SemanticNode, DepEdge>,
index: HashMap<SemanticId, NodeIndex>,
/// Secondary index: qualified_name -> NodeIndex for diff matching.
name_index: HashMap<String, NodeIndex>,
}
impl CodeGraph {
/// Create an empty code graph.
pub fn new() -> Self {
Self {
graph: DiGraph::new(),
index: HashMap::new(),
name_index: HashMap::new(),
}
}
/// Add a semantic unit as a node. Returns its NodeIndex.
pub fn add_unit(&mut self, unit: SemanticUnit) -> NodeIndex {
let qualified_name = unit.qualified_name.clone();
let id = unit.id;
let index = self.graph.add_node(SemanticNode { unit });
self.index.insert(id, index);
self.name_index.insert(qualified_name, index);
index
}
/// Add a dependency edge between two units.
pub fn add_dep(&mut self, from: SemanticId, to: SemanticId, kind: DepKind) {
let Some(from_index) = self.index.get(&from).copied() else {
return;
};
let Some(to_index) = self.index.get(&to).copied() else {
return;
};
self.graph.add_edge(from_index, to_index, DepEdge { kind });
}
/// Look up a unit by its SemanticId.
pub fn get(&self, id: &SemanticId) -> Option<&SemanticUnit> {
let index = self.index.get(id)?;
self.graph.node_weight(*index).map(|node| &node.unit)
}
/// Find all units that call/depend on the given unit (incoming edges).
pub fn callers_of(&self, id: &SemanticId) -> Vec<&SemanticUnit> {
let Some(index) = self.index.get(id).copied() else {
return Vec::new();
};
self.graph
.neighbors_directed(index, Direction::Incoming)
.filter_map(|neighbor| self.graph.node_weight(neighbor).map(|node| &node.unit))
.collect()
}
/// Find all units that the given unit depends on (outgoing edges).
pub fn deps_of(&self, id: &SemanticId) -> Vec<&SemanticUnit> {
let Some(index) = self.index.get(id).copied() else {
return Vec::new();
};
self.graph
.neighbors_directed(index, Direction::Outgoing)
.filter_map(|neighbor| self.graph.node_weight(neighbor).map(|node| &node.unit))
.collect()
}
/// Return all units in the graph.
pub fn units(&self) -> Vec<&SemanticUnit> {
self.graph.node_weights().map(|node| &node.unit).collect()
}
/// Return the number of units in the graph.
pub fn unit_count(&self) -> usize {
self.graph.node_count()
}
/// Return the number of dependency edges in the graph.
pub fn edge_count(&self) -> usize {
self.graph.edge_count()
}
/// Compute the semantic diff between `self` (before) and `other` (after).
///
/// Algorithm (from spec):
/// - Match units by `qualified_name`
/// - Same name + different `signature_hash` -> SignatureChanged
/// - Same signature + different `body_hash` -> BodyChanged
/// - Same `body_hash` + different `file_path` -> Moved
/// - Unmatched in `other` -> Added
/// - Unmatched in `self` -> Removed
pub fn diff(&self, other: &CodeGraph) -> SemanticDiff {
let self_by_name = self.units_by_name();
let other_by_name = other.units_by_name();
let mut added = Vec::new();
let mut removed = Vec::new();
let mut modified = Vec::new();
let mut moved = Vec::new();
for (name, other_unit) in &other_by_name {
if let Some(self_unit) = self_by_name.get(name) {
if self_unit.body_hash == other_unit.body_hash
&& self_unit.signature_hash == other_unit.signature_hash
{
if self_unit.file_path != other_unit.file_path {
moved.push(MovedUnit {
unit: (*other_unit).clone(),
old_path: self_unit.file_path.clone(),
new_path: other_unit.file_path.clone(),
});
}
continue;
}
let mut changes = Vec::new();
if self_unit.signature_hash != other_unit.signature_hash {
changes.push(ChangeKind::SignatureChanged);
}
if self_unit.body_hash != other_unit.body_hash {
changes.push(ChangeKind::BodyChanged);
}
modified.push(ModifiedUnit {
before: (*self_unit).clone(),
after: (*other_unit).clone(),
changes,
});
} else {
added.push((*other_unit).clone());
}
}
for (name, self_unit) in &self_by_name {
if !other_by_name.contains_key(name) {
removed.push((*self_unit).clone());
}
}
SemanticDiff {
added,
removed,
modified,
moved,
}
}
fn units_by_name(&self) -> HashMap<&str, &SemanticUnit> {
self.name_index
.iter()
.filter_map(|(name, index)| {
self.graph
.node_weight(*index)
.map(|node| (name.as_str(), &node.unit))
})
.collect()
}
}
impl Default for CodeGraph {
fn default() -> Self {
Self::new()
}
}