Skip to content

Commit 413dd2b

Browse files
authored
Merge pull request #2854 from ruby/expose-sub-locations-ruby-rbs
Expose sub-locations to nodes in ruby-rbs crate
2 parents b140e0f + 3a7e0d5 commit 413dd2b

3 files changed

Lines changed: 239 additions & 0 deletions

File tree

rust/ruby-rbs/build.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,30 @@ impl NodeField {
3535
}
3636
}
3737

38+
#[derive(Debug, Deserialize)]
39+
struct LocationField {
40+
#[serde(default)]
41+
required: Option<String>,
42+
#[serde(default)]
43+
optional: Option<String>,
44+
}
45+
46+
impl LocationField {
47+
fn name(&self) -> &str {
48+
self.required.as_ref().or(self.optional.as_ref()).unwrap()
49+
}
50+
51+
fn is_required(&self) -> bool {
52+
self.required.is_some()
53+
}
54+
}
55+
3856
#[derive(Debug, Deserialize)]
3957
struct Node {
4058
name: String,
4159
rust_name: String,
4260
fields: Option<Vec<NodeField>>,
61+
locations: Option<Vec<LocationField>>,
4362
}
4463

4564
impl Node {
@@ -72,6 +91,7 @@ fn main() -> Result<(), Box<dyn Error>> {
7291
name: "RBS::AST::Symbol".to_string(),
7392
rust_name: "SymbolNode".to_string(),
7493
fields: None,
94+
locations: None,
7595
});
7696

7797
config.nodes.sort_by(|a, b| a.name.cmp(&b.name));
@@ -487,6 +507,59 @@ fn generate(config: &Config) -> Result<(), Box<dyn Error>> {
487507
writeln!(file, " }}")?;
488508
writeln!(file)?;
489509

510+
// Generate location accessor methods
511+
if let Some(locations) = &node.locations {
512+
for location in locations {
513+
let location_name = location.name();
514+
let method_name = format!("{}_location", location_name);
515+
let field_name = format!("{}_range", location_name);
516+
517+
if location.is_required() {
518+
writeln!(
519+
file,
520+
" /// Returns the `{}` sub-location of this node.",
521+
location_name
522+
)?;
523+
writeln!(file, " #[must_use]")?;
524+
writeln!(
525+
file,
526+
" pub fn {}(&self) -> RBSLocationRange {{",
527+
method_name
528+
)?;
529+
writeln!(
530+
file,
531+
" RBSLocationRange::new(unsafe {{ (*self.pointer).{} }})",
532+
field_name
533+
)?;
534+
writeln!(file, " }}")?;
535+
} else {
536+
writeln!(
537+
file,
538+
" /// Returns the `{}` sub-location of this node if present.",
539+
location_name
540+
)?;
541+
writeln!(file, " #[must_use]")?;
542+
writeln!(
543+
file,
544+
" pub fn {}(&self) -> Option<RBSLocationRange> {{",
545+
method_name
546+
)?;
547+
writeln!(
548+
file,
549+
" let range = unsafe {{ (*self.pointer).{} }};",
550+
field_name
551+
)?;
552+
writeln!(file, " if range.start_char == -1 {{")?;
553+
writeln!(file, " None")?;
554+
writeln!(file, " }} else {{")?;
555+
writeln!(file, " Some(RBSLocationRange::new(range))")?;
556+
writeln!(file, " }}")?;
557+
writeln!(file, " }}")?;
558+
}
559+
writeln!(file)?;
560+
}
561+
}
562+
490563
if let Some(fields) = &node.fields {
491564
for field in fields {
492565
match field.c_type.as_str() {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use ruby_rbs::node::{Node, parse};
2+
3+
fn main() {
4+
let rbs_code = r#"class Foo[T] < Bar end"#;
5+
let signature = parse(rbs_code.as_bytes()).unwrap();
6+
7+
let declaration = signature.declarations().iter().next().unwrap();
8+
if let Node::Class(class) = declaration {
9+
println!("Class declaration: '{}'", rbs_code);
10+
println!(
11+
"Overall location: {}..{}",
12+
class.location().start(),
13+
class.location().end()
14+
);
15+
16+
// Required sub-locations
17+
let keyword = class.keyword_location();
18+
println!(
19+
" keyword location: {}..{} = '{}'",
20+
keyword.start(),
21+
keyword.end(),
22+
&rbs_code[keyword.start() as usize..keyword.end() as usize]
23+
);
24+
25+
let name = class.name_location();
26+
println!(
27+
" name location: {}..{} = '{}'",
28+
name.start(),
29+
name.end(),
30+
&rbs_code[name.start() as usize..name.end() as usize]
31+
);
32+
33+
let end_loc = class.end_location();
34+
println!(
35+
" end location: {}..{} = '{}'",
36+
end_loc.start(),
37+
end_loc.end(),
38+
&rbs_code[end_loc.start() as usize..end_loc.end() as usize]
39+
);
40+
41+
// Optional sub-locations
42+
if let Some(type_params) = class.type_params_location() {
43+
println!(
44+
" type_params location: {}..{} = '{}'",
45+
type_params.start(),
46+
type_params.end(),
47+
&rbs_code[type_params.start() as usize..type_params.end() as usize]
48+
);
49+
}
50+
51+
if let Some(lt) = class.lt_location() {
52+
println!(
53+
" lt location: {}..{} = '{}'",
54+
lt.start(),
55+
lt.end(),
56+
&rbs_code[lt.start() as usize..lt.end() as usize]
57+
);
58+
}
59+
}
60+
}

rust/ruby-rbs/src/node/mod.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,112 @@ mod tests {
483483
assert_eq!(12, int_loc.end());
484484
}
485485

486+
#[test]
487+
fn test_sub_locations() {
488+
let rbs_code = r#"class Foo < Bar end"#;
489+
let signature = parse(rbs_code.as_bytes()).unwrap();
490+
491+
let declaration = signature.declarations().iter().next().unwrap();
492+
let Node::Class(class) = declaration else {
493+
panic!("Expected Class");
494+
};
495+
496+
// Test required sub-locations
497+
let keyword_loc = class.keyword_location();
498+
assert_eq!(0, keyword_loc.start());
499+
assert_eq!(5, keyword_loc.end());
500+
501+
let name_loc = class.name_location();
502+
assert_eq!(6, name_loc.start());
503+
assert_eq!(9, name_loc.end());
504+
505+
let end_loc = class.end_location();
506+
assert_eq!(16, end_loc.start());
507+
assert_eq!(19, end_loc.end());
508+
509+
// Test optional sub-location that's present
510+
let lt_loc = class.lt_location();
511+
assert!(lt_loc.is_some());
512+
let lt = lt_loc.unwrap();
513+
assert_eq!(10, lt.start());
514+
assert_eq!(11, lt.end());
515+
516+
// Test optional sub-location that's not present (no type params in this class)
517+
let type_params_loc = class.type_params_location();
518+
assert!(type_params_loc.is_none());
519+
}
520+
521+
#[test]
522+
fn test_type_alias_sub_locations() {
523+
let rbs_code = r#"type foo = String"#;
524+
let signature = parse(rbs_code.as_bytes()).unwrap();
525+
526+
let declaration = signature.declarations().iter().next().unwrap();
527+
let Node::TypeAlias(type_alias) = declaration else {
528+
panic!("Expected TypeAlias");
529+
};
530+
531+
// Test required sub-locations
532+
let keyword_loc = type_alias.keyword_location();
533+
assert_eq!(0, keyword_loc.start());
534+
assert_eq!(4, keyword_loc.end());
535+
536+
let name_loc = type_alias.name_location();
537+
assert_eq!(5, name_loc.start());
538+
assert_eq!(8, name_loc.end());
539+
540+
let eq_loc = type_alias.eq_location();
541+
assert_eq!(9, eq_loc.start());
542+
assert_eq!(10, eq_loc.end());
543+
544+
// Test optional sub-location that's not present (no type params)
545+
let type_params_loc = type_alias.type_params_location();
546+
assert!(type_params_loc.is_none());
547+
}
548+
549+
#[test]
550+
fn test_module_sub_locations() {
551+
let rbs_code = r#"module Foo[T] : Bar end"#;
552+
let signature = parse(rbs_code.as_bytes()).unwrap();
553+
554+
let declaration = signature.declarations().iter().next().unwrap();
555+
let Node::Module(module) = declaration else {
556+
panic!("Expected Module");
557+
};
558+
559+
// Test required sub-locations
560+
let keyword_loc = module.keyword_location();
561+
assert_eq!(0, keyword_loc.start());
562+
assert_eq!(6, keyword_loc.end());
563+
564+
let name_loc = module.name_location();
565+
assert_eq!(7, name_loc.start());
566+
assert_eq!(10, name_loc.end());
567+
568+
let end_loc = module.end_location();
569+
assert_eq!(20, end_loc.start());
570+
assert_eq!(23, end_loc.end());
571+
572+
// Test optional sub-locations that are present
573+
let type_params_loc = module.type_params_location();
574+
assert!(type_params_loc.is_some());
575+
let tp = type_params_loc.unwrap();
576+
assert_eq!(10, tp.start());
577+
assert_eq!(13, tp.end());
578+
579+
let colon_loc = module.colon_location();
580+
assert!(colon_loc.is_some());
581+
let colon = colon_loc.unwrap();
582+
assert_eq!(14, colon.start());
583+
assert_eq!(15, colon.end());
584+
585+
let self_types_loc = module.self_types_location();
586+
assert!(self_types_loc.is_some());
587+
let st = self_types_loc.unwrap();
588+
assert_eq!(16, st.start());
589+
assert_eq!(19, st.end());
590+
}
591+
486592
#[test]
487593
fn test_enum_types() {
488594
let rbs_code = r#"

0 commit comments

Comments
 (0)