Refactor NodeIdentity to use unique IDs

The `NodeIdentity` struct has been refactored to use a unique `id` field
generated by an atomic counter. This ensures that each `NodeIdentity`
instance is distinct, even if it has the same `SourceLocation`. The
`location` field is now optional, allowing for anonymous nodes.

This change improves the reliability of identity comparisons and
provides a more robust way to manage AST node identities.
This commit is contained in:
Michael Schimmel
2026-02-25 13:43:57 +01:00
parent 7436edc9b5
commit 3ff7ba9d59
6 changed files with 56 additions and 37 deletions
+1 -3
View File
@@ -130,9 +130,7 @@ impl Binder {
}; };
binder.functions.push(FunctionCompiler::new( binder.functions.push(FunctionCompiler::new(
ScopeKind::Root, ScopeKind::Root,
Rc::new(crate::ast::types::NodeIdentity { crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation { line: 0, col: 0 }),
location: crate::ast::types::SourceLocation { line: 0, col: 0 },
}),
)); ));
binder binder
} }
+2 -1
View File
@@ -101,9 +101,10 @@ impl Dumper {
if !captured_by.is_empty() { if !captured_by.is_empty() {
for capturer in captured_by { for capturer in captured_by {
self.write_indent(); self.write_indent();
let loc = capturer.location.unwrap_or(crate::ast::types::SourceLocation { line: 0, col: 0 });
self.output.push_str(&format!( self.output.push_str(&format!(
"- Capturer: Lambda at line {}, col {}\n", "- Capturer: Lambda at line {}, col {}\n",
capturer.location.line, capturer.location.col loc.line, loc.col
)); ));
} }
} }
+1 -3
View File
@@ -717,9 +717,7 @@ mod tests {
Symbol::from("*"), Symbol::from("*"),
( (
0, 0,
Rc::new(crate::ast::types::NodeIdentity { crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation { line: 0, col: 0 }),
location: crate::ast::types::SourceLocation { line: 0, col: 0 },
}),
), ),
); );
let globals = Rc::new(RefCell::new(global_names)); let globals = Rc::new(RefCell::new(global_names));
+2 -6
View File
@@ -134,9 +134,7 @@ impl Environment {
let mut purity = self.global_purity.borrow_mut(); let mut purity = self.global_purity.borrow_mut();
let idx = values.len() as u32; let idx = values.len() as u32;
let identity = Rc::new(NodeIdentity { let identity = NodeIdentity::new(SourceLocation { line: 0, col: 0 });
location: SourceLocation { line: 0, col: 0 },
});
names.insert(Symbol::from(name), (idx, identity)); names.insert(Symbol::from(name), (idx, identity));
types.insert(idx, ty); types.insert(idx, ty);
purity.insert(idx, func.purity); purity.insert(idx, func.purity);
@@ -167,9 +165,7 @@ impl Environment {
let mut purity = self.global_purity.borrow_mut(); let mut purity = self.global_purity.borrow_mut();
let idx = values.len() as u32; let idx = values.len() as u32;
let identity = Rc::new(NodeIdentity { let identity = NodeIdentity::new(SourceLocation { line: 0, col: 0 });
location: SourceLocation { line: 0, col: 0 },
});
names.insert(Symbol::from(name), (idx, identity)); names.insert(Symbol::from(name), (idx, identity));
types.insert(idx, ty); types.insert(idx, ty);
purity.insert(idx, Purity::Pure); purity.insert(idx, Purity::Pure);
+7 -21
View File
@@ -29,9 +29,7 @@ impl<'a> Parser<'a> {
pub fn parse_expression(&mut self) -> Result<Node<UntypedKind>, String> { pub fn parse_expression(&mut self) -> Result<Node<UntypedKind>, String> {
let token_loc = self.current_token.location; let token_loc = self.current_token.location;
let identity = Rc::new(NodeIdentity { let identity = NodeIdentity::new(token_loc);
location: token_loc,
});
match self.peek() { match self.peek() {
TokenKind::LeftParen => self.parse_list(), TokenKind::LeftParen => self.parse_list(),
@@ -93,9 +91,7 @@ impl<'a> Parser<'a> {
fn parse_atom(&mut self) -> Result<Node<UntypedKind>, String> { fn parse_atom(&mut self) -> Result<Node<UntypedKind>, String> {
let token = self.advance()?; let token = self.advance()?;
let identity = Rc::new(NodeIdentity { let identity = NodeIdentity::new(token.location);
location: token.location,
});
let kind = match token.kind { let kind = match token.kind {
TokenKind::Integer(n) => UntypedKind::Constant(Value::Int(n)), TokenKind::Integer(n) => UntypedKind::Constant(Value::Int(n)),
@@ -123,9 +119,7 @@ impl<'a> Parser<'a> {
fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> { fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> {
let start_loc = self.advance()?.location; // consume '(' let start_loc = self.advance()?.location; // consume '('
let identity = Rc::new(NodeIdentity { let identity = NodeIdentity::new(start_loc);
location: start_loc,
});
if *self.peek() == TokenKind::RightParen { if *self.peek() == TokenKind::RightParen {
return Err(format!( return Err(format!(
@@ -272,18 +266,14 @@ impl<'a> Parser<'a> {
_ => unreachable!(), _ => unreachable!(),
}; };
Ok(Node { Ok(Node {
identity: Rc::new(NodeIdentity { identity: NodeIdentity::new(token.location),
location: token.location,
}),
kind: UntypedKind::Parameter(sym), kind: UntypedKind::Parameter(sym),
ty: (), ty: (),
}) })
} }
TokenKind::LeftBracket => { TokenKind::LeftBracket => {
let token = self.advance()?; let token = self.advance()?;
let identity = Rc::new(NodeIdentity { let identity = NodeIdentity::new(token.location);
location: token.location,
});
let mut elements = Vec::new(); let mut elements = Vec::new();
while *self.peek() != TokenKind::RightBracket { while *self.peek() != TokenKind::RightBracket {
@@ -344,9 +334,7 @@ impl<'a> Parser<'a> {
self.expect(TokenKind::RightBracket)?; self.expect(TokenKind::RightBracket)?;
Ok(Node { Ok(Node {
identity: Rc::new(NodeIdentity { identity: NodeIdentity::new(token.location),
location: token.location,
}),
kind: UntypedKind::Tuple { elements }, kind: UntypedKind::Tuple { elements },
ty: (), ty: (),
}) })
@@ -380,9 +368,7 @@ impl<'a> Parser<'a> {
self.expect(TokenKind::RightBrace)?; self.expect(TokenKind::RightBrace)?;
Ok(Node { Ok(Node {
identity: Rc::new(NodeIdentity { identity: NodeIdentity::new(token.location),
location: token.location,
}),
kind: UntypedKind::Record { fields }, kind: UntypedKind::Record { fields },
ty: (), ty: (),
}) })
+43 -3
View File
@@ -14,14 +14,54 @@ pub struct SourceLocation {
pub col: u32, pub col: u32,
} }
/// Shared identity for nodes (Location, etc.) /// Shared identity for nodes. The identity is defined by the object instance (unique ID).
#[derive(Debug, Clone, PartialEq, Eq, Hash)] /// SourceLocation is kept as optional metadata.
#[derive(Debug, Clone)]
pub struct NodeIdentity { pub struct NodeIdentity {
pub location: SourceLocation, pub id: u64,
pub location: Option<SourceLocation>,
}
impl PartialEq for NodeIdentity {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for NodeIdentity {}
impl std::hash::Hash for NodeIdentity {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
} }
pub type Identity = Rc<NodeIdentity>; pub type Identity = Rc<NodeIdentity>;
static NODE_ID_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
impl NodeIdentity {
pub fn next_id() -> u64 {
NODE_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
}
/// Creates a new unique identity at the given location.
pub fn new(location: SourceLocation) -> Identity {
Rc::new(NodeIdentity {
id: Self::next_id(),
location: Some(location),
})
}
/// Creates a new unique identity without location metadata (for synthetic nodes).
pub fn anonymous() -> Identity {
Rc::new(NodeIdentity {
id: Self::next_id(),
location: None,
})
}
}
/// Interned string identifier /// Interned string identifier
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Keyword(pub u32); pub struct Keyword(pub u32);