Skip to content

Commit db57f23

Browse files
authored
Merge pull request #3887 from sei40kr/feat/rust-node-ext
[rust] add `node_ext` module with `full_name` methods for constant nodes
2 parents a690c9d + 04b986f commit db57f23

2 files changed

Lines changed: 252 additions & 0 deletions

File tree

rust/ruby-prism/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod bindings {
1414
}
1515

1616
mod node;
17+
mod node_ext;
1718
mod parse_result;
1819

1920
use std::ffi::CString;
@@ -22,6 +23,7 @@ use std::ptr::NonNull;
2223

2324
pub use self::bindings::*;
2425
pub use self::node::{ConstantId, ConstantList, ConstantListIter, Integer, NodeList, NodeListIter};
26+
pub use self::node_ext::{ConstantPathError, FullName};
2527
pub use self::parse_result::{Comment, CommentType, Comments, Diagnostic, Diagnostics, Location, MagicComment, MagicComments, ParseResult};
2628

2729
use ruby_prism_sys::{

rust/ruby-prism/src/node_ext.rs

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
//! Node extension methods for the prism parser.
2+
//!
3+
//! This module provides convenience methods on AST nodes that aren't generated
4+
//! from the config, mirroring Ruby's `node_ext.rb`.
5+
6+
use std::fmt;
7+
8+
use crate::{ConstantPathNode, ConstantPathTargetNode, ConstantReadNode, ConstantTargetNode, ConstantWriteNode, Node};
9+
10+
/// Errors for constant path name computation.
11+
#[derive(Debug, Clone, PartialEq, Eq)]
12+
pub enum ConstantPathError {
13+
/// The constant path contains dynamic parts (e.g., `var::Bar::Baz`).
14+
DynamicParts,
15+
/// The constant path contains missing nodes (e.g., `Foo::`).
16+
MissingNodes,
17+
}
18+
19+
impl fmt::Display for ConstantPathError {
20+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21+
match self {
22+
Self::DynamicParts => {
23+
write!(f, "Constant path contains dynamic parts. Cannot compute full name")
24+
},
25+
Self::MissingNodes => {
26+
write!(f, "Constant path contains missing nodes. Cannot compute full name")
27+
},
28+
}
29+
}
30+
}
31+
32+
impl std::error::Error for ConstantPathError {}
33+
34+
/// Trait for nodes that can compute their full constant name.
35+
///
36+
/// Implemented by constant-related nodes (`ConstantReadNode`,
37+
/// `ConstantWriteNode`, `ConstantTargetNode`, `ConstantPathNode`, and
38+
/// `ConstantPathTargetNode`).
39+
pub trait FullName<'pr> {
40+
/// Returns the list of parts for the full name of this constant.
41+
///
42+
/// # Errors
43+
///
44+
/// Returns [`ConstantPathError`] if the path contains dynamic parts or
45+
/// missing nodes.
46+
fn full_name_parts(&self) -> Result<Vec<&'pr [u8]>, ConstantPathError>;
47+
48+
/// Returns the full name of this constant.
49+
///
50+
/// # Errors
51+
///
52+
/// Returns [`ConstantPathError`] if the path contains dynamic parts or
53+
/// missing nodes.
54+
fn full_name(&self) -> Result<Vec<u8>, ConstantPathError> {
55+
let parts = self.full_name_parts()?;
56+
let mut result = Vec::new();
57+
for (index, part) in parts.iter().enumerate() {
58+
if index > 0 {
59+
result.extend_from_slice(b"::");
60+
}
61+
result.extend_from_slice(part);
62+
}
63+
Ok(result)
64+
}
65+
}
66+
67+
/// Computes `full_name_parts` for a `Node` by dispatching to the appropriate
68+
/// `FullName` implementation.
69+
#[allow(clippy::option_if_let_else)]
70+
fn full_name_parts_for_node<'pr>(node: &Node<'pr>) -> Result<Vec<&'pr [u8]>, ConstantPathError> {
71+
if let Some(path_node) = node.as_constant_path_node() {
72+
path_node.full_name_parts()
73+
} else if let Some(read_node) = node.as_constant_read_node() {
74+
read_node.full_name_parts()
75+
} else {
76+
Err(ConstantPathError::DynamicParts)
77+
}
78+
}
79+
80+
/// Computes `full_name_parts` for a constant path node given its name and
81+
/// parent.
82+
fn constant_path_full_name_parts<'pr>(name: Option<crate::ConstantId<'pr>>, parent: Option<Node<'pr>>) -> Result<Vec<&'pr [u8]>, ConstantPathError> {
83+
let name = name.ok_or(ConstantPathError::MissingNodes)?;
84+
85+
let mut parts = match parent {
86+
Some(parent) => full_name_parts_for_node(&parent)?,
87+
None => vec![b"".as_slice()],
88+
};
89+
90+
parts.push(name.as_slice());
91+
Ok(parts)
92+
}
93+
94+
/// Implements `FullName` for simple constant nodes that have a `name()` method
95+
/// returning a single constant identifier.
96+
macro_rules! impl_simple_full_name {
97+
($node_type:ident) => {
98+
impl<'pr> FullName<'pr> for $node_type<'pr> {
99+
fn full_name_parts(&self) -> Result<Vec<&'pr [u8]>, ConstantPathError> {
100+
Ok(vec![self.name().as_slice()])
101+
}
102+
}
103+
};
104+
}
105+
106+
impl_simple_full_name!(ConstantReadNode);
107+
impl_simple_full_name!(ConstantWriteNode);
108+
impl_simple_full_name!(ConstantTargetNode);
109+
110+
impl<'pr> FullName<'pr> for ConstantPathNode<'pr> {
111+
fn full_name_parts(&self) -> Result<Vec<&'pr [u8]>, ConstantPathError> {
112+
constant_path_full_name_parts(self.name(), self.parent())
113+
}
114+
}
115+
116+
impl<'pr> FullName<'pr> for ConstantPathTargetNode<'pr> {
117+
fn full_name_parts(&self) -> Result<Vec<&'pr [u8]>, ConstantPathError> {
118+
constant_path_full_name_parts(self.name(), self.parent())
119+
}
120+
}
121+
122+
#[cfg(test)]
123+
mod tests {
124+
use super::{ConstantPathError, FullName};
125+
use crate::parse;
126+
127+
#[test]
128+
fn test_full_name_for_constant_read_node() {
129+
let result = parse(b"Foo");
130+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
131+
let constant = node.as_constant_read_node().unwrap();
132+
133+
assert_eq!(constant.full_name_parts().unwrap(), vec![b"Foo".as_slice()]);
134+
assert_eq!(constant.full_name().unwrap(), b"Foo");
135+
}
136+
137+
#[test]
138+
fn test_full_name_for_constant_write_node() {
139+
let result = parse(b"Foo = 1");
140+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
141+
let constant = node.as_constant_write_node().unwrap();
142+
143+
assert_eq!(constant.full_name_parts().unwrap(), vec![b"Foo".as_slice()]);
144+
assert_eq!(constant.full_name().unwrap(), b"Foo");
145+
}
146+
147+
#[test]
148+
fn test_full_name_for_constant_target_node() {
149+
let result = parse(b"Foo, Bar = [1, 2]");
150+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
151+
let multi_write = node.as_multi_write_node().unwrap();
152+
let target = multi_write.lefts().iter().next().unwrap();
153+
let constant = target.as_constant_target_node().unwrap();
154+
155+
assert_eq!(constant.full_name_parts().unwrap(), vec![b"Foo".as_slice()]);
156+
assert_eq!(constant.full_name().unwrap(), b"Foo");
157+
}
158+
159+
#[test]
160+
fn test_full_name_for_constant_path() {
161+
let result = parse(b"Foo::Bar");
162+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
163+
let constant_path = node.as_constant_path_node().unwrap();
164+
165+
assert_eq!(constant_path.full_name_parts().unwrap(), vec![b"Foo".as_slice(), b"Bar".as_slice()]);
166+
assert_eq!(constant_path.full_name().unwrap(), b"Foo::Bar");
167+
}
168+
169+
#[test]
170+
fn test_full_name_for_constant_path_with_stovetop() {
171+
let result = parse(b"::Foo::Bar");
172+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
173+
let constant_path = node.as_constant_path_node().unwrap();
174+
175+
assert_eq!(constant_path.full_name_parts().unwrap(), vec![b"".as_slice(), b"Foo".as_slice(), b"Bar".as_slice()]);
176+
assert_eq!(constant_path.full_name().unwrap(), b"::Foo::Bar");
177+
}
178+
179+
#[test]
180+
fn test_full_name_for_constant_path_with_self() {
181+
let source = r"
182+
self::
183+
Bar::Baz::
184+
Qux
185+
";
186+
let result = parse(source.as_bytes());
187+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
188+
let constant_path = node.as_constant_path_node().unwrap();
189+
190+
assert_eq!(constant_path.full_name().unwrap_err(), ConstantPathError::DynamicParts);
191+
}
192+
193+
#[test]
194+
fn test_full_name_for_constant_path_with_variable() {
195+
let source = r"
196+
foo::
197+
Bar::Baz::
198+
Qux
199+
";
200+
let result = parse(source.as_bytes());
201+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
202+
let constant_path = node.as_constant_path_node().unwrap();
203+
204+
assert_eq!(constant_path.full_name().unwrap_err(), ConstantPathError::DynamicParts);
205+
}
206+
207+
#[test]
208+
fn test_full_name_for_constant_path_with_missing_name() {
209+
let result = parse(b"Foo::");
210+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
211+
let constant_path = node.as_constant_path_node().unwrap();
212+
213+
assert_eq!(constant_path.full_name().unwrap_err(), ConstantPathError::MissingNodes);
214+
}
215+
216+
#[test]
217+
fn test_full_name_for_constant_path_target() {
218+
let result = parse(b"Foo::Bar, Baz = [1, 2]");
219+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
220+
let multi_write = node.as_multi_write_node().unwrap();
221+
let target = multi_write.lefts().iter().next().unwrap();
222+
let constant_path = target.as_constant_path_target_node().unwrap();
223+
224+
assert_eq!(constant_path.full_name_parts().unwrap(), vec![b"Foo".as_slice(), b"Bar".as_slice()]);
225+
assert_eq!(constant_path.full_name().unwrap(), b"Foo::Bar");
226+
}
227+
228+
#[test]
229+
fn test_full_name_for_constant_path_target_with_stovetop() {
230+
let result = parse(b"::Foo, Bar = [1, 2]");
231+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
232+
let multi_write = node.as_multi_write_node().unwrap();
233+
let target = multi_write.lefts().iter().next().unwrap();
234+
let constant_path = target.as_constant_path_target_node().unwrap();
235+
236+
assert_eq!(constant_path.full_name_parts().unwrap(), vec![b"".as_slice(), b"Foo".as_slice()]);
237+
assert_eq!(constant_path.full_name().unwrap(), b"::Foo");
238+
}
239+
240+
#[test]
241+
fn test_full_name_for_constant_path_target_with_self() {
242+
let result = parse(b"self::Foo, Bar = [1, 2]");
243+
let node = result.node().as_program_node().unwrap().statements().body().iter().next().unwrap();
244+
let multi_write = node.as_multi_write_node().unwrap();
245+
let target = multi_write.lefts().iter().next().unwrap();
246+
let constant_path = target.as_constant_path_target_node().unwrap();
247+
248+
assert_eq!(constant_path.full_name().unwrap_err(), ConstantPathError::DynamicParts);
249+
}
250+
}

0 commit comments

Comments
 (0)