Skip to content

Commit bf3f70c

Browse files
committed
Handle RBSHash types
Add support for RBS hashes (rbs_hash_t), which are used in Record types and Function keyword arguments
1 parent d29585e commit bf3f70c

2 files changed

Lines changed: 98 additions & 0 deletions

File tree

rust/ruby-rbs/build.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ fn generate(config: &Config) -> Result<(), Box<dyn Error>> {
172172
)?;
173173
writeln!(file, " }}")?;
174174
}
175+
"rbs_hash" => {
176+
writeln!(file, " pub fn {}(&self) -> RBSHash {{", field.name)?;
177+
writeln!(
178+
file,
179+
" RBSHash::new(self.parser, unsafe {{ (*self.pointer).{} }})",
180+
field.c_name()
181+
)?;
182+
writeln!(file, " }}")?;
183+
}
175184
"rbs_location" => {
176185
writeln!(file, " pub fn {}(&self) -> RBSLocation {{", field.name)?;
177186
writeln!(

rust/ruby-rbs/src/lib.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,47 @@ impl NodeList {
9191
}
9292
}
9393

94+
pub struct RBSHash {
95+
parser: *mut rbs_parser_t,
96+
pointer: *mut rbs_hash,
97+
}
98+
99+
impl RBSHash {
100+
pub fn new(parser: *mut rbs_parser_t, pointer: *mut rbs_hash) -> Self {
101+
Self { parser, pointer }
102+
}
103+
104+
/// Returns an iterator over the key-value pairs.
105+
#[must_use]
106+
pub fn iter(&self) -> RBSHashIter {
107+
RBSHashIter {
108+
parser: self.parser,
109+
current: unsafe { (*self.pointer).head },
110+
}
111+
}
112+
}
113+
114+
pub struct RBSHashIter {
115+
parser: *mut rbs_parser_t,
116+
current: *mut rbs_hash_node_t,
117+
}
118+
119+
impl Iterator for RBSHashIter {
120+
type Item = (Node, Node);
121+
122+
fn next(&mut self) -> Option<Self::Item> {
123+
if self.current.is_null() {
124+
None
125+
} else {
126+
let pointer_data = unsafe { *self.current };
127+
let key = unsafe { Node::new(self.parser, pointer_data.key) };
128+
let value = unsafe { Node::new(self.parser, pointer_data.value) };
129+
self.current = pointer_data.next;
130+
Some((key, value))
131+
}
132+
}
133+
}
134+
94135
pub struct RBSLocation {
95136
pointer: *const rbs_location_t,
96137
#[allow(dead_code)]
@@ -237,4 +278,52 @@ mod tests {
237278
panic!("No literal type node found");
238279
}
239280
}
281+
282+
#[test]
283+
fn test_rbs_hash_via_record_type() {
284+
// RecordType stores its fields in an RBSHash via all_fields()
285+
let rbs_code = r#"type foo = { name: String, age: Integer }"#;
286+
let signature = parse(rbs_code.as_bytes());
287+
assert!(signature.is_ok(), "Failed to parse RBS signature");
288+
289+
let signature_node = signature.unwrap();
290+
if let Node::TypeAlias(type_alias) = signature_node.declarations().iter().next().unwrap()
291+
&& let Node::RecordType(record) = type_alias.type_()
292+
{
293+
let hash = record.all_fields();
294+
let fields: Vec<_> = hash.iter().collect();
295+
assert_eq!(fields.len(), 2, "Expected 2 fields in record");
296+
297+
// Build a map of field names to type names
298+
let mut field_types: Vec<(String, String)> = Vec::new();
299+
for (key, value) in &fields {
300+
let Node::Symbol(sym) = key else {
301+
panic!("Expected Symbol key");
302+
};
303+
let Node::RecordFieldType(field_type) = value else {
304+
panic!("Expected RecordFieldType value");
305+
};
306+
let Node::ClassInstanceType(class_type) = field_type.type_() else {
307+
panic!("Expected ClassInstanceType");
308+
};
309+
310+
let key_name = String::from_utf8(sym.name().to_vec()).unwrap();
311+
let type_name_node = class_type.name();
312+
let type_name_sym = type_name_node.name();
313+
let type_name = String::from_utf8(type_name_sym.name().to_vec()).unwrap();
314+
field_types.push((key_name, type_name));
315+
}
316+
317+
assert!(
318+
field_types.contains(&("name".to_string(), "String".to_string())),
319+
"Expected 'name: String'"
320+
);
321+
assert!(
322+
field_types.contains(&("age".to_string(), "Integer".to_string())),
323+
"Expected 'age: Integer'"
324+
);
325+
} else {
326+
panic!("Expected TypeAlias with RecordType");
327+
}
328+
}
240329
}

0 commit comments

Comments
 (0)