Skip to content

Commit 607baea

Browse files
authored
Merge pull request #3044 from ruby/owned-ast-locations
Add the missing source locations to the owned AST
2 parents 91601f1 + 549f251 commit 607baea

5 files changed

Lines changed: 194 additions & 14 deletions

File tree

‎rust/ruby-rbs/src/ast/convert.rs‎

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ use crate::ast::location::{
1212
AliasDeclarationLocation, AliasLocation, AliasMemberLocation, AttributeMemberLocation,
1313
ClassDeclarationLocation, ClassInstanceLocation, ClassSingletonLocation, ClassSuperLocation,
1414
ConstantDeclarationLocation, FunctionParamLocation, GlobalDeclarationLocation,
15-
InterfaceDeclarationLocation, InterfaceLocation, LocationRange, MethodDefinitionLocation,
16-
MethodTypeLocation, MixinMemberLocation, ModuleDeclarationLocation, ModuleSelfLocation,
17-
TypeAliasDeclarationLocation, TypeParamLocation, UseDirectiveLocation, UseSingleClauseLocation,
18-
UseWildcardClauseLocation, VariableMemberLocation,
15+
InterfaceDeclarationLocation, InterfaceLocation, KeywordParamLocation, LocationRange,
16+
MethodDefinitionLocation, MethodTypeLocation, MixinMemberLocation, ModuleDeclarationLocation,
17+
ModuleSelfLocation, RecordFieldLocation, TypeAliasDeclarationLocation, TypeParamLocation,
18+
UseDirectiveLocation, UseSingleClauseLocation, UseWildcardClauseLocation,
19+
VariableMemberLocation,
1920
};
2021
use crate::ast::members::{
2122
AliasKind, AliasMember, AttrAccessorMember, AttrReaderMember, AttrWriterMember, AttributeKind,
@@ -163,6 +164,7 @@ impl<'a> AstConverter<'a> {
163164
Node::RecordType(node) => {
164165
let mut fields = Vec::new();
165166
for (key, value) in node.all_fields().iter() {
167+
let key_range = key.location();
166168
let key = self.convert_record_key(&key);
167169
let Node::RecordFieldType(field) = value else {
168170
panic_expected("record field value while converting record type", &value);
@@ -171,6 +173,7 @@ impl<'a> AstConverter<'a> {
171173
key,
172174
ty: self.convert_type(&field.type_()),
173175
required: field.required(),
176+
location: Some(record_field_location(key_range, field.location())),
174177
});
175178
}
176179
Type::Record(RecordType {
@@ -466,6 +469,7 @@ impl<'a> AstConverter<'a> {
466469
MethodDefinitionOverload {
467470
method_type: self.convert_method_type_node(&node.method_type()),
468471
annotations: self.convert_annotations(node.annotations()),
472+
location: Some(convert_range(node.location())),
469473
}
470474
}
471475

@@ -929,6 +933,7 @@ impl<'a> AstConverter<'a> {
929933
KeywordParam {
930934
name: self.intern_symbol(&symbol),
931935
param: self.convert_function_param_node(&value),
936+
location: Some(keyword_param_location(symbol.location(), value.location())),
932937
}
933938
})
934939
.collect()
@@ -1068,6 +1073,44 @@ fn convert_optional_range(range: Option<RBSLocationRange>) -> Option<LocationRan
10681073
range.map(convert_range)
10691074
}
10701075

1076+
/// Builds the location of a keyword parameter, which the C AST keeps as a pair
1077+
/// of a key symbol and a function param node instead of a single node.
1078+
fn keyword_param_location(
1079+
name_range: RBSLocationRange,
1080+
param_range: RBSLocationRange,
1081+
) -> KeywordParamLocation {
1082+
let name_range = convert_range(name_range);
1083+
1084+
KeywordParamLocation {
1085+
range: span_range(name_range, convert_range(param_range)),
1086+
name_range,
1087+
}
1088+
}
1089+
1090+
/// Builds the location of a record field, which the C AST keeps as a pair of a
1091+
/// key node and a field type node instead of a single node.
1092+
fn record_field_location(
1093+
key_range: RBSLocationRange,
1094+
field_range: RBSLocationRange,
1095+
) -> RecordFieldLocation {
1096+
let key_range = convert_range(key_range);
1097+
1098+
RecordFieldLocation {
1099+
range: span_range(key_range, convert_range(field_range)),
1100+
key_range,
1101+
}
1102+
}
1103+
1104+
/// Returns the range starting where `start` starts and ending where `end` ends.
1105+
fn span_range(start: LocationRange, end: LocationRange) -> LocationRange {
1106+
LocationRange {
1107+
start_char: start.start_char,
1108+
start_byte: start.start_byte,
1109+
end_char: end.end_char,
1110+
end_byte: end.end_byte,
1111+
}
1112+
}
1113+
10711114
fn variable_member_location(
10721115
range: RBSLocationRange,
10731116
name_range: RBSLocationRange,

‎rust/ruby-rbs/src/ast/location.rs‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,40 @@ pub struct FunctionParamLocation {
7070
pub name_range: Option<LocationRange>,
7171
}
7272

73+
/// ```rbs
74+
/// (name: String) -> void
75+
/// ^^^^^^^^^^^^ range
76+
/// ^^^^ name
77+
///
78+
/// (?size: Integer bytes) -> void
79+
/// ^^^^^^^^^^^^^^^^^^^ range
80+
/// ^^^^ name
81+
/// ```
82+
///
83+
/// The `?` marker of an optional keyword is not part of `range`: it belongs to
84+
/// the enclosing function type, not to the keyword parameter itself.
85+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
86+
pub struct KeywordParamLocation {
87+
pub range: LocationRange,
88+
pub name_range: LocationRange,
89+
}
90+
91+
/// ```rbs
92+
/// { name: String, "id" => Integer }
93+
/// ^^^^^^^^^^^^ range
94+
/// ^^^^ key
95+
/// ^^^^^^^^^^^^^^^ range
96+
/// ^^^^ key
97+
/// ```
98+
///
99+
/// The `?` marker of an optional field is not part of `range`: it belongs to
100+
/// the enclosing record type, not to the field itself.
101+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
102+
pub struct RecordFieldLocation {
103+
pub range: LocationRange,
104+
pub key_range: LocationRange,
105+
}
106+
73107
/// ```rbs
74108
/// _Foo
75109
/// ^^^^ name

‎rust/ruby-rbs/src/ast/members.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::ast::annotation::Annotation;
22
use crate::ast::comment::Comment;
33
use crate::ast::location::{
4-
AliasMemberLocation, AttributeMemberLocation, MethodDefinitionLocation, MixinMemberLocation,
5-
VariableMemberLocation,
4+
AliasMemberLocation, AttributeMemberLocation, LocationRange, MethodDefinitionLocation,
5+
MixinMemberLocation, VariableMemberLocation,
66
};
77
use crate::ast::method_type::MethodType;
88
use crate::ast::types::Type;
@@ -54,6 +54,9 @@ pub struct MethodDefinitionMember {
5454
pub struct MethodDefinitionOverload {
5555
pub method_type: MethodType,
5656
pub annotations: Vec<Annotation>,
57+
/// Starts at the `:` or `|` separator preceding the overload, so that the
58+
/// overloads of a method definition tile its whole type without gaps.
59+
pub location: Option<LocationRange>,
5760
}
5861

5962
#[derive(Clone, Debug, Eq, PartialEq, Hash)]

‎rust/ruby-rbs/src/ast/mod.rs‎

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ pub use location::{
3434
AliasDeclarationLocation, AliasLocation, AliasMemberLocation, AttributeMemberLocation,
3535
ClassDeclarationLocation, ClassInstanceLocation, ClassSingletonLocation, ClassSuperLocation,
3636
ConstantDeclarationLocation, FunctionParamLocation, GlobalDeclarationLocation,
37-
InterfaceDeclarationLocation, InterfaceLocation, LocationRange, MethodDefinitionLocation,
38-
MethodTypeLocation, MixinMemberLocation, ModuleDeclarationLocation, ModuleSelfLocation,
39-
ResolveTypeNamesDirectiveLocation, TypeAliasDeclarationLocation, TypeParamLocation,
40-
UseDirectiveLocation, UseSingleClauseLocation, UseWildcardClauseLocation,
41-
VariableMemberLocation,
37+
InterfaceDeclarationLocation, InterfaceLocation, KeywordParamLocation, LocationRange,
38+
MethodDefinitionLocation, MethodTypeLocation, MixinMemberLocation, ModuleDeclarationLocation,
39+
ModuleSelfLocation, RecordFieldLocation, ResolveTypeNamesDirectiveLocation,
40+
TypeAliasDeclarationLocation, TypeParamLocation, UseDirectiveLocation, UseSingleClauseLocation,
41+
UseWildcardClauseLocation, VariableMemberLocation,
4242
};
4343
pub use members::{
4444
AliasKind, AliasMember, AttrAccessorMember, AttrReaderMember, AttrWriterMember, AttributeKind,
@@ -58,8 +58,9 @@ pub use types::{
5858
#[cfg(test)]
5959
mod tests {
6060
use crate::ast::{
61-
AstConverter, BaseType, BaseTypeKind, ClassMember, Declaration, Directive, IvarName,
62-
Literal, Member, MethodKind, ModuleMember, RecordKey, Type, UseClause,
61+
AstConverter, BaseType, BaseTypeKind, ClassMember, Declaration, Directive, Function,
62+
IvarName, Literal, LocationRange, Member, MethodKind, ModuleMember, RecordKey, Type,
63+
UseClause,
6364
};
6465
use crate::interner::StringInterner;
6566
use crate::node::{Node, parse};
@@ -324,4 +325,101 @@ mod tests {
324325
assert_eq!(type_names.display(wildcard.namespace, &strings), "Foo::Baz");
325326
assert!(wildcard.location.is_some());
326327
}
328+
329+
#[test]
330+
fn converts_keyword_param_locations() {
331+
let source = "class Foo\n def bar: (name: String, ?size: Integer bytes) -> void\nend\n";
332+
let signature = parse(source).unwrap();
333+
334+
let mut strings = StringInterner::new();
335+
let mut type_names = TypeNameInterner::new();
336+
let mut converter = AstConverter::new(&mut strings, &mut type_names);
337+
let declaration =
338+
converter.convert_declaration(&signature.declarations().iter().next().unwrap());
339+
340+
let Declaration::Class(class_decl) = &declaration else {
341+
panic!("expected class declaration");
342+
};
343+
let ClassMember::Member(Member::MethodDefinition(method)) = &class_decl.members[0] else {
344+
panic!("expected method definition member");
345+
};
346+
let Function::Typed(function) = &method.overloads[0].method_type.function else {
347+
panic!("expected typed function");
348+
};
349+
350+
let text =
351+
|range: &LocationRange| &source[range.start_byte as usize..range.end_byte as usize];
352+
353+
let required = &function.required_keywords[0];
354+
let required_location = required.location.as_ref().unwrap();
355+
assert_eq!(required.name, strings.intern("name"));
356+
assert_eq!(text(&required_location.range), "name: String");
357+
assert_eq!(text(&required_location.name_range), "name");
358+
359+
let optional = &function.optional_keywords[0];
360+
let optional_location = optional.location.as_ref().unwrap();
361+
assert_eq!(optional.name, strings.intern("size"));
362+
assert_eq!(text(&optional_location.range), "size: Integer bytes");
363+
assert_eq!(text(&optional_location.name_range), "size");
364+
}
365+
366+
#[test]
367+
fn converts_record_field_locations() {
368+
let source = "type t = { name: String, ?age: Integer, \"id\" => Integer }\n";
369+
let signature = parse(source).unwrap();
370+
371+
let mut strings = StringInterner::new();
372+
let mut type_names = TypeNameInterner::new();
373+
let mut converter = AstConverter::new(&mut strings, &mut type_names);
374+
let declaration =
375+
converter.convert_declaration(&signature.declarations().iter().next().unwrap());
376+
377+
let Declaration::TypeAlias(alias) = &declaration else {
378+
panic!("expected type alias declaration");
379+
};
380+
let Type::Record(record) = &alias.ty else {
381+
panic!("expected record type");
382+
};
383+
384+
let text =
385+
|range: &LocationRange| &source[range.start_byte as usize..range.end_byte as usize];
386+
let location = |index: usize| record.fields[index].location.as_ref().unwrap();
387+
388+
assert_eq!(text(&location(0).range), "name: String");
389+
assert_eq!(text(&location(0).key_range), "name");
390+
391+
// The `?` marker belongs to the record type, not to the field.
392+
assert_eq!(text(&location(1).range), "age: Integer");
393+
assert_eq!(text(&location(1).key_range), "age");
394+
395+
assert_eq!(text(&location(2).range), "\"id\" => Integer");
396+
assert_eq!(text(&location(2).key_range), "\"id\"");
397+
}
398+
399+
#[test]
400+
fn converts_method_definition_overload_locations() {
401+
let source = "class Foo\n def bar: () -> void\n | (Integer) -> String\nend\n";
402+
let signature = parse(source).unwrap();
403+
404+
let mut strings = StringInterner::new();
405+
let mut type_names = TypeNameInterner::new();
406+
let mut converter = AstConverter::new(&mut strings, &mut type_names);
407+
let declaration =
408+
converter.convert_declaration(&signature.declarations().iter().next().unwrap());
409+
410+
let Declaration::Class(class_decl) = &declaration else {
411+
panic!("expected class declaration");
412+
};
413+
let ClassMember::Member(Member::MethodDefinition(method)) = &class_decl.members[0] else {
414+
panic!("expected method definition member");
415+
};
416+
417+
let text =
418+
|range: &LocationRange| &source[range.start_byte as usize..range.end_byte as usize];
419+
let location = |index: usize| method.overloads[index].location.as_ref().unwrap();
420+
421+
// The leading `:`/`|` separator is part of the overload range.
422+
assert_eq!(text(location(0)), ": () -> void");
423+
assert_eq!(text(location(1)), "| (Integer) -> String");
424+
}
327425
}

‎rust/ruby-rbs/src/ast/types.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::ast::location::{
22
AliasLocation, ClassInstanceLocation, ClassSingletonLocation, FunctionParamLocation,
3-
InterfaceLocation, LocationRange,
3+
InterfaceLocation, KeywordParamLocation, LocationRange, RecordFieldLocation,
44
};
55
use crate::ids::{SymbolId, TypeName};
66

@@ -99,6 +99,7 @@ pub struct RecordField {
9999
pub key: RecordKey,
100100
pub ty: Type,
101101
pub required: bool,
102+
pub location: Option<RecordFieldLocation>,
102103
}
103104

104105
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
@@ -136,6 +137,7 @@ pub struct FunctionType {
136137
pub struct KeywordParam {
137138
pub name: SymbolId,
138139
pub param: FunctionParam,
140+
pub location: Option<KeywordParamLocation>,
139141
}
140142

141143
#[derive(Clone, Debug, Eq, PartialEq, Hash)]

0 commit comments

Comments
 (0)