From 783ee1e441f92df124b6a323b89df9dc369c2833 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Mon, 25 May 2026 17:46:17 +0900 Subject: [PATCH 1/5] Add owned Rust AST types --- rust/ruby-rbs/src/ast/annotation.rs | 8 + rust/ruby-rbs/src/ast/comment.rs | 8 + rust/ruby-rbs/src/ast/convert.rs | 485 +++++++++++++++++++++++++++ rust/ruby-rbs/src/ast/location.rs | 124 +++++++ rust/ruby-rbs/src/ast/method_type.rs | 11 + rust/ruby-rbs/src/ast/mod.rs | 105 ++++++ rust/ruby-rbs/src/ast/type_param.rs | 21 ++ rust/ruby-rbs/src/ast/types.rs | 188 +++++++++++ rust/ruby-rbs/src/lib.rs | 1 + rust/ruby-rbs/src/node/mod.rs | 20 ++ 10 files changed, 971 insertions(+) create mode 100644 rust/ruby-rbs/src/ast/annotation.rs create mode 100644 rust/ruby-rbs/src/ast/comment.rs create mode 100644 rust/ruby-rbs/src/ast/convert.rs create mode 100644 rust/ruby-rbs/src/ast/location.rs create mode 100644 rust/ruby-rbs/src/ast/method_type.rs create mode 100644 rust/ruby-rbs/src/ast/mod.rs create mode 100644 rust/ruby-rbs/src/ast/type_param.rs create mode 100644 rust/ruby-rbs/src/ast/types.rs diff --git a/rust/ruby-rbs/src/ast/annotation.rs b/rust/ruby-rbs/src/ast/annotation.rs new file mode 100644 index 000000000..f4592da10 --- /dev/null +++ b/rust/ruby-rbs/src/ast/annotation.rs @@ -0,0 +1,8 @@ +use crate::ast::location::LocationRange; +use crate::ids::SymbolId; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct Annotation { + pub string: SymbolId, + pub location: Option, +} diff --git a/rust/ruby-rbs/src/ast/comment.rs b/rust/ruby-rbs/src/ast/comment.rs new file mode 100644 index 000000000..ad5928226 --- /dev/null +++ b/rust/ruby-rbs/src/ast/comment.rs @@ -0,0 +1,8 @@ +use crate::ast::location::LocationRange; +use crate::ids::SymbolId; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct Comment { + pub string: SymbolId, + pub location: Option, +} diff --git a/rust/ruby-rbs/src/ast/convert.rs b/rust/ruby-rbs/src/ast/convert.rs new file mode 100644 index 000000000..a4dfb76e9 --- /dev/null +++ b/rust/ruby-rbs/src/ast/convert.rs @@ -0,0 +1,485 @@ +use crate::ast::location::{ + AliasLocation, ClassInstanceLocation, ClassSingletonLocation, FunctionParamLocation, + InterfaceLocation, LocationRange, MethodTypeLocation, TypeParamLocation, +}; +use crate::ast::method_type::MethodType; +use crate::ast::type_param::{TypeParam, Variance}; +use crate::ast::types::{ + AliasType, BaseType, BaseTypeKind, BlockType, ClassInstanceType, ClassSingletonType, Function, + FunctionParam, FunctionType, InterfaceType, IntersectionType, KeywordParam, Literal, + LiteralType, OptionalType, ProcType, RecordField, RecordKey, RecordType, TupleType, Type, + UnionType, UntypedFunctionType, VariableType, +}; +use crate::ids::{SymbolId, TypeName}; +use crate::interner::StringInterner; +use crate::node::{ + AliasTypeNode, BlockTypeNode, ClassInstanceTypeNode, ClassSingletonTypeNode, FunctionParamNode, + FunctionTypeNode, InterfaceTypeNode, MethodTypeNode, Node, RBSLocationRange, SymbolNode, + TypeNameNode, TypeParamNode, TypeParamVariance, UntypedFunctionTypeNode, +}; +use crate::type_name::TypeNameInterner; + +pub struct AstConverter<'a> { + strings: &'a mut StringInterner, + type_names: &'a mut TypeNameInterner, +} + +impl<'a> AstConverter<'a> { + pub fn new(strings: &'a mut StringInterner, type_names: &'a mut TypeNameInterner) -> Self { + Self { + strings, + type_names, + } + } + + pub fn convert_type(&mut self, node: &Node<'_>) -> Type { + match node { + Node::AliasType(node) => Type::Alias(self.convert_alias_type(node)), + Node::AnyType(node) => Type::Base(BaseType { + kind: BaseTypeKind::Any { todo: node.todo() }, + location: Some(convert_range(node.location())), + }), + Node::BoolType(node) => self.base(BaseTypeKind::Bool, node.location()), + Node::BottomType(node) => self.base(BaseTypeKind::Bottom, node.location()), + Node::ClassType(node) => self.base(BaseTypeKind::Class, node.location()), + Node::InstanceType(node) => self.base(BaseTypeKind::Instance, node.location()), + Node::NilType(node) => self.base(BaseTypeKind::Nil, node.location()), + Node::SelfType(node) => self.base(BaseTypeKind::SelfType, node.location()), + Node::TopType(node) => self.base(BaseTypeKind::Top, node.location()), + Node::VoidType(node) => self.base(BaseTypeKind::Void, node.location()), + Node::ClassInstanceType(node) => { + Type::ClassInstance(self.convert_class_instance_type(node)) + } + Node::ClassSingletonType(node) => { + Type::ClassSingleton(self.convert_class_singleton_type(node)) + } + Node::InterfaceType(node) => Type::Interface(self.convert_interface_type(node)), + Node::IntersectionType(node) => Type::Intersection(IntersectionType { + types: self.convert_type_list(node.types()), + location: Some(convert_range(node.location())), + }), + Node::LiteralType(node) => Type::Literal(LiteralType { + literal: self.convert_literal(&node.literal()), + location: Some(convert_range(node.location())), + }), + Node::OptionalType(node) => Type::Optional(OptionalType { + ty: Box::new(self.convert_type(&node.type_())), + location: Some(convert_range(node.location())), + }), + Node::ProcType(node) => Type::Proc(ProcType { + function: self.convert_function_type(&node.type_()), + block: node.block().map(|block| self.convert_block_type(&block)), + self_type: node.self_type().map(|ty| Box::new(self.convert_type(&ty))), + location: Some(convert_range(node.location())), + }), + Node::RecordType(node) => { + let mut fields = Vec::new(); + for (key, value) in node.all_fields().iter() { + let key = self.convert_record_key(&key); + let Node::RecordFieldType(field) = value else { + panic_expected("record field value while converting record type", &value); + }; + fields.push(RecordField { + key, + ty: self.convert_type(&field.type_()), + required: field.required(), + }); + } + Type::Record(RecordType { + fields, + location: Some(convert_range(node.location())), + }) + } + Node::TupleType(node) => Type::Tuple(TupleType { + types: self.convert_type_list(node.types()), + location: Some(convert_range(node.location())), + }), + Node::UnionType(node) => Type::Union(UnionType { + types: self.convert_type_list(node.types()), + location: Some(convert_range(node.location())), + }), + Node::VariableType(node) => Type::Variable(VariableType { + name: self.intern_symbol(&node.name()), + location: Some(convert_range(node.location())), + }), + _ => panic_expected("type node while converting type", node), + } + } + + pub fn convert_function_type(&mut self, node: &Node<'_>) -> Function { + match node { + Node::FunctionType(node) => Function::Typed(self.convert_function(node)), + Node::UntypedFunctionType(node) => { + Function::Untyped(self.convert_untyped_function(node)) + } + _ => panic_expected("function type node while converting function type", node), + } + } + + pub fn convert_method_type(&mut self, node: &MethodTypeNode<'_>) -> MethodType { + MethodType { + type_params: self.convert_type_params(node.type_params()), + function: self.convert_function_type(&node.type_()), + block: node.block().map(|block| self.convert_block_type(&block)), + location: Some(MethodTypeLocation { + range: convert_range(node.location()), + type_range: convert_range(node.type_location()), + type_params_range: convert_optional_range(node.type_params_location()), + }), + } + } + + fn base(&self, kind: BaseTypeKind, location: RBSLocationRange) -> Type { + Type::Base(BaseType { + kind, + location: Some(convert_range(location)), + }) + } + + fn convert_alias_type(&mut self, node: &AliasTypeNode<'_>) -> AliasType { + AliasType { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + location: Some(AliasLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + args_range: convert_optional_range(node.args_location()), + }), + } + } + + fn convert_class_instance_type( + &mut self, + node: &ClassInstanceTypeNode<'_>, + ) -> ClassInstanceType { + ClassInstanceType { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + location: Some(ClassInstanceLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + args_range: convert_optional_range(node.args_location()), + }), + } + } + + fn convert_class_singleton_type( + &mut self, + node: &ClassSingletonTypeNode<'_>, + ) -> ClassSingletonType { + ClassSingletonType { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + location: Some(ClassSingletonLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + args_range: convert_optional_range(node.args_location()), + }), + } + } + + fn convert_interface_type(&mut self, node: &InterfaceTypeNode<'_>) -> InterfaceType { + InterfaceType { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + location: Some(InterfaceLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + args_range: convert_optional_range(node.args_location()), + }), + } + } + + fn convert_function(&mut self, node: &FunctionTypeNode<'_>) -> FunctionType { + FunctionType { + required_positionals: self.convert_function_params(node.required_positionals()), + optional_positionals: self.convert_function_params(node.optional_positionals()), + rest_positionals: node + .rest_positionals() + .map(|param| Box::new(self.convert_function_param_node(¶m))), + trailing_positionals: self.convert_function_params(node.trailing_positionals()), + required_keywords: self.convert_keyword_params(node.required_keywords()), + optional_keywords: self.convert_keyword_params(node.optional_keywords()), + rest_keywords: node + .rest_keywords() + .map(|param| Box::new(self.convert_function_param_node(¶m))), + return_type: Box::new(self.convert_type(&node.return_type())), + location: Some(convert_range(node.location())), + } + } + + fn convert_untyped_function( + &mut self, + node: &UntypedFunctionTypeNode<'_>, + ) -> UntypedFunctionType { + UntypedFunctionType { + return_type: Box::new(self.convert_type(&node.return_type())), + location: Some(convert_range(node.location())), + } + } + + fn convert_block_type(&mut self, node: &BlockTypeNode<'_>) -> BlockType { + BlockType { + function: self.convert_function_type(&node.type_()), + required: node.required(), + self_type: node.self_type().map(|ty| Box::new(self.convert_type(&ty))), + location: Some(convert_range(node.location())), + } + } + + fn convert_function_param_node(&mut self, node: &Node<'_>) -> FunctionParam { + let Node::FunctionParam(node) = node else { + panic_expected( + "function parameter node while converting function type", + node, + ); + }; + self.convert_function_param(node) + } + + fn convert_function_param(&mut self, node: &FunctionParamNode<'_>) -> FunctionParam { + FunctionParam { + ty: Box::new(self.convert_type(&node.type_())), + name: node.name().map(|name| self.intern_symbol(&name)), + location: Some(FunctionParamLocation { + range: convert_range(node.location()), + name_range: convert_optional_range(node.name_location()), + }), + } + } + + fn convert_type_param_node(&mut self, node: &Node<'_>) -> TypeParam { + let Node::TypeParam(node) = node else { + panic_expected("type parameter node while converting type parameters", node); + }; + self.convert_type_param(node) + } + + fn convert_type_param(&mut self, node: &TypeParamNode<'_>) -> TypeParam { + TypeParam { + name: self.intern_symbol(&node.name()), + variance: convert_variance(node.variance()), + upper_bound: node.upper_bound().map(|ty| self.convert_type(&ty)), + lower_bound: node.lower_bound().map(|ty| self.convert_type(&ty)), + default_type: node.default_type().map(|ty| self.convert_type(&ty)), + unchecked: node.unchecked(), + location: Some(TypeParamLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + variance_range: convert_optional_range(node.variance_location()), + unchecked_range: convert_optional_range(node.unchecked_location()), + upper_bound_range: convert_optional_range(node.upper_bound_location()), + lower_bound_range: convert_optional_range(node.lower_bound_location()), + default_range: convert_optional_range(node.default_location()), + }), + } + } + + fn convert_type_list(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter().map(|node| self.convert_type(&node)).collect() + } + + fn convert_function_params(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter() + .map(|node| self.convert_function_param_node(&node)) + .collect() + } + + fn convert_type_params(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter() + .map(|node| self.convert_type_param_node(&node)) + .collect() + } + + fn convert_keyword_params(&mut self, hash: crate::node::RBSHash<'_>) -> Vec { + hash.iter() + .map(|(key, value)| { + let Node::Symbol(symbol) = key else { + panic_expected( + "symbol keyword name while converting keyword parameter", + &key, + ); + }; + KeywordParam { + name: self.intern_symbol(&symbol), + param: self.convert_function_param_node(&value), + } + }) + .collect() + } + + fn convert_record_key(&mut self, node: &Node<'_>) -> RecordKey { + match node { + Node::Symbol(symbol) => RecordKey::Symbol(self.intern_symbol(symbol)), + Node::String(string) => RecordKey::String(string.string().as_str().to_owned()), + Node::Integer(integer) => { + RecordKey::Integer(integer.string_representation().as_str().to_owned()) + } + Node::Bool(boolean) => RecordKey::Bool(boolean.value()), + _ => panic_expected("record key while converting record type", node), + } + } + + fn convert_literal(&mut self, node: &Node<'_>) -> Literal { + match node { + Node::String(string) => Literal::String(string.string().as_str().to_owned()), + Node::Integer(integer) => { + Literal::Integer(integer.string_representation().as_str().to_owned()) + } + Node::Symbol(symbol) => Literal::Symbol(self.intern_symbol(symbol)), + Node::Bool(boolean) => Literal::Bool(boolean.value()), + _ => panic_expected("literal value while converting literal type", node), + } + } + + fn convert_type_name(&mut self, node: &TypeNameNode<'_>) -> TypeName { + let namespace = node.namespace(); + let mut name = self.type_names.root(namespace.absolute()); + for segment_node in namespace.path().iter() { + match segment_node { + Node::Symbol(segment) => { + let segment = self.intern_symbol(&segment); + name = self.type_names.append(name, segment); + } + _ => panic_expected( + "symbol in type name namespace path while converting type name", + &segment_node, + ), + } + } + let final_segment = self.intern_symbol(&node.name()); + self.type_names.append(name, final_segment) + } + + fn intern_symbol(&mut self, node: &SymbolNode<'_>) -> SymbolId { + self.strings.intern(node.as_str()) + } +} + +fn convert_range(range: RBSLocationRange) -> LocationRange { + let start_char = range.start_char(); + let start_byte = range.start_byte(); + let end_char = range.end_char(); + let end_byte = range.end_byte(); + + LocationRange { + start_char: convert_range_component("start_char", start_char, &range), + start_byte: convert_range_component("start_byte", start_byte, &range), + end_char: convert_range_component("end_char", end_char, &range), + end_byte: convert_range_component("end_byte", end_byte, &range), + } +} + +fn convert_range_component(name: &str, value: i32, range: &RBSLocationRange) -> u32 { + u32::try_from(value).unwrap_or_else(|_| { + panic!( + "invalid RBS location range while converting to owned AST: {name} must be non-negative and fit in u32, got {value}; full range is start_char={}, start_byte={}, end_char={}, end_byte={}", + range.start_char(), + range.start_byte(), + range.end_char(), + range.end_byte() + ) + }) +} + +fn convert_optional_range(range: Option) -> Option { + range.map(convert_range) +} + +fn convert_variance(variance: TypeParamVariance) -> Variance { + match variance { + TypeParamVariance::Invariant => Variance::Invariant, + TypeParamVariance::Covariant => Variance::Covariant, + TypeParamVariance::Contravariant => Variance::Contravariant, + } +} + +fn panic_expected(expected: &str, actual: &Node<'_>) -> ! { + panic!( + "invalid RBS AST while converting to owned AST: expected {expected}, got {}", + node_kind(actual) + ) +} + +fn node_kind(node: &Node<'_>) -> &'static str { + match node { + Node::Annotation(_) => "Annotation", + Node::Bool(_) => "Bool", + Node::Comment(_) => "Comment", + Node::Class(_) => "Class", + Node::ClassSuper(_) => "ClassSuper", + Node::ClassAlias(_) => "ClassAlias", + Node::Constant(_) => "Constant", + Node::Global(_) => "Global", + Node::Interface(_) => "Interface", + Node::Module(_) => "Module", + Node::ModuleSelf(_) => "ModuleSelf", + Node::ModuleAlias(_) => "ModuleAlias", + Node::TypeAlias(_) => "TypeAlias", + Node::Use(_) => "Use", + Node::UseSingleClause(_) => "UseSingleClause", + Node::UseWildcardClause(_) => "UseWildcardClause", + Node::Integer(_) => "Integer", + Node::Alias(_) => "Alias", + Node::AttrAccessor(_) => "AttrAccessor", + Node::AttrReader(_) => "AttrReader", + Node::AttrWriter(_) => "AttrWriter", + Node::ClassInstanceVariable(_) => "ClassInstanceVariable", + Node::ClassVariable(_) => "ClassVariable", + Node::Extend(_) => "Extend", + Node::Include(_) => "Include", + Node::InstanceVariable(_) => "InstanceVariable", + Node::MethodDefinition(_) => "MethodDefinition", + Node::MethodDefinitionOverload(_) => "MethodDefinitionOverload", + Node::Prepend(_) => "Prepend", + Node::Private(_) => "Private", + Node::Public(_) => "Public", + Node::BlockParamTypeAnnotation(_) => "BlockParamTypeAnnotation", + Node::ClassAliasAnnotation(_) => "ClassAliasAnnotation", + Node::ColonMethodTypeAnnotation(_) => "ColonMethodTypeAnnotation", + Node::DoubleSplatParamTypeAnnotation(_) => "DoubleSplatParamTypeAnnotation", + Node::InstanceVariableAnnotation(_) => "InstanceVariableAnnotation", + Node::MethodTypesAnnotation(_) => "MethodTypesAnnotation", + Node::ModuleAliasAnnotation(_) => "ModuleAliasAnnotation", + Node::ModuleSelfAnnotation(_) => "ModuleSelfAnnotation", + Node::NodeTypeAssertion(_) => "NodeTypeAssertion", + Node::ParamTypeAnnotation(_) => "ParamTypeAnnotation", + Node::ReturnTypeAnnotation(_) => "ReturnTypeAnnotation", + Node::SkipAnnotation(_) => "SkipAnnotation", + Node::SplatParamTypeAnnotation(_) => "SplatParamTypeAnnotation", + Node::TypeApplicationAnnotation(_) => "TypeApplicationAnnotation", + Node::String(_) => "String", + Node::Symbol(_) => "Symbol", + Node::TypeParam(_) => "TypeParam", + Node::MethodType(_) => "MethodType", + Node::Namespace(_) => "Namespace", + Node::Signature(_) => "Signature", + Node::TypeName(_) => "TypeName", + Node::AliasType(_) => "AliasType", + Node::AnyType(_) => "AnyType", + Node::BoolType(_) => "BoolType", + Node::BottomType(_) => "BottomType", + Node::ClassType(_) => "ClassType", + Node::InstanceType(_) => "InstanceType", + Node::NilType(_) => "NilType", + Node::SelfType(_) => "SelfType", + Node::TopType(_) => "TopType", + Node::VoidType(_) => "VoidType", + Node::BlockType(_) => "BlockType", + Node::ClassInstanceType(_) => "ClassInstanceType", + Node::ClassSingletonType(_) => "ClassSingletonType", + Node::FunctionType(_) => "FunctionType", + Node::FunctionParam(_) => "FunctionParam", + Node::InterfaceType(_) => "InterfaceType", + Node::IntersectionType(_) => "IntersectionType", + Node::LiteralType(_) => "LiteralType", + Node::OptionalType(_) => "OptionalType", + Node::ProcType(_) => "ProcType", + Node::RecordType(_) => "RecordType", + Node::RecordFieldType(_) => "RecordFieldType", + Node::TupleType(_) => "TupleType", + Node::UnionType(_) => "UnionType", + Node::UntypedFunctionType(_) => "UntypedFunctionType", + Node::VariableType(_) => "VariableType", + } +} diff --git a/rust/ruby-rbs/src/ast/location.rs b/rust/ruby-rbs/src/ast/location.rs new file mode 100644 index 000000000..dda1b86b5 --- /dev/null +++ b/rust/ruby-rbs/src/ast/location.rs @@ -0,0 +1,124 @@ +/// A byte and character range in the source buffer. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct LocationRange { + pub start_char: u32, + pub start_byte: u32, + pub end_char: u32, + pub end_byte: u32, +} + +impl LocationRange { + #[must_use] + pub fn new(start_char: u32, start_byte: u32, end_char: u32, end_byte: u32) -> Self { + Self { + start_char, + start_byte, + end_char, + end_byte, + } + } +} + +/// ```rbs +/// foo +/// ^^^ name +/// +/// foo[bar, baz] +/// ^^^ name +/// ^^^^^^^^^^ args +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AliasLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub args_range: Option, +} + +/// ```rbs +/// Foo +/// ^^^ name +/// +/// Foo[Bar, Baz] +/// ^^^ name +/// ^^^^^^^^^^ args +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassInstanceLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub args_range: Option, +} + +/// ```rbs +/// singleton(::Foo) +/// ^^^^^ name +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassSingletonLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub args_range: Option, +} + +/// ```rbs +/// String name +/// ^^^^ name +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct FunctionParamLocation { + pub range: LocationRange, + pub name_range: Option, +} + +/// ```rbs +/// _Foo +/// ^^^^ name +/// +/// _Foo[Bar, Baz] +/// ^^^^ name +/// ^^^^^^^^^^ args +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct InterfaceLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub args_range: Option, +} + +/// ```rbs +/// () -> void +/// ^^^^^^^^^^ type +/// +/// [A] () { () -> A } -> A +/// ^^^ type_params +/// ^^^^^^^^^^^^^^^^^^^ type +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct MethodTypeLocation { + pub range: LocationRange, + pub type_range: LocationRange, + pub type_params_range: Option, +} + +/// ```rbs +/// Key +/// ^^^ name +/// +/// unchecked out Elem < _ToJson > bot = untyped +/// ^^^^^^^^^ unchecked +/// ^^^ variance +/// ^^^^ name +/// ^^^^^^^^^ upper_bound +/// ^^^^^ lower_bound +/// ^^^^^^^^ default +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct TypeParamLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub variance_range: Option, + pub unchecked_range: Option, + pub upper_bound_range: Option, + pub lower_bound_range: Option, + pub default_range: Option, +} diff --git a/rust/ruby-rbs/src/ast/method_type.rs b/rust/ruby-rbs/src/ast/method_type.rs new file mode 100644 index 000000000..9c9abe6a2 --- /dev/null +++ b/rust/ruby-rbs/src/ast/method_type.rs @@ -0,0 +1,11 @@ +use crate::ast::location::MethodTypeLocation; +use crate::ast::type_param::TypeParam; +use crate::ast::types::{BlockType, Function}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct MethodType { + pub type_params: Vec, + pub function: Function, + pub block: Option, + pub location: Option, +} diff --git a/rust/ruby-rbs/src/ast/mod.rs b/rust/ruby-rbs/src/ast/mod.rs new file mode 100644 index 000000000..f6a97e2df --- /dev/null +++ b/rust/ruby-rbs/src/ast/mod.rs @@ -0,0 +1,105 @@ +//! Owned pure-Rust AST data structures. +//! +//! The [`node`] module exposes borrowed wrappers over the C parser AST. This +//! module is the Rust-owned representation that can be built from parser nodes, +//! generated directly, or transformed without keeping the parser allocation +//! alive. +//! +//! [`node`]: crate::node + +pub mod annotation; +pub mod comment; +pub mod convert; +pub mod location; +pub mod method_type; +pub mod type_param; +pub mod types; + +pub use annotation::Annotation; +pub use comment::Comment; +pub use convert::AstConverter; +pub use location::{ + AliasLocation, ClassInstanceLocation, ClassSingletonLocation, FunctionParamLocation, + InterfaceLocation, LocationRange, MethodTypeLocation, TypeParamLocation, +}; +pub use method_type::MethodType; +pub use type_param::{TypeParam, Variance}; +pub use types::{ + AliasType, BaseType, BaseTypeKind, BlockType, ClassInstanceType, ClassSingletonType, Function, + FunctionParam, FunctionType, InterfaceType, IntersectionType, KeywordParam, Literal, + LiteralType, OptionalType, ProcType, RecordField, RecordKey, RecordType, TupleType, Type, + UnionType, UntypedFunctionType, VariableType, +}; + +#[cfg(test)] +mod tests { + use crate::ast::{AstConverter, BaseType, BaseTypeKind, Literal, RecordKey, Type}; + use crate::interner::StringInterner; + use crate::node::{Node, parse}; + use crate::type_name::TypeNameInterner; + + #[test] + fn builds_owned_type_ast() { + let ty = Type::Base(BaseType { + kind: BaseTypeKind::Any { todo: false }, + location: None, + }); + + assert_eq!( + ty, + Type::Base(BaseType { + kind: BaseTypeKind::Any { todo: false }, + location: None, + }) + ); + } + + #[test] + fn converts_type_node_to_owned_ast() { + let signature = parse( + r#"type foo = { + name: String, + ?age: Integer, + active: true, + tags: Array[String | Symbol] + }"#, + ) + .unwrap(); + + let Node::TypeAlias(alias) = signature.declarations().iter().next().unwrap() else { + panic!("expected type alias"); + }; + + let mut strings = StringInterner::new(); + let mut type_names = TypeNameInterner::new(); + let mut converter = AstConverter::new(&mut strings, &mut type_names); + let ty = converter.convert_type(&alias.type_()); + + let Type::Record(record) = ty else { + panic!("expected record type"); + }; + + assert_eq!(record.fields.len(), 4); + assert_eq!( + record.fields[0].key, + RecordKey::Symbol(strings.intern("name")) + ); + assert!(record.fields[0].required); + assert_eq!( + record.fields[1].key, + RecordKey::Symbol(strings.intern("age")) + ); + assert!(!record.fields[1].required); + + let Type::Literal(literal) = &record.fields[2].ty else { + panic!("expected literal type"); + }; + assert_eq!(literal.literal, Literal::Bool(true)); + + let Type::ClassInstance(array) = &record.fields[3].ty else { + panic!("expected Array class instance"); + }; + assert_eq!(type_names.display(array.name, &strings), "Array"); + assert_eq!(array.args.len(), 1); + } +} diff --git a/rust/ruby-rbs/src/ast/type_param.rs b/rust/ruby-rbs/src/ast/type_param.rs new file mode 100644 index 000000000..4b2a979e6 --- /dev/null +++ b/rust/ruby-rbs/src/ast/type_param.rs @@ -0,0 +1,21 @@ +use crate::ast::location::TypeParamLocation; +use crate::ast::types::Type; +use crate::ids::SymbolId; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Variance { + Invariant, + Covariant, + Contravariant, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct TypeParam { + pub name: SymbolId, + pub variance: Variance, + pub upper_bound: Option, + pub lower_bound: Option, + pub default_type: Option, + pub unchecked: bool, + pub location: Option, +} diff --git a/rust/ruby-rbs/src/ast/types.rs b/rust/ruby-rbs/src/ast/types.rs new file mode 100644 index 000000000..317b8d371 --- /dev/null +++ b/rust/ruby-rbs/src/ast/types.rs @@ -0,0 +1,188 @@ +use crate::ast::location::{ + AliasLocation, ClassInstanceLocation, ClassSingletonLocation, FunctionParamLocation, + InterfaceLocation, LocationRange, +}; +use crate::ids::{SymbolId, TypeName}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum Type { + Base(BaseType), + Variable(VariableType), + ClassSingleton(ClassSingletonType), + Interface(InterfaceType), + ClassInstance(ClassInstanceType), + Alias(AliasType), + Tuple(TupleType), + Record(RecordType), + Optional(OptionalType), + Union(UnionType), + Intersection(IntersectionType), + Proc(ProcType), + Literal(LiteralType), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct BaseType { + pub kind: BaseTypeKind, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum BaseTypeKind { + Bool, + Void, + Any { todo: bool }, + Nil, + Top, + Bottom, + SelfType, + Instance, + Class, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct VariableType { + pub name: SymbolId, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassSingletonType { + pub name: TypeName, + pub args: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct InterfaceType { + pub name: TypeName, + pub args: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassInstanceType { + pub name: TypeName, + pub args: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AliasType { + pub name: TypeName, + pub args: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct TupleType { + pub types: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct RecordType { + pub fields: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum RecordKey { + Symbol(SymbolId), + String(String), + Integer(String), + Bool(bool), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct RecordField { + pub key: RecordKey, + pub ty: Type, + pub required: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct OptionalType { + pub ty: Box, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct UnionType { + pub types: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct IntersectionType { + pub types: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct FunctionType { + pub required_positionals: Vec, + pub optional_positionals: Vec, + pub rest_positionals: Option>, + pub trailing_positionals: Vec, + pub required_keywords: Vec, + pub optional_keywords: Vec, + pub rest_keywords: Option>, + pub return_type: Box, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct KeywordParam { + pub name: SymbolId, + pub param: FunctionParam, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct FunctionParam { + pub ty: Box, + pub name: Option, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct UntypedFunctionType { + pub return_type: Box, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum Function { + Typed(FunctionType), + Untyped(UntypedFunctionType), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct BlockType { + pub function: Function, + pub required: bool, + pub self_type: Option>, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ProcType { + pub function: Function, + pub block: Option, + pub self_type: Option>, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct LiteralType { + pub literal: Literal, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum Literal { + String(String), + Integer(String), + Symbol(SymbolId), + Bool(bool), +} diff --git a/rust/ruby-rbs/src/lib.rs b/rust/ruby-rbs/src/lib.rs index d63bbfa3c..328898158 100644 --- a/rust/ruby-rbs/src/lib.rs +++ b/rust/ruby-rbs/src/lib.rs @@ -1,3 +1,4 @@ +pub mod ast; pub mod ids; pub mod interner; pub mod node; diff --git a/rust/ruby-rbs/src/node/mod.rs b/rust/ruby-rbs/src/node/mod.rs index 2f52b5048..e53aae7a0 100644 --- a/rust/ruby-rbs/src/node/mod.rs +++ b/rust/ruby-rbs/src/node/mod.rs @@ -199,6 +199,26 @@ impl RBSLocationRange { pub fn end(&self) -> i32 { self.range.end_byte } + + #[must_use] + pub fn start_char(&self) -> i32 { + self.range.start_char + } + + #[must_use] + pub fn start_byte(&self) -> i32 { + self.range.start_byte + } + + #[must_use] + pub fn end_char(&self) -> i32 { + self.range.end_char + } + + #[must_use] + pub fn end_byte(&self) -> i32 { + self.range.end_byte + } } pub struct RBSLocationRangeList<'a> { From c5665f9668ef462c1df38665092e5f3ca0806a3a Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Tue, 2 Jun 2026 17:50:26 +0900 Subject: [PATCH 2/5] Add owned AST declarations and members --- rust/ruby-rbs/src/ast/convert.rs | 712 +++++++++++++++++++++++++- rust/ruby-rbs/src/ast/declarations.rs | 127 +++++ rust/ruby-rbs/src/ast/location.rs | 263 ++++++++++ rust/ruby-rbs/src/ast/members.rs | 183 +++++++ rust/ruby-rbs/src/ast/mod.rs | 171 ++++++- rust/ruby-rbs/src/node/mod.rs | 50 ++ 6 files changed, 1498 insertions(+), 8 deletions(-) create mode 100644 rust/ruby-rbs/src/ast/declarations.rs create mode 100644 rust/ruby-rbs/src/ast/members.rs diff --git a/rust/ruby-rbs/src/ast/convert.rs b/rust/ruby-rbs/src/ast/convert.rs index a4dfb76e9..5fe56e8ae 100644 --- a/rust/ruby-rbs/src/ast/convert.rs +++ b/rust/ruby-rbs/src/ast/convert.rs @@ -1,6 +1,23 @@ +use crate::ast::annotation::Annotation; +use crate::ast::comment::Comment; +use crate::ast::declarations::{ + ClassAliasDeclaration, ClassDeclaration, ClassMember, ClassSuper, ConstantDeclaration, + Declaration, GlobalDeclaration, InterfaceDeclaration, ModuleAliasDeclaration, + ModuleDeclaration, ModuleMember, ModuleSelf, TypeAliasDeclaration, +}; use crate::ast::location::{ - AliasLocation, ClassInstanceLocation, ClassSingletonLocation, FunctionParamLocation, - InterfaceLocation, LocationRange, MethodTypeLocation, TypeParamLocation, + AliasDeclarationLocation, AliasLocation, AliasMemberLocation, AttributeMemberLocation, + ClassDeclarationLocation, ClassInstanceLocation, ClassSingletonLocation, ClassSuperLocation, + ConstantDeclarationLocation, FunctionParamLocation, GlobalDeclarationLocation, + InterfaceDeclarationLocation, InterfaceLocation, LocationRange, MethodDefinitionLocation, + MethodTypeLocation, MixinMemberLocation, ModuleDeclarationLocation, ModuleSelfLocation, + TypeAliasDeclarationLocation, TypeParamLocation, VariableMemberLocation, +}; +use crate::ast::members::{ + AliasKind, AliasMember, AttrAccessorMember, AttrReaderMember, AttrWriterMember, AttributeKind, + ClassInstanceVariableMember, ClassVariableMember, ExtendMember, IncludeMember, + InstanceVariableMember, IvarName, Member, MethodDefinitionMember, MethodDefinitionOverload, + MethodKind, PrependMember, PrivateMember, PublicMember, Visibility, }; use crate::ast::method_type::MethodType; use crate::ast::type_param::{TypeParam, Variance}; @@ -13,9 +30,17 @@ use crate::ast::types::{ use crate::ids::{SymbolId, TypeName}; use crate::interner::StringInterner; use crate::node::{ - AliasTypeNode, BlockTypeNode, ClassInstanceTypeNode, ClassSingletonTypeNode, FunctionParamNode, - FunctionTypeNode, InterfaceTypeNode, MethodTypeNode, Node, RBSLocationRange, SymbolNode, - TypeNameNode, TypeParamNode, TypeParamVariance, UntypedFunctionTypeNode, + AliasKind as NodeAliasKind, AliasNode, AliasTypeNode, AnnotationNode, AttrAccessorNode, + AttrIvarName, AttrReaderNode, AttrWriterNode, AttributeKind as NodeAttributeKind, + AttributeVisibility as NodeAttributeVisibility, BlockTypeNode, ClassAliasNode, + ClassInstanceTypeNode, ClassInstanceVariableNode, ClassNode, ClassSingletonTypeNode, + ClassSuperNode, ClassVariableNode, CommentNode, ConstantNode, ExtendNode, FunctionParamNode, + FunctionTypeNode, GlobalNode, IncludeNode, InstanceVariableNode, InterfaceNode, + InterfaceTypeNode, MethodDefinitionKind as NodeMethodDefinitionKind, MethodDefinitionNode, + MethodDefinitionOverloadNode, MethodDefinitionVisibility as NodeMethodDefinitionVisibility, + MethodTypeNode, ModuleAliasNode, ModuleNode, ModuleSelfNode, Node, PrependNode, PrivateNode, + PublicNode, RBSLocationRange, SymbolNode, TypeAliasNode, TypeNameNode, TypeParamNode, + TypeParamVariance, UntypedFunctionTypeNode, }; use crate::type_name::TypeNameInterner; @@ -32,6 +57,57 @@ impl<'a> AstConverter<'a> { } } + pub fn convert_declaration(&mut self, node: &Node<'_>) -> Declaration { + match node { + Node::Class(node) => Declaration::Class(self.convert_class_declaration(node)), + Node::Module(node) => Declaration::Module(self.convert_module_declaration(node)), + Node::Interface(node) => { + Declaration::Interface(self.convert_interface_declaration(node)) + } + Node::Constant(node) => Declaration::Constant(self.convert_constant_declaration(node)), + Node::Global(node) => Declaration::Global(self.convert_global_declaration(node)), + Node::TypeAlias(node) => { + Declaration::TypeAlias(self.convert_type_alias_declaration(node)) + } + Node::ClassAlias(node) => { + Declaration::ClassAlias(self.convert_class_alias_declaration(node)) + } + Node::ModuleAlias(node) => { + Declaration::ModuleAlias(self.convert_module_alias_declaration(node)) + } + _ => panic_expected("declaration node while converting declaration", node), + } + } + + pub fn convert_member(&mut self, node: &Node<'_>) -> Member { + match node { + Node::MethodDefinition(node) => { + Member::MethodDefinition(self.convert_method_definition_member(node)) + } + Node::InstanceVariable(node) => { + Member::InstanceVariable(self.convert_instance_variable_member(node)) + } + Node::ClassInstanceVariable(node) => { + Member::ClassInstanceVariable(self.convert_class_instance_variable_member(node)) + } + Node::ClassVariable(node) => { + Member::ClassVariable(self.convert_class_variable_member(node)) + } + Node::Include(node) => Member::Include(self.convert_include_member(node)), + Node::Extend(node) => Member::Extend(self.convert_extend_member(node)), + Node::Prepend(node) => Member::Prepend(self.convert_prepend_member(node)), + Node::AttrReader(node) => Member::AttrReader(self.convert_attr_reader_member(node)), + Node::AttrWriter(node) => Member::AttrWriter(self.convert_attr_writer_member(node)), + Node::AttrAccessor(node) => { + Member::AttrAccessor(self.convert_attr_accessor_member(node)) + } + Node::Public(node) => Member::Public(self.convert_public_member(node)), + Node::Private(node) => Member::Private(self.convert_private_member(node)), + Node::Alias(node) => Member::Alias(self.convert_alias_member(node)), + _ => panic_expected("member node while converting member", node), + } + } + pub fn convert_type(&mut self, node: &Node<'_>) -> Type { match node { Node::AliasType(node) => Type::Alias(self.convert_alias_type(node)), @@ -136,6 +212,396 @@ impl<'a> AstConverter<'a> { }) } + fn convert_class_declaration(&mut self, node: &ClassNode<'_>) -> ClassDeclaration { + ClassDeclaration { + name: self.convert_type_name(&node.name()), + type_params: self.convert_type_params(node.type_params()), + members: self.convert_class_members(node.members()), + super_class: node + .super_class() + .map(|super_class| self.convert_class_super(&super_class)), + annotations: self.convert_annotations(node.annotations()), + location: Some(ClassDeclarationLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + name_range: convert_range(node.name_location()), + end_range: convert_range(node.end_location()), + type_params_range: convert_optional_range(node.type_params_location()), + lt_range: convert_optional_range(node.lt_location()), + }), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_class_super(&mut self, node: &ClassSuperNode<'_>) -> ClassSuper { + ClassSuper { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + location: Some(ClassSuperLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + args_range: convert_optional_range(node.args_location()), + }), + } + } + + fn convert_module_declaration(&mut self, node: &ModuleNode<'_>) -> ModuleDeclaration { + ModuleDeclaration { + name: self.convert_type_name(&node.name()), + type_params: self.convert_type_params(node.type_params()), + members: self.convert_module_members(node.members()), + location: Some(ModuleDeclarationLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + name_range: convert_range(node.name_location()), + end_range: convert_range(node.end_location()), + type_params_range: convert_optional_range(node.type_params_location()), + colon_range: convert_optional_range(node.colon_location()), + self_types_range: convert_optional_range(node.self_types_location()), + }), + annotations: self.convert_annotations(node.annotations()), + self_types: self.convert_module_selfs(node.self_types()), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_module_self(&mut self, node: &ModuleSelfNode<'_>) -> ModuleSelf { + ModuleSelf { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + location: Some(ModuleSelfLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + args_range: convert_optional_range(node.args_location()), + }), + } + } + + fn convert_interface_declaration(&mut self, node: &InterfaceNode<'_>) -> InterfaceDeclaration { + InterfaceDeclaration { + name: self.convert_type_name(&node.name()), + type_params: self.convert_type_params(node.type_params()), + members: self.convert_members(node.members()), + annotations: self.convert_annotations(node.annotations()), + location: Some(InterfaceDeclarationLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + name_range: convert_range(node.name_location()), + end_range: convert_range(node.end_location()), + type_params_range: convert_optional_range(node.type_params_location()), + }), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_type_alias_declaration(&mut self, node: &TypeAliasNode<'_>) -> TypeAliasDeclaration { + TypeAliasDeclaration { + name: self.convert_type_name(&node.name()), + type_params: self.convert_type_params(node.type_params()), + ty: self.convert_type(&node.type_()), + annotations: self.convert_annotations(node.annotations()), + location: Some(TypeAliasDeclarationLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + name_range: convert_range(node.name_location()), + eq_range: convert_range(node.eq_location()), + type_params_range: convert_optional_range(node.type_params_location()), + }), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_constant_declaration(&mut self, node: &ConstantNode<'_>) -> ConstantDeclaration { + ConstantDeclaration { + name: self.convert_type_name(&node.name()), + ty: self.convert_type(&node.type_()), + location: Some(ConstantDeclarationLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + colon_range: convert_range(node.colon_location()), + }), + comment: self.convert_optional_comment(node.comment()), + annotations: self.convert_annotations(node.annotations()), + } + } + + fn convert_global_declaration(&mut self, node: &GlobalNode<'_>) -> GlobalDeclaration { + GlobalDeclaration { + name: self.intern_symbol(&node.name()), + ty: self.convert_type(&node.type_()), + location: Some(GlobalDeclarationLocation { + range: convert_range(node.location()), + name_range: convert_range(node.name_location()), + colon_range: convert_range(node.colon_location()), + }), + comment: self.convert_optional_comment(node.comment()), + annotations: self.convert_annotations(node.annotations()), + } + } + + fn convert_class_alias_declaration( + &mut self, + node: &ClassAliasNode<'_>, + ) -> ClassAliasDeclaration { + ClassAliasDeclaration { + new_name: self.convert_type_name(&node.new_name()), + old_name: self.convert_type_name(&node.old_name()), + location: Some(AliasDeclarationLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + new_name_range: convert_range(node.new_name_location()), + eq_range: convert_range(node.eq_location()), + old_name_range: convert_range(node.old_name_location()), + }), + comment: self.convert_optional_comment(node.comment()), + annotations: self.convert_annotations(node.annotations()), + } + } + + fn convert_module_alias_declaration( + &mut self, + node: &ModuleAliasNode<'_>, + ) -> ModuleAliasDeclaration { + ModuleAliasDeclaration { + new_name: self.convert_type_name(&node.new_name()), + old_name: self.convert_type_name(&node.old_name()), + location: Some(AliasDeclarationLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + new_name_range: convert_range(node.new_name_location()), + eq_range: convert_range(node.eq_location()), + old_name_range: convert_range(node.old_name_location()), + }), + comment: self.convert_optional_comment(node.comment()), + annotations: self.convert_annotations(node.annotations()), + } + } + + fn convert_method_definition_member( + &mut self, + node: &MethodDefinitionNode<'_>, + ) -> MethodDefinitionMember { + MethodDefinitionMember { + name: self.intern_symbol(&node.name()), + kind: convert_method_kind(node.kind()), + overloads: self.convert_method_definition_overloads(node.overloads()), + annotations: self.convert_annotations(node.annotations()), + location: Some(MethodDefinitionLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + name_range: convert_range(node.name_location()), + kind_range: convert_optional_range(node.kind_location()), + overloading_range: convert_optional_range(node.overloading_location()), + visibility_range: convert_optional_range(node.visibility_location()), + }), + comment: self.convert_optional_comment(node.comment()), + overloading: node.overloading(), + visibility: convert_method_visibility(node.visibility()), + } + } + + fn convert_method_definition_overload( + &mut self, + node: &MethodDefinitionOverloadNode<'_>, + ) -> MethodDefinitionOverload { + MethodDefinitionOverload { + method_type: self.convert_method_type_node(&node.method_type()), + annotations: self.convert_annotations(node.annotations()), + } + } + + fn convert_instance_variable_member( + &mut self, + node: &InstanceVariableNode<'_>, + ) -> InstanceVariableMember { + InstanceVariableMember { + name: self.intern_symbol(&node.name()), + ty: self.convert_type(&node.type_()), + location: Some(variable_member_location( + node.location(), + node.name_location(), + node.colon_location(), + node.kind_location(), + )), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_class_instance_variable_member( + &mut self, + node: &ClassInstanceVariableNode<'_>, + ) -> ClassInstanceVariableMember { + ClassInstanceVariableMember { + name: self.intern_symbol(&node.name()), + ty: self.convert_type(&node.type_()), + location: Some(variable_member_location( + node.location(), + node.name_location(), + node.colon_location(), + node.kind_location(), + )), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_class_variable_member( + &mut self, + node: &ClassVariableNode<'_>, + ) -> ClassVariableMember { + ClassVariableMember { + name: self.intern_symbol(&node.name()), + ty: self.convert_type(&node.type_()), + location: Some(variable_member_location( + node.location(), + node.name_location(), + node.colon_location(), + node.kind_location(), + )), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_include_member(&mut self, node: &IncludeNode<'_>) -> IncludeMember { + IncludeMember { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + annotations: self.convert_annotations(node.annotations()), + location: Some(mixin_member_location( + node.location(), + node.keyword_location(), + node.name_location(), + node.args_location(), + )), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_extend_member(&mut self, node: &ExtendNode<'_>) -> ExtendMember { + ExtendMember { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + annotations: self.convert_annotations(node.annotations()), + location: Some(mixin_member_location( + node.location(), + node.keyword_location(), + node.name_location(), + node.args_location(), + )), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_prepend_member(&mut self, node: &PrependNode<'_>) -> PrependMember { + PrependMember { + name: self.convert_type_name(&node.name()), + args: self.convert_type_list(node.args()), + annotations: self.convert_annotations(node.annotations()), + location: Some(mixin_member_location( + node.location(), + node.keyword_location(), + node.name_location(), + node.args_location(), + )), + comment: self.convert_optional_comment(node.comment()), + } + } + + fn convert_attr_reader_member(&mut self, node: &AttrReaderNode<'_>) -> AttrReaderMember { + AttrReaderMember { + name: self.intern_symbol(&node.name()), + ty: self.convert_type(&node.type_()), + ivar_name: self.convert_ivar_name(node.ivar_name(), node.ivar_name_string()), + kind: convert_attribute_kind(node.kind()), + annotations: self.convert_annotations(node.annotations()), + location: Some(attribute_member_location( + node.location(), + node.keyword_location(), + node.name_location(), + node.colon_location(), + node.kind_location(), + node.ivar_location(), + node.ivar_name_location(), + node.visibility_location(), + )), + comment: self.convert_optional_comment(node.comment()), + visibility: convert_attribute_visibility(node.visibility()), + } + } + + fn convert_attr_accessor_member(&mut self, node: &AttrAccessorNode<'_>) -> AttrAccessorMember { + AttrAccessorMember { + name: self.intern_symbol(&node.name()), + ty: self.convert_type(&node.type_()), + ivar_name: self.convert_ivar_name(node.ivar_name(), node.ivar_name_string()), + kind: convert_attribute_kind(node.kind()), + annotations: self.convert_annotations(node.annotations()), + location: Some(attribute_member_location( + node.location(), + node.keyword_location(), + node.name_location(), + node.colon_location(), + node.kind_location(), + node.ivar_location(), + node.ivar_name_location(), + node.visibility_location(), + )), + comment: self.convert_optional_comment(node.comment()), + visibility: convert_attribute_visibility(node.visibility()), + } + } + + fn convert_attr_writer_member(&mut self, node: &AttrWriterNode<'_>) -> AttrWriterMember { + AttrWriterMember { + name: self.intern_symbol(&node.name()), + ty: self.convert_type(&node.type_()), + ivar_name: self.convert_ivar_name(node.ivar_name(), node.ivar_name_string()), + kind: convert_attribute_kind(node.kind()), + annotations: self.convert_annotations(node.annotations()), + location: Some(attribute_member_location( + node.location(), + node.keyword_location(), + node.name_location(), + node.colon_location(), + node.kind_location(), + node.ivar_location(), + node.ivar_name_location(), + node.visibility_location(), + )), + comment: self.convert_optional_comment(node.comment()), + visibility: convert_attribute_visibility(node.visibility()), + } + } + + fn convert_public_member(&mut self, node: &PublicNode<'_>) -> PublicMember { + PublicMember { + location: Some(convert_range(node.location())), + } + } + + fn convert_private_member(&mut self, node: &PrivateNode<'_>) -> PrivateMember { + PrivateMember { + location: Some(convert_range(node.location())), + } + } + + fn convert_alias_member(&mut self, node: &AliasNode<'_>) -> AliasMember { + AliasMember { + new_name: self.intern_symbol(&node.new_name()), + old_name: self.intern_symbol(&node.old_name()), + kind: convert_alias_kind(node.kind()), + annotations: self.convert_annotations(node.annotations()), + location: Some(AliasMemberLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + new_name_range: convert_range(node.new_name_location()), + old_name_range: convert_range(node.old_name_location()), + new_kind_range: convert_optional_range(node.new_kind_location()), + old_kind_range: convert_optional_range(node.old_kind_location()), + }), + comment: self.convert_optional_comment(node.comment()), + } + } + fn convert_alias_type(&mut self, node: &AliasTypeNode<'_>) -> AliasType { AliasType { name: self.convert_type_name(&node.name()), @@ -291,6 +757,101 @@ impl<'a> AstConverter<'a> { .collect() } + fn convert_class_members(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter() + .map(|node| match node { + Node::Class(_) + | Node::Module(_) + | Node::Interface(_) + | Node::Constant(_) + | Node::Global(_) + | Node::TypeAlias(_) + | Node::ClassAlias(_) + | Node::ModuleAlias(_) => ClassMember::Declaration(self.convert_declaration(&node)), + Node::Alias(_) + | Node::AttrAccessor(_) + | Node::AttrReader(_) + | Node::AttrWriter(_) + | Node::ClassInstanceVariable(_) + | Node::ClassVariable(_) + | Node::Extend(_) + | Node::Include(_) + | Node::InstanceVariable(_) + | Node::MethodDefinition(_) + | Node::Prepend(_) + | Node::Private(_) + | Node::Public(_) => ClassMember::Member(self.convert_member(&node)), + _ => panic_expected("class member while converting class declaration", &node), + }) + .collect() + } + + fn convert_module_members(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter() + .map(|node| match node { + Node::Class(_) + | Node::Module(_) + | Node::Interface(_) + | Node::Constant(_) + | Node::Global(_) + | Node::TypeAlias(_) + | Node::ClassAlias(_) + | Node::ModuleAlias(_) => { + ModuleMember::Declaration(self.convert_declaration(&node)) + } + Node::Alias(_) + | Node::AttrAccessor(_) + | Node::AttrReader(_) + | Node::AttrWriter(_) + | Node::ClassInstanceVariable(_) + | Node::ClassVariable(_) + | Node::Extend(_) + | Node::Include(_) + | Node::InstanceVariable(_) + | Node::MethodDefinition(_) + | Node::Prepend(_) + | Node::Private(_) + | Node::Public(_) => ModuleMember::Member(self.convert_member(&node)), + _ => panic_expected("module member while converting module declaration", &node), + }) + .collect() + } + + fn convert_members(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter().map(|node| self.convert_member(&node)).collect() + } + + fn convert_module_selfs(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter() + .map(|node| { + let Node::ModuleSelf(node) = node else { + panic_expected( + "module self type while converting module declaration", + &node, + ); + }; + self.convert_module_self(&node) + }) + .collect() + } + + fn convert_method_definition_overloads( + &mut self, + list: crate::node::NodeList<'_>, + ) -> Vec { + list.iter() + .map(|node| { + let Node::MethodDefinitionOverload(node) = node else { + panic_expected( + "method definition overload while converting method definition", + &node, + ); + }; + self.convert_method_definition_overload(&node) + }) + .collect() + } + fn convert_keyword_params(&mut self, hash: crate::node::RBSHash<'_>) -> Vec { hash.iter() .map(|(key, value)| { @@ -332,6 +893,58 @@ impl<'a> AstConverter<'a> { } } + fn convert_method_type_node(&mut self, node: &Node<'_>) -> MethodType { + let Node::MethodType(node) = node else { + panic_expected( + "method type node while converting method definition overload", + node, + ); + }; + self.convert_method_type(node) + } + + fn convert_annotations(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter() + .map(|node| { + let Node::Annotation(node) = node else { + panic_expected("annotation node while converting annotations", &node); + }; + self.convert_annotation(&node) + }) + .collect() + } + + fn convert_annotation(&mut self, node: &AnnotationNode<'_>) -> Annotation { + Annotation { + string: self.strings.intern(node.string().as_str()), + location: Some(convert_range(node.location())), + } + } + + fn convert_optional_comment(&mut self, node: Option>) -> Option { + node.map(|node| self.convert_comment(&node)) + } + + fn convert_comment(&mut self, node: &CommentNode<'_>) -> Comment { + Comment { + string: self.strings.intern(node.string().as_str()), + location: Some(convert_range(node.location())), + } + } + + fn convert_ivar_name(&mut self, ivar_name: AttrIvarName, name: Option) -> IvarName { + match ivar_name { + AttrIvarName::Unspecified => IvarName::Unspecified, + AttrIvarName::Empty => IvarName::Empty, + AttrIvarName::Name(_) => { + let name = name.unwrap_or_else(|| { + panic!("invalid RBS AST while converting to owned AST: explicit ivar name is missing from the constant pool") + }); + IvarName::Name(self.strings.intern(&name)) + } + } + } + fn convert_type_name(&mut self, node: &TypeNameNode<'_>) -> TypeName { let namespace = node.namespace(); let mut name = self.type_names.root(namespace.absolute()); @@ -386,6 +999,57 @@ fn convert_optional_range(range: Option) -> Option, +) -> VariableMemberLocation { + VariableMemberLocation { + range: convert_range(range), + name_range: convert_range(name_range), + colon_range: convert_range(colon_range), + kind_range: convert_optional_range(kind_range), + } +} + +fn mixin_member_location( + range: RBSLocationRange, + keyword_range: RBSLocationRange, + name_range: RBSLocationRange, + args_range: Option, +) -> MixinMemberLocation { + MixinMemberLocation { + range: convert_range(range), + keyword_range: convert_range(keyword_range), + name_range: convert_range(name_range), + args_range: convert_optional_range(args_range), + } +} + +#[allow(clippy::too_many_arguments)] +fn attribute_member_location( + range: RBSLocationRange, + keyword_range: RBSLocationRange, + name_range: RBSLocationRange, + colon_range: RBSLocationRange, + kind_range: Option, + ivar_range: Option, + ivar_name_range: Option, + visibility_range: Option, +) -> AttributeMemberLocation { + AttributeMemberLocation { + range: convert_range(range), + keyword_range: convert_range(keyword_range), + name_range: convert_range(name_range), + colon_range: convert_range(colon_range), + kind_range: convert_optional_range(kind_range), + ivar_range: convert_optional_range(ivar_range), + ivar_name_range: convert_optional_range(ivar_name_range), + visibility_range: convert_optional_range(visibility_range), + } +} + fn convert_variance(variance: TypeParamVariance) -> Variance { match variance { TypeParamVariance::Invariant => Variance::Invariant, @@ -394,6 +1058,44 @@ fn convert_variance(variance: TypeParamVariance) -> Variance { } } +fn convert_method_kind(kind: NodeMethodDefinitionKind) -> MethodKind { + match kind { + NodeMethodDefinitionKind::Instance => MethodKind::Instance, + NodeMethodDefinitionKind::Singleton => MethodKind::Singleton, + NodeMethodDefinitionKind::SingletonInstance => MethodKind::SingletonInstance, + } +} + +fn convert_method_visibility(visibility: NodeMethodDefinitionVisibility) -> Option { + match visibility { + NodeMethodDefinitionVisibility::Unspecified => None, + NodeMethodDefinitionVisibility::Public => Some(Visibility::Public), + NodeMethodDefinitionVisibility::Private => Some(Visibility::Private), + } +} + +fn convert_attribute_kind(kind: NodeAttributeKind) -> AttributeKind { + match kind { + NodeAttributeKind::Instance => AttributeKind::Instance, + NodeAttributeKind::Singleton => AttributeKind::Singleton, + } +} + +fn convert_attribute_visibility(visibility: NodeAttributeVisibility) -> Option { + match visibility { + NodeAttributeVisibility::Unspecified => None, + NodeAttributeVisibility::Public => Some(Visibility::Public), + NodeAttributeVisibility::Private => Some(Visibility::Private), + } +} + +fn convert_alias_kind(kind: NodeAliasKind) -> AliasKind { + match kind { + NodeAliasKind::Instance => AliasKind::Instance, + NodeAliasKind::Singleton => AliasKind::Singleton, + } +} + fn panic_expected(expected: &str, actual: &Node<'_>) -> ! { panic!( "invalid RBS AST while converting to owned AST: expected {expected}, got {}", diff --git a/rust/ruby-rbs/src/ast/declarations.rs b/rust/ruby-rbs/src/ast/declarations.rs new file mode 100644 index 000000000..5e392bc32 --- /dev/null +++ b/rust/ruby-rbs/src/ast/declarations.rs @@ -0,0 +1,127 @@ +use crate::ast::annotation::Annotation; +use crate::ast::comment::Comment; +use crate::ast::location::{ + AliasDeclarationLocation, ClassDeclarationLocation, ClassSuperLocation, + ConstantDeclarationLocation, GlobalDeclarationLocation, InterfaceDeclarationLocation, + ModuleDeclarationLocation, ModuleSelfLocation, TypeAliasDeclarationLocation, +}; +use crate::ast::members::Member; +use crate::ast::type_param::TypeParam; +use crate::ast::types::Type; +use crate::ids::{SymbolId, TypeName}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum Declaration { + Class(ClassDeclaration), + Module(ModuleDeclaration), + Interface(InterfaceDeclaration), + Constant(ConstantDeclaration), + Global(GlobalDeclaration), + TypeAlias(TypeAliasDeclaration), + ClassAlias(ClassAliasDeclaration), + ModuleAlias(ModuleAliasDeclaration), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum ClassMember { + Member(Member), + Declaration(Declaration), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum ModuleMember { + Member(Member), + Declaration(Declaration), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassSuper { + pub name: TypeName, + pub args: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassDeclaration { + pub name: TypeName, + pub type_params: Vec, + pub members: Vec, + pub super_class: Option, + pub annotations: Vec, + pub location: Option, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ModuleSelf { + pub name: TypeName, + pub args: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ModuleDeclaration { + pub name: TypeName, + pub type_params: Vec, + pub members: Vec, + pub location: Option, + pub annotations: Vec, + pub self_types: Vec, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct InterfaceDeclaration { + pub name: TypeName, + pub type_params: Vec, + pub members: Vec, + pub annotations: Vec, + pub location: Option, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct TypeAliasDeclaration { + pub name: TypeName, + pub type_params: Vec, + pub ty: Type, + pub annotations: Vec, + pub location: Option, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ConstantDeclaration { + pub name: TypeName, + pub ty: Type, + pub location: Option, + pub comment: Option, + pub annotations: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct GlobalDeclaration { + pub name: SymbolId, + pub ty: Type, + pub location: Option, + pub comment: Option, + pub annotations: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassAliasDeclaration { + pub new_name: TypeName, + pub old_name: TypeName, + pub location: Option, + pub comment: Option, + pub annotations: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ModuleAliasDeclaration { + pub new_name: TypeName, + pub old_name: TypeName, + pub location: Option, + pub comment: Option, + pub annotations: Vec, +} diff --git a/rust/ruby-rbs/src/ast/location.rs b/rust/ruby-rbs/src/ast/location.rs index dda1b86b5..917048c97 100644 --- a/rust/ruby-rbs/src/ast/location.rs +++ b/rust/ruby-rbs/src/ast/location.rs @@ -122,3 +122,266 @@ pub struct TypeParamLocation { pub lower_bound_range: Option, pub default_range: Option, } + +/// ```rbs +/// String +/// ^^^^^^ name +/// +/// Array[String] +/// ^^^^^ name +/// ^^^^^^^^ args +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassSuperLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub args_range: Option, +} + +/// ```rbs +/// class Foo end +/// ^^^^^ keyword +/// ^^^ name +/// ^^^ end +/// +/// class Foo[A] < String end +/// ^^^^^ keyword +/// ^^^ name +/// ^^^ type_params +/// ^ lt +/// ^^^ end +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassDeclarationLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub name_range: LocationRange, + pub end_range: LocationRange, + pub type_params_range: Option, + pub lt_range: Option, +} + +/// ```rbs +/// _Each[String] +/// ^^^^^ name +/// ^^^^^^^^ args +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ModuleSelfLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub args_range: Option, +} + +/// ```rbs +/// module Foo end +/// ^^^^^^ keyword +/// ^^^ name +/// ^^^ end +/// +/// module Foo[A] : BasicObject end +/// ^^^^^^ keyword +/// ^^^ name +/// ^^^ type_params +/// ^ colon +/// ^^^^^^^^^^^ self_types +/// ^^^ end +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ModuleDeclarationLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub name_range: LocationRange, + pub end_range: LocationRange, + pub type_params_range: Option, + pub colon_range: Option, + pub self_types_range: Option, +} + +/// ```rbs +/// interface _Foo end +/// ^^^^^^^^^ keyword +/// ^^^^ name +/// ^^^ end +/// +/// interface _Bar[A, B] end +/// ^^^^^^^^^ keyword +/// ^^^^ name +/// ^^^^^^ type_params +/// ^^^ end +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct InterfaceDeclarationLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub name_range: LocationRange, + pub end_range: LocationRange, + pub type_params_range: Option, +} + +/// ```rbs +/// type loc[T] = Location[T, bot] +/// ^^^^ keyword +/// ^^^ name +/// ^^^ type_params +/// ^ eq +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct TypeAliasDeclarationLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub name_range: LocationRange, + pub eq_range: LocationRange, + pub type_params_range: Option, +} + +/// ```rbs +/// VERSION: String +/// ^^^^^^^ name +/// ^ colon +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ConstantDeclarationLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub colon_range: LocationRange, +} + +/// ```rbs +/// $SIZE: String +/// ^^^^^ name +/// ^ colon +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct GlobalDeclarationLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub colon_range: LocationRange, +} + +/// ```rbs +/// module Foo = Bar +/// ^^^^^^ keyword +/// ^^^ new_name +/// ^ eq +/// ^^^ old_name +/// +/// class Foo = Bar +/// ^^^^^ keyword +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AliasDeclarationLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub new_name_range: LocationRange, + pub eq_range: LocationRange, + pub old_name_range: LocationRange, +} + +/// ```rbs +/// def foo: () -> void +/// ^^^ keyword +/// ^^^ name +/// +/// private def self.bar: () -> void | ... +/// ^^^^^^^ visibility +/// ^^^ keyword +/// ^^^^^ kind +/// ^^^ name +/// ^^^ overloading +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct MethodDefinitionLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub name_range: LocationRange, + pub kind_range: Option, + pub overloading_range: Option, + pub visibility_range: Option, +} + +/// ```rbs +/// @foo: String +/// ^^^^ name +/// ^ colon +/// +/// self.@all: Array[String] +/// ^^^^^ kind +/// ^^^^ name +/// ^ colon +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct VariableMemberLocation { + pub range: LocationRange, + pub name_range: LocationRange, + pub colon_range: LocationRange, + pub kind_range: Option, +} + +/// ```rbs +/// include Foo +/// ^^^^^^^ keyword +/// ^^^ name +/// +/// include Array[String] +/// ^^^^^^^ keyword +/// ^^^^^ name +/// ^^^^^^^^ args +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct MixinMemberLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub name_range: LocationRange, + pub args_range: Option, +} + +/// ```rbs +/// attr_reader name: String +/// ^^^^^^^^^^^ keyword +/// ^^^^ name +/// ^ colon +/// +/// public attr_accessor self.name (@foo) : String +/// ^^^^^^ visibility +/// ^^^^^^^^^^^^^ keyword +/// ^^^^^ kind +/// ^^^^ name +/// ^^^^^^ ivar +/// ^^^^ ivar_name +/// ^ colon +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AttributeMemberLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub name_range: LocationRange, + pub colon_range: LocationRange, + pub kind_range: Option, + pub ivar_range: Option, + pub ivar_name_range: Option, + pub visibility_range: Option, +} + +/// ```rbs +/// alias foo bar +/// ^^^^^ keyword +/// ^^^ new_name +/// ^^^ old_name +/// +/// alias self.foo self.bar +/// ^^^^^ keyword +/// ^^^^^ new_kind +/// ^^^ new_name +/// ^^^^^ old_kind +/// ^^^ old_name +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AliasMemberLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub new_name_range: LocationRange, + pub old_name_range: LocationRange, + pub new_kind_range: Option, + pub old_kind_range: Option, +} diff --git a/rust/ruby-rbs/src/ast/members.rs b/rust/ruby-rbs/src/ast/members.rs new file mode 100644 index 000000000..bbe4e3111 --- /dev/null +++ b/rust/ruby-rbs/src/ast/members.rs @@ -0,0 +1,183 @@ +use crate::ast::annotation::Annotation; +use crate::ast::comment::Comment; +use crate::ast::location::{ + AliasMemberLocation, AttributeMemberLocation, MethodDefinitionLocation, MixinMemberLocation, + VariableMemberLocation, +}; +use crate::ast::method_type::MethodType; +use crate::ast::types::Type; +use crate::ids::{SymbolId, TypeName}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum Member { + MethodDefinition(MethodDefinitionMember), + InstanceVariable(InstanceVariableMember), + ClassInstanceVariable(ClassInstanceVariableMember), + ClassVariable(ClassVariableMember), + Include(IncludeMember), + Extend(ExtendMember), + Prepend(PrependMember), + AttrReader(AttrReaderMember), + AttrWriter(AttrWriterMember), + AttrAccessor(AttrAccessorMember), + Public(PublicMember), + Private(PrivateMember), + Alias(AliasMember), +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Visibility { + Public, + Private, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum MethodKind { + Instance, + Singleton, + SingletonInstance, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct MethodDefinitionMember { + pub name: SymbolId, + pub kind: MethodKind, + pub overloads: Vec, + pub annotations: Vec, + pub location: Option, + pub comment: Option, + pub overloading: bool, + pub visibility: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct MethodDefinitionOverload { + pub method_type: MethodType, + pub annotations: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct InstanceVariableMember { + pub name: SymbolId, + pub ty: Type, + pub location: Option, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassInstanceVariableMember { + pub name: SymbolId, + pub ty: Type, + pub location: Option, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ClassVariableMember { + pub name: SymbolId, + pub ty: Type, + pub location: Option, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct IncludeMember { + pub name: TypeName, + pub args: Vec, + pub annotations: Vec, + pub location: Option, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ExtendMember { + pub name: TypeName, + pub args: Vec, + pub annotations: Vec, + pub location: Option, + pub comment: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct PrependMember { + pub name: TypeName, + pub args: Vec, + pub annotations: Vec, + pub location: Option, + pub comment: Option, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum AttributeKind { + Instance, + Singleton, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum IvarName { + Unspecified, + Empty, + Name(SymbolId), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AttrReaderMember { + pub name: SymbolId, + pub ty: Type, + pub ivar_name: IvarName, + pub kind: AttributeKind, + pub annotations: Vec, + pub location: Option, + pub comment: Option, + pub visibility: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AttrAccessorMember { + pub name: SymbolId, + pub ty: Type, + pub ivar_name: IvarName, + pub kind: AttributeKind, + pub annotations: Vec, + pub location: Option, + pub comment: Option, + pub visibility: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AttrWriterMember { + pub name: SymbolId, + pub ty: Type, + pub ivar_name: IvarName, + pub kind: AttributeKind, + pub annotations: Vec, + pub location: Option, + pub comment: Option, + pub visibility: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct PublicMember { + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct PrivateMember { + pub location: Option, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum AliasKind { + Instance, + Singleton, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AliasMember { + pub new_name: SymbolId, + pub old_name: SymbolId, + pub kind: AliasKind, + pub annotations: Vec, + pub location: Option, + pub comment: Option, +} diff --git a/rust/ruby-rbs/src/ast/mod.rs b/rust/ruby-rbs/src/ast/mod.rs index f6a97e2df..c65358521 100644 --- a/rust/ruby-rbs/src/ast/mod.rs +++ b/rust/ruby-rbs/src/ast/mod.rs @@ -10,7 +10,9 @@ pub mod annotation; pub mod comment; pub mod convert; +pub mod declarations; pub mod location; +pub mod members; pub mod method_type; pub mod type_param; pub mod types; @@ -18,9 +20,24 @@ pub mod types; pub use annotation::Annotation; pub use comment::Comment; pub use convert::AstConverter; +pub use declarations::{ + ClassAliasDeclaration, ClassDeclaration, ClassMember, ClassSuper, ConstantDeclaration, + Declaration, GlobalDeclaration, InterfaceDeclaration, ModuleAliasDeclaration, + ModuleDeclaration, ModuleMember, ModuleSelf, TypeAliasDeclaration, +}; pub use location::{ - AliasLocation, ClassInstanceLocation, ClassSingletonLocation, FunctionParamLocation, - InterfaceLocation, LocationRange, MethodTypeLocation, TypeParamLocation, + AliasDeclarationLocation, AliasLocation, AliasMemberLocation, AttributeMemberLocation, + ClassDeclarationLocation, ClassInstanceLocation, ClassSingletonLocation, ClassSuperLocation, + ConstantDeclarationLocation, FunctionParamLocation, GlobalDeclarationLocation, + InterfaceDeclarationLocation, InterfaceLocation, LocationRange, MethodDefinitionLocation, + MethodTypeLocation, MixinMemberLocation, ModuleDeclarationLocation, ModuleSelfLocation, + TypeAliasDeclarationLocation, TypeParamLocation, VariableMemberLocation, +}; +pub use members::{ + AliasKind, AliasMember, AttrAccessorMember, AttrReaderMember, AttrWriterMember, AttributeKind, + ClassInstanceVariableMember, ClassVariableMember, ExtendMember, IncludeMember, + InstanceVariableMember, IvarName, Member, MethodDefinitionMember, MethodDefinitionOverload, + MethodKind, PrependMember, PrivateMember, PublicMember, Visibility, }; pub use method_type::MethodType; pub use type_param::{TypeParam, Variance}; @@ -33,7 +50,10 @@ pub use types::{ #[cfg(test)] mod tests { - use crate::ast::{AstConverter, BaseType, BaseTypeKind, Literal, RecordKey, Type}; + use crate::ast::{ + AstConverter, BaseType, BaseTypeKind, ClassMember, Declaration, IvarName, Literal, Member, + MethodKind, ModuleMember, RecordKey, Type, + }; use crate::interner::StringInterner; use crate::node::{Node, parse}; use crate::type_name::TypeNameInterner; @@ -102,4 +122,149 @@ mod tests { assert_eq!(type_names.display(array.name, &strings), "Array"); assert_eq!(array.args.len(), 1); } + + #[test] + fn converts_declarations_and_members_to_owned_ast() { + let signature = parse( + r#" + class Foo[T] < Bar[String] + public + include Enumerable[String] + attr_reader name: String + attr_writer email(@email): String + def self.process: (String) -> void + @ivar: Integer + class Nested + end + end + + module M : _Each[String] + extend Kernel + end + + interface _I + def foo: () -> void + end + + type pair[T] = [T, T] + VERSION: String + $global: Integer + class Old = New + module OldM = NewM + "#, + ) + .unwrap(); + + let mut strings = StringInterner::new(); + let mut type_names = TypeNameInterner::new(); + let mut converter = AstConverter::new(&mut strings, &mut type_names); + let declarations = signature + .declarations() + .iter() + .map(|node| converter.convert_declaration(&node)) + .collect::>(); + + assert_eq!(declarations.len(), 8); + + let Declaration::Class(class_decl) = &declarations[0] else { + panic!("expected class declaration"); + }; + assert_eq!(type_names.display(class_decl.name, &strings), "Foo"); + assert_eq!(class_decl.type_params.len(), 1); + assert_eq!( + type_names.display(class_decl.super_class.as_ref().unwrap().name, &strings), + "Bar" + ); + assert_eq!(class_decl.members.len(), 7); + + let ClassMember::Member(Member::Public(public_member)) = &class_decl.members[0] else { + panic!("expected public member"); + }; + assert!(public_member.location.is_some()); + + let ClassMember::Member(Member::Include(include_member)) = &class_decl.members[1] else { + panic!("expected include member"); + }; + assert_eq!( + type_names.display(include_member.name, &strings), + "Enumerable" + ); + assert_eq!(include_member.args.len(), 1); + + let ClassMember::Member(Member::AttrReader(attr_reader)) = &class_decl.members[2] else { + panic!("expected attr_reader member"); + }; + assert_eq!(attr_reader.name, strings.intern("name")); + assert_eq!(attr_reader.visibility, None); + + let ClassMember::Member(Member::AttrWriter(attr_writer)) = &class_decl.members[3] else { + panic!("expected attr_writer member"); + }; + assert_eq!( + attr_writer.ivar_name, + IvarName::Name(strings.intern("@email")) + ); + + let ClassMember::Member(Member::MethodDefinition(method)) = &class_decl.members[4] else { + panic!("expected method definition member"); + }; + assert_eq!(method.name, strings.intern("process")); + assert_eq!(method.kind, MethodKind::Singleton); + assert_eq!(method.visibility, None); + assert_eq!(method.overloads.len(), 1); + + let ClassMember::Member(Member::InstanceVariable(ivar)) = &class_decl.members[5] else { + panic!("expected instance variable member"); + }; + assert_eq!(ivar.name, strings.intern("@ivar")); + + let ClassMember::Declaration(Declaration::Class(nested)) = &class_decl.members[6] else { + panic!("expected nested class declaration"); + }; + assert_eq!(type_names.display(nested.name, &strings), "Nested"); + + let Declaration::Module(module_decl) = &declarations[1] else { + panic!("expected module declaration"); + }; + assert_eq!(type_names.display(module_decl.name, &strings), "M"); + assert_eq!(module_decl.self_types.len(), 1); + let ModuleMember::Member(Member::Extend(extend_member)) = &module_decl.members[0] else { + panic!("expected extend member"); + }; + assert_eq!(type_names.display(extend_member.name, &strings), "Kernel"); + + let Declaration::Interface(interface_decl) = &declarations[2] else { + panic!("expected interface declaration"); + }; + assert_eq!(type_names.display(interface_decl.name, &strings), "_I"); + let Member::MethodDefinition(interface_method) = &interface_decl.members[0] else { + panic!("expected interface method definition"); + }; + assert_eq!(interface_method.kind, MethodKind::Instance); + + let Declaration::TypeAlias(type_alias) = &declarations[3] else { + panic!("expected type alias declaration"); + }; + assert_eq!(type_names.display(type_alias.name, &strings), "pair"); + + let Declaration::Constant(constant) = &declarations[4] else { + panic!("expected constant declaration"); + }; + assert_eq!(type_names.display(constant.name, &strings), "VERSION"); + + let Declaration::Global(global) = &declarations[5] else { + panic!("expected global declaration"); + }; + assert_eq!(global.name, strings.intern("$global")); + + let Declaration::ClassAlias(class_alias) = &declarations[6] else { + panic!("expected class alias declaration"); + }; + assert_eq!(type_names.display(class_alias.new_name, &strings), "Old"); + + let Declaration::ModuleAlias(module_alias) = &declarations[7] else { + panic!("expected module alias declaration"); + }; + assert_eq!(type_names.display(module_alias.old_name, &strings), "NewM"); + } } diff --git a/rust/ruby-rbs/src/node/mod.rs b/rust/ruby-rbs/src/node/mod.rs index e53aae7a0..d007dfc1c 100644 --- a/rust/ruby-rbs/src/node/mod.rs +++ b/rust/ruby-rbs/src/node/mod.rs @@ -322,6 +322,56 @@ impl std::fmt::Display for SymbolNode<'_> { } } +fn constant_id_to_string(parser: NonNull, constant_id: rbs_constant_id_t) -> String { + unsafe { + let constant_ptr = + rbs_constant_pool_id_to_constant(&(*parser.as_ptr()).constant_pool, constant_id); + if constant_ptr.is_null() { + panic!("Constant ID is not present in the pool"); + } + + let constant = &*constant_ptr; + let bytes = std::slice::from_raw_parts(constant.start, constant.length); + std::str::from_utf8_unchecked(bytes).to_owned() + } +} + +impl AttrAccessorNode<'_> { + #[must_use] + pub fn ivar_name_string(&self) -> Option { + match self.ivar_name() { + AttrIvarName::Name(constant_id) => { + Some(constant_id_to_string(self.parser, constant_id)) + } + AttrIvarName::Unspecified | AttrIvarName::Empty => None, + } + } +} + +impl AttrReaderNode<'_> { + #[must_use] + pub fn ivar_name_string(&self) -> Option { + match self.ivar_name() { + AttrIvarName::Name(constant_id) => { + Some(constant_id_to_string(self.parser, constant_id)) + } + AttrIvarName::Unspecified | AttrIvarName::Empty => None, + } + } +} + +impl AttrWriterNode<'_> { + #[must_use] + pub fn ivar_name_string(&self) -> Option { + match self.ivar_name() { + AttrIvarName::Name(constant_id) => { + Some(constant_id_to_string(self.parser, constant_id)) + } + AttrIvarName::Unspecified | AttrIvarName::Empty => None, + } + } +} + #[cfg(test)] mod tests { use super::*; From 17ea0ed520e8595ba6a7df6868f6f507476d8dd0 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Wed, 3 Jun 2026 14:47:16 +0900 Subject: [PATCH 3/5] Add owned AST directives --- rust/ruby-rbs/src/ast/convert.rs | 87 ++++++++++++++++++++++++++--- rust/ruby-rbs/src/ast/directives.rs | 42 ++++++++++++++ rust/ruby-rbs/src/ast/location.rs | 52 +++++++++++++++++ rust/ruby-rbs/src/ast/mod.rs | 63 ++++++++++++++++++++- 4 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 rust/ruby-rbs/src/ast/directives.rs diff --git a/rust/ruby-rbs/src/ast/convert.rs b/rust/ruby-rbs/src/ast/convert.rs index 5fe56e8ae..cc7a0666d 100644 --- a/rust/ruby-rbs/src/ast/convert.rs +++ b/rust/ruby-rbs/src/ast/convert.rs @@ -5,13 +5,17 @@ use crate::ast::declarations::{ Declaration, GlobalDeclaration, InterfaceDeclaration, ModuleAliasDeclaration, ModuleDeclaration, ModuleMember, ModuleSelf, TypeAliasDeclaration, }; +use crate::ast::directives::{ + Directive, UseClause, UseDirective, UseSingleClause, UseWildcardClause, +}; use crate::ast::location::{ AliasDeclarationLocation, AliasLocation, AliasMemberLocation, AttributeMemberLocation, ClassDeclarationLocation, ClassInstanceLocation, ClassSingletonLocation, ClassSuperLocation, ConstantDeclarationLocation, FunctionParamLocation, GlobalDeclarationLocation, InterfaceDeclarationLocation, InterfaceLocation, LocationRange, MethodDefinitionLocation, MethodTypeLocation, MixinMemberLocation, ModuleDeclarationLocation, ModuleSelfLocation, - TypeAliasDeclarationLocation, TypeParamLocation, VariableMemberLocation, + TypeAliasDeclarationLocation, TypeParamLocation, UseDirectiveLocation, UseSingleClauseLocation, + UseWildcardClauseLocation, VariableMemberLocation, }; use crate::ast::members::{ AliasKind, AliasMember, AttrAccessorMember, AttrReaderMember, AttrWriterMember, AttributeKind, @@ -38,9 +42,10 @@ use crate::node::{ FunctionTypeNode, GlobalNode, IncludeNode, InstanceVariableNode, InterfaceNode, InterfaceTypeNode, MethodDefinitionKind as NodeMethodDefinitionKind, MethodDefinitionNode, MethodDefinitionOverloadNode, MethodDefinitionVisibility as NodeMethodDefinitionVisibility, - MethodTypeNode, ModuleAliasNode, ModuleNode, ModuleSelfNode, Node, PrependNode, PrivateNode, - PublicNode, RBSLocationRange, SymbolNode, TypeAliasNode, TypeNameNode, TypeParamNode, - TypeParamVariance, UntypedFunctionTypeNode, + MethodTypeNode, ModuleAliasNode, ModuleNode, ModuleSelfNode, NamespaceNode, Node, PrependNode, + PrivateNode, PublicNode, RBSLocationRange, SymbolNode, TypeAliasNode, TypeNameNode, + TypeParamNode, TypeParamVariance, UntypedFunctionTypeNode, UseNode, UseSingleClauseNode, + UseWildcardClauseNode, }; use crate::type_name::TypeNameInterner; @@ -108,6 +113,13 @@ impl<'a> AstConverter<'a> { } } + pub fn convert_directive(&mut self, node: &Node<'_>) -> Directive { + match node { + Node::Use(node) => Directive::Use(self.convert_use_directive(node)), + _ => panic_expected("directive node while converting directive", node), + } + } + pub fn convert_type(&mut self, node: &Node<'_>) -> Type { match node { Node::AliasType(node) => Type::Alias(self.convert_alias_type(node)), @@ -245,6 +257,53 @@ impl<'a> AstConverter<'a> { } } + fn convert_use_directive(&mut self, node: &UseNode<'_>) -> UseDirective { + UseDirective { + clauses: self.convert_use_clauses(node.clauses()), + location: Some(UseDirectiveLocation { + range: convert_range(node.location()), + keyword_range: convert_range(node.keyword_location()), + }), + } + } + + fn convert_use_clause(&mut self, node: &Node<'_>) -> UseClause { + match node { + Node::UseSingleClause(node) => UseClause::Single(self.convert_use_single_clause(node)), + Node::UseWildcardClause(node) => { + UseClause::Wildcard(self.convert_use_wildcard_clause(node)) + } + _ => panic_expected("use clause node while converting use directive", node), + } + } + + fn convert_use_single_clause(&mut self, node: &UseSingleClauseNode<'_>) -> UseSingleClause { + UseSingleClause { + type_name: self.convert_type_name(&node.type_name()), + new_name: node.new_name().map(|name| self.intern_symbol(&name)), + location: Some(UseSingleClauseLocation { + range: convert_range(node.location()), + type_name_range: convert_range(node.type_name_location()), + keyword_range: convert_optional_range(node.keyword_location()), + new_name_range: convert_optional_range(node.new_name_location()), + }), + } + } + + fn convert_use_wildcard_clause( + &mut self, + node: &UseWildcardClauseNode<'_>, + ) -> UseWildcardClause { + UseWildcardClause { + namespace: self.convert_namespace(&node.namespace()), + location: Some(UseWildcardClauseLocation { + range: convert_range(node.location()), + namespace_range: convert_range(node.namespace_location()), + star_range: convert_range(node.star_location()), + }), + } + } + fn convert_module_declaration(&mut self, node: &ModuleNode<'_>) -> ModuleDeclaration { ModuleDeclaration { name: self.convert_type_name(&node.name()), @@ -757,6 +816,12 @@ impl<'a> AstConverter<'a> { .collect() } + fn convert_use_clauses(&mut self, list: crate::node::NodeList<'_>) -> Vec { + list.iter() + .map(|node| self.convert_use_clause(&node)) + .collect() + } + fn convert_class_members(&mut self, list: crate::node::NodeList<'_>) -> Vec { list.iter() .map(|node| match node { @@ -946,9 +1011,14 @@ impl<'a> AstConverter<'a> { } fn convert_type_name(&mut self, node: &TypeNameNode<'_>) -> TypeName { - let namespace = node.namespace(); - let mut name = self.type_names.root(namespace.absolute()); - for segment_node in namespace.path().iter() { + let name = self.convert_namespace(&node.namespace()); + let final_segment = self.intern_symbol(&node.name()); + self.type_names.append(name, final_segment) + } + + fn convert_namespace(&mut self, node: &NamespaceNode<'_>) -> TypeName { + let mut name = self.type_names.root(node.absolute()); + for segment_node in node.path().iter() { match segment_node { Node::Symbol(segment) => { let segment = self.intern_symbol(&segment); @@ -960,8 +1030,7 @@ impl<'a> AstConverter<'a> { ), } } - let final_segment = self.intern_symbol(&node.name()); - self.type_names.append(name, final_segment) + name } fn intern_symbol(&mut self, node: &SymbolNode<'_>) -> SymbolId { diff --git a/rust/ruby-rbs/src/ast/directives.rs b/rust/ruby-rbs/src/ast/directives.rs new file mode 100644 index 000000000..b21dd7ab6 --- /dev/null +++ b/rust/ruby-rbs/src/ast/directives.rs @@ -0,0 +1,42 @@ +use crate::ast::location::{ + ResolveTypeNamesDirectiveLocation, UseDirectiveLocation, UseSingleClauseLocation, + UseWildcardClauseLocation, +}; +use crate::ids::{SymbolId, TypeName}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum Directive { + Use(UseDirective), + ResolveTypeNames(ResolveTypeNamesDirective), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct UseDirective { + pub clauses: Vec, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum UseClause { + Single(UseSingleClause), + Wildcard(UseWildcardClause), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct UseSingleClause { + pub type_name: TypeName, + pub new_name: Option, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct UseWildcardClause { + pub namespace: TypeName, + pub location: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ResolveTypeNamesDirective { + pub value: bool, + pub location: Option, +} diff --git a/rust/ruby-rbs/src/ast/location.rs b/rust/ruby-rbs/src/ast/location.rs index 917048c97..9b9cecc72 100644 --- a/rust/ruby-rbs/src/ast/location.rs +++ b/rust/ruby-rbs/src/ast/location.rs @@ -385,3 +385,55 @@ pub struct AliasMemberLocation { pub new_kind_range: Option, pub old_kind_range: Option, } + +/// ```rbs +/// use Foo +/// ^^^ keyword +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct UseDirectiveLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, +} + +/// ```rbs +/// Foo::Bar +/// ^^^^^^^^ type_name +/// +/// Foo::Bar as X +/// ^^ keyword +/// ^ new_name +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct UseSingleClauseLocation { + pub range: LocationRange, + pub type_name_range: LocationRange, + pub keyword_range: Option, + pub new_name_range: Option, +} + +/// ```rbs +/// Foo::Bar::* +/// ^^^^^^^^^^ namespace +/// ^ star +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct UseWildcardClauseLocation { + pub range: LocationRange, + pub namespace_range: LocationRange, + pub star_range: LocationRange, +} + +/// ```rbs +/// # resolve-type-names: false +/// ^^^^^^^^^^^^^^^^^^ keyword +/// ^ colon +/// ^^^^^ value +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct ResolveTypeNamesDirectiveLocation { + pub range: LocationRange, + pub keyword_range: LocationRange, + pub colon_range: LocationRange, + pub value_range: LocationRange, +} diff --git a/rust/ruby-rbs/src/ast/mod.rs b/rust/ruby-rbs/src/ast/mod.rs index c65358521..99e341184 100644 --- a/rust/ruby-rbs/src/ast/mod.rs +++ b/rust/ruby-rbs/src/ast/mod.rs @@ -11,6 +11,7 @@ pub mod annotation; pub mod comment; pub mod convert; pub mod declarations; +pub mod directives; pub mod location; pub mod members; pub mod method_type; @@ -25,13 +26,19 @@ pub use declarations::{ Declaration, GlobalDeclaration, InterfaceDeclaration, ModuleAliasDeclaration, ModuleDeclaration, ModuleMember, ModuleSelf, TypeAliasDeclaration, }; +pub use directives::{ + Directive, ResolveTypeNamesDirective, UseClause, UseDirective, UseSingleClause, + UseWildcardClause, +}; pub use location::{ AliasDeclarationLocation, AliasLocation, AliasMemberLocation, AttributeMemberLocation, ClassDeclarationLocation, ClassInstanceLocation, ClassSingletonLocation, ClassSuperLocation, ConstantDeclarationLocation, FunctionParamLocation, GlobalDeclarationLocation, InterfaceDeclarationLocation, InterfaceLocation, LocationRange, MethodDefinitionLocation, MethodTypeLocation, MixinMemberLocation, ModuleDeclarationLocation, ModuleSelfLocation, - TypeAliasDeclarationLocation, TypeParamLocation, VariableMemberLocation, + ResolveTypeNamesDirectiveLocation, TypeAliasDeclarationLocation, TypeParamLocation, + UseDirectiveLocation, UseSingleClauseLocation, UseWildcardClauseLocation, + VariableMemberLocation, }; pub use members::{ AliasKind, AliasMember, AttrAccessorMember, AttrReaderMember, AttrWriterMember, AttributeKind, @@ -51,8 +58,8 @@ pub use types::{ #[cfg(test)] mod tests { use crate::ast::{ - AstConverter, BaseType, BaseTypeKind, ClassMember, Declaration, IvarName, Literal, Member, - MethodKind, ModuleMember, RecordKey, Type, + AstConverter, BaseType, BaseTypeKind, ClassMember, Declaration, Directive, IvarName, + Literal, Member, MethodKind, ModuleMember, RecordKey, Type, UseClause, }; use crate::interner::StringInterner; use crate::node::{Node, parse}; @@ -267,4 +274,54 @@ mod tests { }; assert_eq!(type_names.display(module_alias.old_name, &strings), "NewM"); } + + #[test] + fn converts_directives_to_owned_ast() { + let signature = parse( + r#" + use Foo, Foo::Bar as FBar, Foo::Baz::* + + class Foo + end + "#, + ) + .unwrap(); + + let mut strings = StringInterner::new(); + let mut type_names = TypeNameInterner::new(); + let mut converter = AstConverter::new(&mut strings, &mut type_names); + let directives = signature + .directives() + .iter() + .map(|node| converter.convert_directive(&node)) + .collect::>(); + + assert_eq!(directives.len(), 1); + + let Directive::Use(use_directive) = &directives[0] else { + panic!("expected use directive"); + }; + assert_eq!(use_directive.clauses.len(), 3); + assert!(use_directive.location.is_some()); + + let UseClause::Single(single) = &use_directive.clauses[0] else { + panic!("expected single use clause"); + }; + assert_eq!(type_names.display(single.type_name, &strings), "Foo"); + assert_eq!(single.new_name, None); + + let UseClause::Single(aliased) = &use_directive.clauses[1] else { + panic!("expected aliased single use clause"); + }; + assert_eq!(type_names.display(aliased.type_name, &strings), "Foo::Bar"); + assert_eq!(aliased.new_name, Some(strings.intern("FBar"))); + assert!(aliased.location.as_ref().unwrap().keyword_range.is_some()); + assert!(aliased.location.as_ref().unwrap().new_name_range.is_some()); + + let UseClause::Wildcard(wildcard) = &use_directive.clauses[2] else { + panic!("expected wildcard use clause"); + }; + assert_eq!(type_names.display(wildcard.namespace, &strings), "Foo::Baz"); + assert!(wildcard.location.is_some()); + } } From a12e0e846fdfd1526963ff6536a4cbf800f3bdff Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 6 Jun 2026 03:53:28 +0000 Subject: [PATCH 4/5] Drop ModuleSelfAnnotation arm absent from pinned v4.0.2 AST The generated Node enum is built from vendor/rbs/config.yml, which is synced from the pinned v4.0.2 tag. v4.0.2 has no ModuleSelfAnnotation node, so node_kind referenced a non-existent variant and failed to compile under cargo clippy / cargo test. https://claude.ai/code/session_01Bb4HnSKGf3PMGo1F8LUpz4 --- rust/ruby-rbs/src/ast/convert.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/ruby-rbs/src/ast/convert.rs b/rust/ruby-rbs/src/ast/convert.rs index cc7a0666d..24a797f78 100644 --- a/rust/ruby-rbs/src/ast/convert.rs +++ b/rust/ruby-rbs/src/ast/convert.rs @@ -1212,7 +1212,6 @@ fn node_kind(node: &Node<'_>) -> &'static str { Node::InstanceVariableAnnotation(_) => "InstanceVariableAnnotation", Node::MethodTypesAnnotation(_) => "MethodTypesAnnotation", Node::ModuleAliasAnnotation(_) => "ModuleAliasAnnotation", - Node::ModuleSelfAnnotation(_) => "ModuleSelfAnnotation", Node::NodeTypeAssertion(_) => "NodeTypeAssertion", Node::ParamTypeAnnotation(_) => "ParamTypeAnnotation", Node::ReturnTypeAnnotation(_) => "ReturnTypeAnnotation", From a401d94f8a409e08cf2e911a2502e8f915bd5b49 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 01:20:37 +0000 Subject: [PATCH 5/5] Box ProcType in Type enum ProcType embeds Function and an optional BlockType directly, making it ~400 bytes while the next-largest variant is 88 bytes. Since the enum size is that of its largest variant, every Type occupied 400 bytes. Boxing the rare Proc variant shrinks Type to 96 bytes and resolves the clippy::large_enum_variant error under -D warnings. https://claude.ai/code/session_01Bb4HnSKGf3PMGo1F8LUpz4 --- rust/ruby-rbs/src/ast/convert.rs | 4 ++-- rust/ruby-rbs/src/ast/types.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/ruby-rbs/src/ast/convert.rs b/rust/ruby-rbs/src/ast/convert.rs index 24a797f78..628f0720b 100644 --- a/rust/ruby-rbs/src/ast/convert.rs +++ b/rust/ruby-rbs/src/ast/convert.rs @@ -154,12 +154,12 @@ impl<'a> AstConverter<'a> { ty: Box::new(self.convert_type(&node.type_())), location: Some(convert_range(node.location())), }), - Node::ProcType(node) => Type::Proc(ProcType { + Node::ProcType(node) => Type::Proc(Box::new(ProcType { function: self.convert_function_type(&node.type_()), block: node.block().map(|block| self.convert_block_type(&block)), self_type: node.self_type().map(|ty| Box::new(self.convert_type(&ty))), location: Some(convert_range(node.location())), - }), + })), Node::RecordType(node) => { let mut fields = Vec::new(); for (key, value) in node.all_fields().iter() { diff --git a/rust/ruby-rbs/src/ast/types.rs b/rust/ruby-rbs/src/ast/types.rs index 317b8d371..3d1262dfd 100644 --- a/rust/ruby-rbs/src/ast/types.rs +++ b/rust/ruby-rbs/src/ast/types.rs @@ -17,7 +17,7 @@ pub enum Type { Optional(OptionalType), Union(UnionType), Intersection(IntersectionType), - Proc(ProcType), + Proc(Box), Literal(LiteralType), }