diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 6646c2536..425c90d98 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -278,6 +278,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" name = "ruby-rbs" version = "0.3.0" dependencies = [ + "indexmap", "ruby-rbs-sys", "serde", "serde_yaml", diff --git a/rust/ruby-rbs/Cargo.toml b/rust/ruby-rbs/Cargo.toml index 973162288..d09b9a86b 100644 --- a/rust/ruby-rbs/Cargo.toml +++ b/rust/ruby-rbs/Cargo.toml @@ -17,6 +17,7 @@ include = [ [dependencies] ruby-rbs-sys = { version = "0.3", path = "../ruby-rbs-sys" } xxhash-rust = { version = "0.8", features = ["xxh3"] } +indexmap = "2" [build-dependencies] serde = { version = "1.0", features = ["derive"] } diff --git a/rust/ruby-rbs/src/ast/declarations.rs b/rust/ruby-rbs/src/ast/declarations.rs index 5e392bc32..c35afda21 100644 --- a/rust/ruby-rbs/src/ast/declarations.rs +++ b/rust/ruby-rbs/src/ast/declarations.rs @@ -3,7 +3,7 @@ use crate::ast::comment::Comment; use crate::ast::location::{ AliasDeclarationLocation, ClassDeclarationLocation, ClassSuperLocation, ConstantDeclarationLocation, GlobalDeclarationLocation, InterfaceDeclarationLocation, - ModuleDeclarationLocation, ModuleSelfLocation, TypeAliasDeclarationLocation, + LocationRange, ModuleDeclarationLocation, ModuleSelfLocation, TypeAliasDeclarationLocation, }; use crate::ast::members::Member; use crate::ast::type_param::TypeParam; @@ -22,18 +22,54 @@ pub enum Declaration { ModuleAlias(ModuleAliasDeclaration), } +impl Declaration { + #[must_use] + pub(crate) fn location_range(&self) -> Option { + match self { + Declaration::Class(d) => d.location.as_ref().map(|l| l.range), + Declaration::Module(d) => d.location.as_ref().map(|l| l.range), + Declaration::Interface(d) => d.location.as_ref().map(|l| l.range), + Declaration::Constant(d) => d.location.as_ref().map(|l| l.range), + Declaration::Global(d) => d.location.as_ref().map(|l| l.range), + Declaration::TypeAlias(d) => d.location.as_ref().map(|l| l.range), + Declaration::ClassAlias(d) => d.location.as_ref().map(|l| l.range), + Declaration::ModuleAlias(d) => d.location.as_ref().map(|l| l.range), + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ClassMember { Member(Member), Declaration(Declaration), } +impl ClassMember { + #[must_use] + pub fn as_declaration(&self) -> Option<&Declaration> { + match self { + Self::Declaration(d) => Some(d), + Self::Member(_) => None, + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ModuleMember { Member(Member), Declaration(Declaration), } +impl ModuleMember { + #[must_use] + pub fn as_declaration(&self) -> Option<&Declaration> { + match self { + Self::Declaration(d) => Some(d), + Self::Member(_) => None, + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ClassSuper { pub name: TypeName, diff --git a/rust/ruby-rbs/src/environment/decl.rs b/rust/ruby-rbs/src/environment/decl.rs new file mode 100644 index 000000000..c3a8ef912 --- /dev/null +++ b/rust/ruby-rbs/src/environment/decl.rs @@ -0,0 +1,76 @@ +use crate::ast::Declaration; +use crate::ids::TypeName; + +use super::Environment; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct DeclId(u32); + +#[derive(Copy, Clone, Debug)] +pub(super) struct DeclNode { + pub(super) source: u32, + pub(super) parent: Option, + pub(super) index: u32, + pub(super) context: Context, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Context(Option); + +impl Context { + #[must_use] + pub const fn root() -> Self { + Self(None) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub(super) struct ContextNode { + parent: Context, + name: TypeName, +} + +impl Environment { + #[must_use] + pub fn decl(&self, id: DeclId) -> &Declaration { + self.member_decl(self.node(id)) + } + + #[must_use] + pub fn context_of(&self, id: DeclId) -> Context { + self.node(id).context + } + + pub fn context_names(&self, context: Context) -> impl Iterator + '_ { + let node = move |i: u32| &self.contexts[i as usize]; + std::iter::successors(context.0, move |&i| node(i).parent.0).map(move |i| node(i).name) + } + + pub(super) fn intern_context(&mut self, parent: Context, name: TypeName) -> Context { + let (index, _) = self.contexts.insert_full(ContextNode { parent, name }); + Context(Some(u32::try_from(index).expect("too many contexts"))) + } + + pub(super) fn node(&self, id: DeclId) -> &DeclNode { + &self.decl_nodes[id.0 as usize] + } + + pub(super) fn push_node(&mut self, node: DeclNode) -> DeclId { + let id = DeclId(u32::try_from(self.decl_nodes.len()).expect("too many declarations")); + self.decl_nodes.push(node); + id + } + + pub(super) fn member_decl(&self, node: &DeclNode) -> &Declaration { + let index = node.index as usize; + let Some(parent) = node.parent else { + return &self.sources[node.source as usize].declarations[index]; + }; + let member = match self.decl(parent) { + Declaration::Class(c) => c.members[index].as_declaration(), + Declaration::Module(m) => m.members[index].as_declaration(), + _ => unreachable!("only class/module declarations have child declarations"), + }; + member.expect("DeclNode must point at a Declaration") + } +} diff --git a/rust/ruby-rbs/src/environment/entry.rs b/rust/ruby-rbs/src/environment/entry.rs new file mode 100644 index 000000000..0b0d47382 --- /dev/null +++ b/rust/ruby-rbs/src/environment/entry.rs @@ -0,0 +1,423 @@ +use std::iter::{Chain, Copied, Once}; +use std::marker::PhantomData; +use std::slice; + +use crate::ids::{SymbolId, TypeName}; + +use super::{DeclId, Environment}; + +/// `name` repeats the table key, as in Ruby, so iterating entries alone +/// yields their names. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct SingleEntry { + pub name: N, + pub decl: DeclId, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ClassDeclKind { + Class, + Module, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ClassTag; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ModuleTag; + +pub type DeclIter<'a> = Chain, Copied>>; + +/// Ruby's `ClassEntry` and `ModuleEntry` differ only in `primary_decl`, so +/// they share one layout and the tag selects which `primary_decl` exists. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Entry { + name: TypeName, + first: DeclId, + rest: Vec, + primary: DeclId, + primary_has_super: bool, + kind: PhantomData, +} + +pub type ClassEntry = Entry; +pub type ModuleEntry = Entry; + +impl Entry { + pub(super) fn new(name: TypeName, first: DeclId, has_super: bool) -> Self { + Self { + name, + first, + rest: Vec::new(), + primary: first, + primary_has_super: has_super, + kind: PhantomData, + } + } + + pub(super) fn push(&mut self, decl: DeclId, has_super: bool) { + self.rest.push(decl); + if has_super && !self.primary_has_super { + self.primary = decl; + self.primary_has_super = true; + } + } + + #[must_use] + pub fn name(&self) -> TypeName { + self.name + } + + pub fn each_decl(&self) -> DeclIter<'_> { + self.decls() + } + + fn decls(&self) -> DeclIter<'_> { + std::iter::once(self.first).chain(self.rest.iter().copied()) + } +} + +impl Entry { + #[must_use] + pub fn primary_decl(&self) -> DeclId { + self.primary + } +} + +impl Entry { + #[must_use] + pub fn primary_decl(&self) -> DeclId { + self.first + } +} + +/// Ruby's `class_decls` holds both kinds under one name space, so a name is +/// either a class or a module, never both. The name follows `class_decls`, +/// so it covers modules too. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ClassDeclEntry { + Class(ClassEntry), + Module(ModuleEntry), +} + +impl ClassDeclEntry { + pub(super) fn new(name: TypeName, kind: ClassDeclKind, first: DeclId, has_super: bool) -> Self { + match kind { + ClassDeclKind::Class => Self::Class(Entry::new(name, first, has_super)), + ClassDeclKind::Module => Self::Module(Entry::new(name, first, false)), + } + } + + pub(super) fn push(&mut self, decl: DeclId, has_super: bool) { + match self { + Self::Class(e) => e.push(decl, has_super), + Self::Module(e) => e.push(decl, false), + } + } + + #[must_use] + pub fn kind(&self) -> ClassDeclKind { + match self { + Self::Class(_) => ClassDeclKind::Class, + Self::Module(_) => ClassDeclKind::Module, + } + } + + #[must_use] + pub fn name(&self) -> TypeName { + match self { + Self::Class(e) => e.name(), + Self::Module(e) => e.name(), + } + } + + pub fn each_decl(&self) -> DeclIter<'_> { + self.decls() + } + + fn decls(&self) -> DeclIter<'_> { + match self { + Self::Class(e) => e.decls(), + Self::Module(e) => e.decls(), + } + } + + #[must_use] + pub fn primary_decl(&self) -> DeclId { + match self { + Self::Class(e) => e.primary_decl(), + Self::Module(e) => e.primary_decl(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum AliasEntry { + Module(SingleEntry), + Class(SingleEntry), +} + +impl SingleEntry { + fn decls(&self) -> DeclIter<'_> { + std::iter::once(self.decl).chain([].iter().copied()) + } +} + +/// Named `*Lookup` to stay apart from Ruby's `ConstantEntry`, which is the +/// `Constant` arm here. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ConstantLookup<'a> { + ClassDecl(&'a ClassDeclEntry), + ClassAlias(&'a SingleEntry), + ModuleAlias(&'a SingleEntry), + Constant(&'a SingleEntry), +} + +impl<'a> ConstantLookup<'a> { + pub fn each_decl(&self) -> DeclIter<'a> { + match *self { + Self::ClassDecl(e) => e.decls(), + Self::ClassAlias(e) | Self::ModuleAlias(e) | Self::Constant(e) => e.decls(), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ClassLookup<'a> { + Class(&'a ClassEntry), + ClassAlias(&'a SingleEntry), +} + +impl<'a> ClassLookup<'a> { + #[must_use] + pub fn name(&self) -> TypeName { + match self { + Self::Class(e) => e.name(), + Self::ClassAlias(e) => e.name, + } + } + + pub fn each_decl(&self) -> DeclIter<'a> { + match *self { + Self::Class(e) => e.decls(), + Self::ClassAlias(e) => e.decls(), + } + } + + #[must_use] + pub fn primary_decl(&self) -> DeclId { + match self { + Self::Class(e) => e.primary_decl(), + Self::ClassAlias(e) => e.decl, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ModuleClassLookup<'a> { + ClassDecl(&'a ClassDeclEntry), + ClassAlias(&'a SingleEntry), + ModuleAlias(&'a SingleEntry), +} + +impl<'a> ModuleClassLookup<'a> { + #[must_use] + pub fn name(&self) -> TypeName { + match self { + Self::ClassDecl(e) => e.name(), + Self::ClassAlias(e) | Self::ModuleAlias(e) => e.name, + } + } + + pub fn each_decl(&self) -> DeclIter<'a> { + match *self { + Self::ClassDecl(e) => e.decls(), + Self::ClassAlias(e) | Self::ModuleAlias(e) => e.decls(), + } + } + + #[must_use] + pub fn primary_decl(&self) -> DeclId { + match self { + Self::ClassDecl(e) => e.primary_decl(), + Self::ClassAlias(e) | Self::ModuleAlias(e) => e.decl, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ModuleLookup<'a> { + Module(&'a ModuleEntry), + ModuleAlias(&'a SingleEntry), +} + +impl<'a> ModuleLookup<'a> { + #[must_use] + pub fn name(&self) -> TypeName { + match self { + Self::Module(e) => e.name(), + Self::ModuleAlias(e) => e.name, + } + } + + pub fn each_decl(&self) -> DeclIter<'a> { + match *self { + Self::Module(e) => e.decls(), + Self::ModuleAlias(e) => e.decls(), + } + } + + #[must_use] + pub fn primary_decl(&self) -> DeclId { + match self { + Self::Module(e) => e.primary_decl(), + Self::ModuleAlias(e) => e.decl, + } + } +} + +impl Environment { + #[must_use] + pub fn is_class_decl(&self, name: TypeName) -> bool { + matches!(self.class_decls.get(&name), Some(ClassDeclEntry::Class(_))) + } + + #[must_use] + pub fn is_module_decl(&self, name: TypeName) -> bool { + matches!(self.class_decls.get(&name), Some(ClassDeclEntry::Module(_))) + } + + #[must_use] + pub fn is_module_alias(&self, name: TypeName) -> bool { + matches!( + self.class_alias_decls.get(&name), + Some(AliasEntry::Module(_)) + ) + } + + #[must_use] + pub fn is_class_alias(&self, name: TypeName) -> bool { + matches!( + self.class_alias_decls.get(&name), + Some(AliasEntry::Class(_)) + ) + } + + #[must_use] + pub fn is_module_name(&self, name: TypeName) -> bool { + self.class_decls.contains_key(&name) || self.class_alias_decls.contains_key(&name) + } + + #[must_use] + pub fn is_type_name(&self, name: TypeName) -> bool { + self.is_interface_name(name) || self.is_type_alias_name(name) || self.is_module_name(name) + } + + #[must_use] + pub fn class_entry(&self, name: TypeName) -> Option> { + match self.constant_entry(name)? { + ConstantLookup::ClassDecl(ClassDeclEntry::Class(e)) => Some(ClassLookup::Class(e)), + ConstantLookup::ClassAlias(e) => Some(ClassLookup::ClassAlias(e)), + ConstantLookup::ClassDecl(ClassDeclEntry::Module(_)) + | ConstantLookup::ModuleAlias(_) + | ConstantLookup::Constant(_) => None, + } + } + + #[must_use] + pub fn module_entry(&self, name: TypeName) -> Option> { + match self.constant_entry(name)? { + ConstantLookup::ClassDecl(ClassDeclEntry::Module(e)) => Some(ModuleLookup::Module(e)), + ConstantLookup::ModuleAlias(e) => Some(ModuleLookup::ModuleAlias(e)), + ConstantLookup::ClassDecl(ClassDeclEntry::Class(_)) + | ConstantLookup::ClassAlias(_) + | ConstantLookup::Constant(_) => None, + } + } + + #[must_use] + pub fn module_class_entry(&self, name: TypeName) -> Option> { + match self.constant_entry(name)? { + ConstantLookup::ClassDecl(e) => Some(ModuleClassLookup::ClassDecl(e)), + ConstantLookup::ClassAlias(e) => Some(ModuleClassLookup::ClassAlias(e)), + ConstantLookup::ModuleAlias(e) => Some(ModuleClassLookup::ModuleAlias(e)), + ConstantLookup::Constant(_) => None, + } + } + + #[must_use] + pub fn constant_entry(&self, name: TypeName) -> Option> { + if let Some(e) = self.class_decls.get(&name) { + return Some(ConstantLookup::ClassDecl(e)); + } + match self.class_alias_decls.get(&name) { + Some(AliasEntry::Class(e)) => return Some(ConstantLookup::ClassAlias(e)), + Some(AliasEntry::Module(e)) => return Some(ConstantLookup::ModuleAlias(e)), + None => {} + } + self.constant_decls.get(&name).map(ConstantLookup::Constant) + } + + #[must_use] + pub fn is_constant_decl(&self, name: TypeName) -> bool { + self.constant_decls.contains_key(&name) + } + + #[must_use] + pub fn is_constant_name(&self, name: TypeName) -> bool { + self.is_constant_decl(name) || self.is_module_name(name) + } + + pub fn constant_entries(&self) -> impl Iterator { + self.constant_decls.values() + } + + pub fn class_entries(&self) -> impl Iterator { + self.class_decls.values() + } + + #[must_use] + pub fn is_interface_name(&self, name: TypeName) -> bool { + self.interface_decls.contains_key(&name) + } + + #[must_use] + pub fn interface_entry(&self, name: TypeName) -> Option<&SingleEntry> { + self.interface_decls.get(&name) + } + + pub fn interface_entries(&self) -> impl Iterator { + self.interface_decls.values() + } + + #[must_use] + pub fn is_type_alias_name(&self, name: TypeName) -> bool { + self.type_alias_decls.contains_key(&name) + } + + #[must_use] + pub fn type_alias_entry(&self, name: TypeName) -> Option<&SingleEntry> { + self.type_alias_decls.get(&name) + } + + pub fn type_alias_entries(&self) -> impl Iterator { + self.type_alias_decls.values() + } +} + +impl Environment { + #[must_use] + pub fn is_global_name(&self, name: SymbolId) -> bool { + self.global_decls.contains_key(&name) + } + + #[must_use] + pub fn global_entry(&self, name: SymbolId) -> Option<&SingleEntry> { + self.global_decls.get(&name) + } + + pub fn global_entries(&self) -> impl Iterator> { + self.global_decls.values() + } +} diff --git a/rust/ruby-rbs/src/environment/error.rs b/rust/ruby-rbs/src/environment/error.rs new file mode 100644 index 000000000..08f1dfc29 --- /dev/null +++ b/rust/ruby-rbs/src/environment/error.rs @@ -0,0 +1,54 @@ +use std::fmt; +use std::path::PathBuf; + +use crate::ast::location::LocationRange; + +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct DuplicatedDecl { + pub path: PathBuf, + pub location: Option, +} + +/// Owns rendered data instead of `DeclId`s because it outlives the +/// `Environment` (`Environment::from_loader` drops it on error). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DuplicatedDeclarationError { + name: String, + decls: Vec, +} + +impl DuplicatedDeclarationError { + pub(crate) fn new( + name: String, + inserted: DuplicatedDecl, + first_existing: DuplicatedDecl, + rest: impl IntoIterator, + ) -> Self { + let decls = [inserted, first_existing].into_iter().chain(rest).collect(); + Self { name, decls } + } + + #[must_use] + pub fn name(&self) -> &str { + &self.name + } + + /// A slice rather than a pair because Ruby's error takes `*decls`, so a + /// constant clashing with a class reports every reopening. + #[must_use] + pub fn decls(&self) -> &[DuplicatedDecl] { + &self.decls + } +} + +impl fmt::Display for DuplicatedDeclarationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Ruby's nil-location fallback: line:col needs the source text, which + // `Source` does not keep yet, and path alone matches no Ruby format. + // TODO: render `decls.last()` as `path:line:col...line:col`. + write!(f, "*:*:*...*:*: Duplicated declaration: {}", self.name) + } +} + +impl std::error::Error for DuplicatedDeclarationError {} diff --git a/rust/ruby-rbs/src/environment/insert.rs b/rust/ruby-rbs/src/environment/insert.rs new file mode 100644 index 000000000..526798f41 --- /dev/null +++ b/rust/ruby-rbs/src/environment/insert.rs @@ -0,0 +1,1227 @@ +use indexmap::map::Entry; + +use crate::ast::{ClassMember, Declaration, ModuleMember}; +use crate::ids::{Id, IdIndexMap, SymbolId, TypeName}; +use crate::interners::Interners; + +use super::decl::{Context, DeclNode}; +use super::entry::{AliasEntry, ClassDeclEntry, ClassDeclKind, ConstantLookup, SingleEntry}; +use super::error::{DuplicatedDecl, DuplicatedDeclarationError}; +use super::source::Source; +use super::{DeclId, Environment}; + +type SingleTable = IdIndexMap>>; + +impl Environment { + pub(crate) fn add_source(&mut self, source: Source) -> Result<(), DuplicatedDeclarationError> { + let source_index = u32::try_from(self.sources.len()).expect("too many sources"); + let decl_count = + u32::try_from(source.declarations.len()).expect("too many declarations in one source"); + self.sources.push(source); + + let root = self.interners.type_names.absolute_root(); + for index in 0..decl_count { + self.insert_decl(source_index, None, index, root, Context::root())?; + } + Ok(()) + } + + fn insert_decl( + &mut self, + source: u32, + parent: Option, + index: u32, + namespace: TypeName, + context: Context, + ) -> Result<(), DuplicatedDeclarationError> { + enum Kind { + ClassOrModule { + kind: ClassDeclKind, + name: TypeName, + has_super: bool, + decl_members: Vec, + }, + Interface(TypeName), + TypeAlias(TypeName), + Constant(TypeName), + Alias(TypeName, fn(SingleEntry) -> AliasEntry), + Global(SymbolId), + } + + let node = DeclNode { + source, + parent, + index, + context, + }; + let kind = match self.member_decl(&node) { + Declaration::Class(c) => Kind::ClassOrModule { + kind: ClassDeclKind::Class, + name: c.name, + has_super: c.super_class.is_some(), + decl_members: decl_member_indices(&c.members, ClassMember::as_declaration), + }, + Declaration::Module(m) => Kind::ClassOrModule { + kind: ClassDeclKind::Module, + name: m.name, + has_super: false, + decl_members: decl_member_indices(&m.members, ModuleMember::as_declaration), + }, + Declaration::Interface(d) => Kind::Interface(d.name), + Declaration::TypeAlias(d) => Kind::TypeAlias(d.name), + Declaration::Constant(d) => Kind::Constant(d.name), + Declaration::Global(d) => Kind::Global(d.name), + Declaration::ModuleAlias(d) => Kind::Alias(d.new_name, AliasEntry::Module), + Declaration::ClassAlias(d) => Kind::Alias(d.new_name, AliasEntry::Class), + }; + + match kind { + Kind::ClassOrModule { + kind, + name, + has_super, + decl_members, + } => { + let name = self.interners.type_names.concat(namespace, name); + if let Some((first, rest)) = self.constant_clash(name, Some(kind)) { + return Err(self.duplicated_type_name(name, &node, first, rest)); + } + let id = self.push_node(node); + match self.class_decls.entry(name) { + Entry::Occupied(mut e) => e.get_mut().push(id, has_super), + Entry::Vacant(e) => { + e.insert(ClassDeclEntry::new(name, kind, id, has_super)); + } + } + self.insert_members(node.source, node.context, id, name, &decl_members) + } + Kind::Interface(name) => { + let name = self.interners.type_names.concat(namespace, name); + self.insert_single( + |env| &mut env.interface_decls, + name, + node, + |i, n| i.type_names.display(n, &i.strings), + ) + } + Kind::TypeAlias(name) => { + let name = self.interners.type_names.concat(namespace, name); + self.insert_single( + |env| &mut env.type_alias_decls, + name, + node, + |i, n| i.type_names.display(n, &i.strings), + ) + } + Kind::Constant(name) => self.insert_constant_like(namespace, name, node, |env, e| { + env.constant_decls.insert(e.name, e); + }), + Kind::Alias(name, wrap) => { + self.insert_constant_like(namespace, name, node, |env, e| { + env.class_alias_decls.insert(e.name, wrap(e)); + }) + } + Kind::Global(name) => self.insert_single( + |env| &mut env.global_decls, + name, + node, + |i, n| i.strings.resolve(n).to_owned(), + ), + } + } + + fn insert_members( + &mut self, + source: u32, + context: Context, + id: DeclId, + name: TypeName, + decl_members: &[u32], + ) -> Result<(), DuplicatedDeclarationError> { + if decl_members.is_empty() { + return Ok(()); + } + let inner = self.intern_context(context, name); + for &member in decl_members { + self.insert_decl(source, Some(id), member, name, inner)?; + } + Ok(()) + } + + fn insert_constant_like( + &mut self, + namespace: TypeName, + name: TypeName, + node: DeclNode, + store: impl FnOnce(&mut Self, SingleEntry), + ) -> Result<(), DuplicatedDeclarationError> { + let name = self.interners.type_names.concat(namespace, name); + if let Some((first, rest)) = self.constant_clash(name, None) { + return Err(self.duplicated_type_name(name, &node, first, rest)); + } + let decl = self.push_node(node); + store(self, SingleEntry { name, decl }); + Ok(()) + } + + fn insert_single( + &mut self, + table: fn(&mut Self) -> &mut SingleTable, + name: Id, + node: DeclNode, + render: fn(&Interners, Id) -> String, + ) -> Result<(), DuplicatedDeclarationError> { + if let Some(entry) = table(self).get(&name) { + let existing = entry.decl; + let name = render(&self.interners, name); + return Err(self.duplicated_declaration(name, &node, existing, [])); + } + self.register_single(table, name, node); + Ok(()) + } + + fn register_single( + &mut self, + table: fn(&mut Self) -> &mut SingleTable, + name: Id, + node: DeclNode, + ) { + let decl = self.push_node(node); + table(self).insert(name, SingleEntry { name, decl }); + } + + fn duplicated_type_name( + &self, + name: TypeName, + inserted: &DeclNode, + first: DeclId, + rest: impl IntoIterator, + ) -> DuplicatedDeclarationError { + let name = self + .interners + .type_names + .display(name, &self.interners.strings); + self.duplicated_declaration(name, inserted, first, rest) + } + + fn duplicated_declaration( + &self, + name: String, + inserted: &DeclNode, + first: DeclId, + rest: impl IntoIterator, + ) -> DuplicatedDeclarationError { + DuplicatedDeclarationError::new( + name, + self.duplicated_decl(inserted), + self.duplicated_decl(self.node(first)), + rest.into_iter() + .map(|id| self.duplicated_decl(self.node(id))), + ) + } + + fn constant_clash( + &self, + name: TypeName, + class_kind: Option, + ) -> Option<(DeclId, Vec)> { + match (self.constant_entry(name)?, class_kind) { + (ConstantLookup::ClassDecl(e), Some(kind)) => { + (e.kind() != kind).then(|| (e.primary_decl(), Vec::new())) + } + (entry, _) => { + let mut ids = entry.each_decl(); + let first = ids.next()?; + Some((first, ids.collect())) + } + } + } + + fn duplicated_decl(&self, node: &DeclNode) -> DuplicatedDecl { + DuplicatedDecl { + path: self.sources[node.source as usize].path.clone(), + location: self.member_decl(node).location_range(), + } + } +} + +fn decl_member_indices( + members: &[M], + as_declaration: impl Fn(&M) -> Option<&Declaration>, +) -> Vec { + members + .iter() + .enumerate() + .filter(|(_, m)| as_declaration(m).is_some()) + .map(|(i, _)| u32::try_from(i).expect("too many members")) + .collect() +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::ast::{AstConverter, Declaration}; + use crate::environment::{ + ClassDeclEntry, ClassLookup, ConstantLookup, Context, DuplicatedDeclarationError, + Environment, ModuleClassLookup, ModuleLookup, Source, SourceKind, + }; + use crate::ids::{SymbolId, TypeName}; + use crate::node; + + fn parse_decls(env: &mut Environment, src: &str) -> Vec { + let signature = node::parse(src).expect("valid RBS source"); + let interners = env.interners_mut(); + let mut converter = AstConverter::new(&mut interners.strings, &mut interners.type_names); + signature + .declarations() + .iter() + .map(|node| converter.convert_declaration(&node)) + .collect() + } + + fn add_rbs_source( + env: &mut Environment, + path: &str, + declarations: Vec, + ) -> Result<(), DuplicatedDeclarationError> { + env.add_source(Source { + path: PathBuf::from(path), + directives: Vec::new(), + declarations, + kind: SourceKind::Dir { + path: PathBuf::from("."), + }, + }) + } + + fn type_name(env: &mut Environment, name: &str) -> TypeName { + let interners = env.interners_mut(); + interners.type_names.parse(&mut interners.strings, name) + } + + fn intern(env: &mut Environment, name: &str) -> SymbolId { + env.interners_mut().strings.intern(name) + } + + fn nested_decl(parent: &Declaration, index: usize) -> &Declaration { + let member = match parent { + Declaration::Class(c) => c.members[index].as_declaration(), + Declaration::Module(m) => m.members[index].as_declaration(), + _ => panic!("expected a class or module declaration"), + }; + member.unwrap_or_else(|| panic!("expected member {index} to be a declaration")) + } + + fn assert_duplicate( + err: &DuplicatedDeclarationError, + inserted: (&str, &Declaration), + existing: &[(&str, &Declaration)], + ) { + let (got_inserted, got_existing) = err.decls().split_first().expect("decls is not empty"); + assert_eq!(got_inserted.path, PathBuf::from(inserted.0)); + assert_eq!(got_inserted.location, inserted.1.location_range()); + assert_eq!(got_existing.len(), existing.len(), "{:?}", err.decls()); + for (got, want) in got_existing.iter().zip(existing) { + assert_eq!(got.path, PathBuf::from(want.0)); + assert_eq!(got.location, want.1.location_range()); + } + } + + #[test] + fn interface_twice_duplication_error() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "interface _I\nend\ninterface _I\nend\n"); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "::_I"); + assert_duplicate(&err, ("b.rbs", &decls[1]), &[("a.rbs", &decls[0])]); + assert_eq!(err.to_string(), "*:*:*...*:*: Duplicated declaration: ::_I"); + + assert_eq!(env.interface_entries().count(), 1); + let name = type_name(&mut env, "::_I"); + let entry = env.interface_entry(name).unwrap(); + assert_eq!(env.decl(entry.decl), &decls[0]); + } + + #[test] + fn absolute_and_relative_names_collide() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "interface _I\nend\ninterface ::_I\nend\n"); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "::_I"); + } + + #[test] + fn duplication_within_a_source_keeps_earlier_entries() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "interface _A\nend\ninterface _I\nend\ninterface _I\nend\ninterface _B\nend\n", + ); + + let err = add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap_err(); + + assert_duplicate(&err, ("a.rbs", &decls[2]), &[("a.rbs", &decls[1])]); + + assert_eq!(env.sources().len(), 1); + let a = type_name(&mut env, "::_A"); + let i = type_name(&mut env, "::_I"); + let b = type_name(&mut env, "::_B"); + let names: Vec<_> = env.interface_entries().map(|e| e.name).collect(); + assert_eq!(names, [a, i]); + assert_eq!(env.decl(env.interface_entry(i).unwrap().decl), &decls[1]); + assert!(!env.is_interface_name(b)); + } + + #[test] + fn distinct_interfaces_are_registered_in_order() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "interface _A\nend\ninterface _B\nend\n"); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap(); + + let a = type_name(&mut env, "::_A"); + let b = type_name(&mut env, "::_B"); + let unknown = type_name(&mut env, "::_C"); + assert!(env.is_interface_name(a)); + assert!(env.is_interface_name(b)); + assert!(!env.is_interface_name(unknown)); + assert!(env.interface_entry(unknown).is_none()); + + let names: Vec<_> = env.interface_entries().map(|e| e.name).collect(); + assert_eq!(names, [a, b]); + for (entry, decl) in env.interface_entries().zip(&decls) { + assert_eq!(env.decl(entry.decl), decl); + } + } + + #[test] + fn type_alias_twice_duplication_error() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "type foo = String\ntype foo = Integer\n"); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "::foo"); + assert_duplicate(&err, ("b.rbs", &decls[1]), &[("a.rbs", &decls[0])]); + + assert_eq!(env.type_alias_entries().count(), 1); + let name = type_name(&mut env, "::foo"); + let entry = env.type_alias_entry(name).unwrap(); + assert_eq!(env.decl(entry.decl), &decls[0]); + } + + #[test] + fn interface_and_type_alias_tables_are_independent() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "interface _I\nend\ntype i = String\n$g: String\n"); + + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let interface = type_name(&mut env, "::_I"); + let alias = type_name(&mut env, "::i"); + assert!(env.is_interface_name(interface)); + assert!(!env.is_type_alias_name(interface)); + assert!(env.is_type_alias_name(alias)); + assert!(!env.is_interface_name(alias)); + assert_eq!(env.interface_entries().count(), 1); + assert_eq!(env.type_alias_entries().count(), 1); + assert_eq!(env.global_entries().count(), 1); + } + + #[test] + fn insert_global() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "$VERSION: String\n"); + + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let name = intern(&mut env, "$VERSION"); + assert!(env.is_global_name(name)); + let entry = env.global_entry(name).unwrap(); + assert_eq!(entry.name, name); + assert_eq!(env.decl(entry.decl), &decls[0]); + + let other = intern(&mut env, "$OTHER"); + assert!(!env.is_global_name(other)); + assert!(env.global_entry(other).is_none()); + } + + #[test] + fn global_twice_duplication_error() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "$VERSION: String\n$VERSION: Integer\n"); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "$VERSION"); + assert_duplicate(&err, ("b.rbs", &decls[1]), &[("a.rbs", &decls[0])]); + + assert_eq!(env.global_entries().count(), 1); + let name = intern(&mut env, "$VERSION"); + let entry = env.global_entry(name).unwrap(); + assert_eq!(env.decl(entry.decl), &decls[0]); + } + + #[test] + fn globals_are_registered_in_order() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "$B: String\n$A: String\n"); + + add_rbs_source(&mut env, "a.rbs", decls).unwrap(); + + let a = intern(&mut env, "$A"); + let b = intern(&mut env, "$B"); + let names: Vec<_> = env.global_entries().map(|e| e.name).collect(); + assert_eq!(names, [b, a]); + } + + #[test] + fn primary_decl_is_first_declaration() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "interface _Animal\nend\nmodule Foo : _Animal\nend\nmodule Foo : Object\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let foo = type_name(&mut env, "::Foo"); + let ModuleLookup::Module(entry) = env.module_entry(foo).unwrap() else { + panic!("expected a module entry"); + }; + assert_eq!(env.decl(entry.primary_decl()), &decls[1]); + } + + #[test] + fn insert_nested_modules() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo\n module Bar\n end\n module ::Baz\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls).unwrap(); + + let names: Vec<_> = env.class_entries().map(ClassDeclEntry::name).collect(); + let expected: Vec<_> = ["::Foo", "::Foo::Bar", "::Baz"] + .into_iter() + .map(|n| type_name(&mut env, n)) + .collect(); + assert_eq!(names, expected); + for name in expected { + assert!(env.is_module_decl(name)); + assert!(env.is_module_name(name)); + } + let unknown = type_name(&mut env, "::Foo::Baz"); + assert!(!env.is_module_decl(unknown)); + assert!(!env.is_module_name(unknown)); + } + + #[test] + fn reopened_module_is_one_entry() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "module Foo\nend\nmodule Foo\nend\n"); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap(); + + assert_eq!(env.class_entries().count(), 1); + let foo = type_name(&mut env, "::Foo"); + let entry = env.module_entry(foo).unwrap(); + let registered: Vec<_> = entry.each_decl().map(|id| env.decl(id)).collect(); + assert_eq!(registered, [&decls[0], &decls[1]]); + } + + #[test] + fn context_of_nested_declarations() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo\n module ::Baz\n module Qux\n end\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [foo, baz, qux] = ["::Foo", "::Baz", "::Baz::Qux"].map(|n| type_name(&mut env, n)); + let id = |name| env.module_entry(name).unwrap().each_decl().next().unwrap(); + + assert_eq!(env.context_names(env.context_of(id(foo))).count(), 0); + let names: Vec<_> = env.context_names(env.context_of(id(baz))).collect(); + assert_eq!(names, [foo]); + let names: Vec<_> = env.context_names(env.context_of(id(qux))).collect(); + assert_eq!(names, [baz, foo]); + + assert_eq!(env.decl(id(qux)), nested_decl(nested_decl(&decls[0], 0), 0)); + } + + #[test] + fn context_is_equal_across_reopenings() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo\n module Bar\n end\nend\nmodule Foo\n module Bar\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls).unwrap(); + + let bar = type_name(&mut env, "::Foo::Bar"); + let ids: Vec<_> = env.module_entry(bar).unwrap().each_decl().collect(); + assert_eq!(ids.len(), 2); + assert_eq!(env.context_of(ids[0]), env.context_of(ids[1])); + assert_ne!(env.context_of(ids[0]), Context::root()); + } + + #[test] + fn context_is_per_declaration_within_an_entry() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo::Bar\nend\nmodule Foo\n module Bar\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [foo, bar] = ["::Foo", "::Foo::Bar"].map(|n| type_name(&mut env, n)); + let ids: Vec<_> = env.module_entry(bar).unwrap().each_decl().collect(); + assert_eq!(ids.len(), 2); + assert_eq!(env.decl(ids[0]), &decls[0]); + assert_eq!(env.decl(ids[1]), nested_decl(&decls[1], 0)); + assert_eq!(env.context_of(ids[0]), Context::root()); + let names: Vec<_> = env.context_names(env.context_of(ids[1])).collect(); + assert_eq!(names, [foo]); + } + + #[test] + fn declarations_in_a_module_use_its_namespace() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo\n interface _I\n end\n type t = String\n def foo: () -> void\n $g: String\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let interface = type_name(&mut env, "::Foo::_I"); + let alias = type_name(&mut env, "::Foo::t"); + let relative = type_name(&mut env, "::_I"); + assert!(env.is_interface_name(interface)); + assert!(env.is_type_alias_name(alias)); + assert!(!env.is_interface_name(relative)); + let global = intern(&mut env, "$g"); + assert!(env.is_global_name(global)); + + let module = &decls[0]; + let entry = env.interface_entry(interface).unwrap(); + assert_eq!(env.decl(entry.decl), nested_decl(module, 0)); + let entry = env.type_alias_entry(alias).unwrap(); + assert_eq!(env.decl(entry.decl), nested_decl(module, 1)); + let entry = env.global_entry(global).unwrap(); + assert_eq!(env.decl(entry.decl), nested_decl(module, 3)); + + let entry = env.interface_entry(interface).unwrap(); + let names: Vec<_> = env.context_names(env.context_of(entry.decl)).collect(); + let foo = type_name(&mut env, "::Foo"); + assert_eq!(names, [foo]); + } + + #[test] + fn nested_duplicate_is_reported_without_leaving_a_node() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo\n interface _I\n end\nend\nmodule Foo\n interface _I\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let nodes = env.decl_nodes.len(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "::Foo::_I"); + assert_duplicate( + &err, + ("b.rbs", nested_decl(&decls[1], 0)), + &[("a.rbs", nested_decl(&decls[0], 0))], + ); + assert_eq!(env.decl_nodes.len(), nodes + 1); + assert_eq!(env.interface_entries().count(), 1); + let foo = type_name(&mut env, "::Foo"); + assert_eq!(env.module_entry(foo).unwrap().each_decl().count(), 2); + } + + #[test] + fn insert_nested_modules_in_a_class() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "class Foo\n module Bar\n end\n module ::Baz\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls).unwrap(); + + let [foo, bar, baz] = ["::Foo", "::Foo::Bar", "::Baz"].map(|n| type_name(&mut env, n)); + assert!(env.is_class_decl(foo)); + assert!(!env.is_module_decl(foo)); + assert!(env.is_module_decl(bar)); + assert!(env.is_module_decl(baz)); + assert!(env.is_module_name(foo)); + assert!(env.class_entry(bar).is_none()); + assert!(env.module_entry(foo).is_none()); + let names: Vec<_> = env.class_entries().map(ClassDeclEntry::name).collect(); + assert_eq!(names, [foo, bar, baz]); + let ModuleLookup::Module(entry) = env.module_entry(bar).unwrap() else { + panic!("expected a module entry"); + }; + let names: Vec<_> = env + .context_names(env.context_of(entry.primary_decl())) + .collect(); + assert_eq!(names, [foo]); + } + + #[test] + fn insert_open_class() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "class Foo\n module Bar\n end\nend\nclass Foo < String\n module Bar\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [foo, bar] = ["::Foo", "::Foo::Bar"].map(|n| type_name(&mut env, n)); + let string = type_name(&mut env, "String"); + let entry = env.class_entry(foo).unwrap(); + assert_eq!(entry.each_decl().count(), 2); + let primary = env.decl(entry.primary_decl()); + assert_eq!(primary, &decls[1]); + let Declaration::Class(primary) = primary else { + panic!("expected a class declaration"); + }; + assert_eq!(primary.super_class.as_ref().unwrap().name, string); + + assert_eq!(env.module_entry(bar).unwrap().each_decl().count(), 2); + } + + #[test] + fn class_primary_decl_falls_back_to_first() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "class Foo\nend\nclass Foo\nend\n"); + + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let foo = type_name(&mut env, "::Foo"); + let entry = env.class_entry(foo).unwrap(); + assert_eq!(env.decl(entry.primary_decl()), &decls[0]); + } + + #[test] + fn module_then_class_is_rejected() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "module Foo\nend\nclass Foo < String\nend\n"); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "::Foo"); + assert_duplicate(&err, ("b.rbs", &decls[1]), &[("a.rbs", &decls[0])]); + let foo = type_name(&mut env, "::Foo"); + assert!(env.is_module_decl(foo)); + assert!(!env.is_class_decl(foo)); + } + + #[test] + fn class_then_module_reports_the_primary_decl() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "class Foo\nend\nclass Foo < String\nend\nmodule Foo\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls[..2].to_vec()).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[2].clone()]).unwrap_err(); + + assert_duplicate(&err, ("b.rbs", &decls[2]), &[("a.rbs", &decls[1])]); + let foo = type_name(&mut env, "::Foo"); + assert!(env.is_class_decl(foo)); + assert_eq!(env.class_entry(foo).unwrap().each_decl().count(), 2); + } + + #[test] + fn nested_kind_mismatch_is_rejected() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "class Foo\n module Bar\n end\nend\nclass Foo\n class Bar\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "::Foo::Bar"); + assert_duplicate( + &err, + ("b.rbs", nested_decl(&decls[1], 0)), + &[("a.rbs", nested_decl(&decls[0], 0))], + ); + } + + #[test] + fn members_of_a_rejected_declaration_are_not_registered() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo\nend\nclass Foo\n module Baz\n end\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let nodes = env.decl_nodes.len(); + add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + let baz = type_name(&mut env, "::Foo::Baz"); + assert!(!env.is_module_name(baz)); + assert_eq!(env.class_entries().count(), 1); + assert_eq!(env.decl_nodes.len(), nodes); + } + + #[test] + fn insert_class_in_a_module() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "module Foo\n class Bar\n end\nend\n"); + + add_rbs_source(&mut env, "a.rbs", decls).unwrap(); + + let [foo, bar] = ["::Foo", "::Foo::Bar"].map(|n| type_name(&mut env, n)); + assert!(env.is_module_decl(foo)); + assert!(env.is_class_decl(bar)); + assert!(env.class_entry(bar).is_some()); + assert!(env.module_entry(bar).is_none()); + let names: Vec<_> = env + .context_names(env.context_of(env.class_entry(bar).unwrap().primary_decl())) + .collect(); + assert_eq!(names, [foo]); + } + + #[test] + fn insert_const_duplication_error() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo\nend\nBar: ::Integer\nFoo: String\nclass Bar\nend\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls[..2].to_vec()).unwrap(); + + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[2].clone()]).unwrap_err(); + assert_eq!(err.name(), "::Foo"); + assert_duplicate(&err, ("b.rbs", &decls[2]), &[("a.rbs", &decls[0])]); + + let err = add_rbs_source(&mut env, "c.rbs", vec![decls[3].clone()]).unwrap_err(); + assert_eq!(err.name(), "::Bar"); + assert_duplicate(&err, ("c.rbs", &decls[3]), &[("a.rbs", &decls[1])]); + } + + #[test] + fn const_twice_duplication_error() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "Foo: String\nFoo: String\n"); + + let err = add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap_err(); + + assert_eq!(err.name(), "::Foo"); + assert_duplicate(&err, ("a.rbs", &decls[1]), &[("a.rbs", &decls[0])]); + } + + #[test] + fn const_clashing_with_a_reopened_class_reports_every_declaration() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "class Foo\nend\nclass Foo\nend\nFoo: String\n"); + + add_rbs_source(&mut env, "a.rbs", decls[..2].to_vec()).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[2].clone()]).unwrap_err(); + + assert_duplicate( + &err, + ("b.rbs", &decls[2]), + &[("a.rbs", &decls[0]), ("a.rbs", &decls[1])], + ); + } + + #[test] + fn const_clashing_with_a_reopened_module_reports_every_declaration() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "module Foo\nend\nmodule Foo\nend\nFoo: String\n"); + + add_rbs_source(&mut env, "a.rbs", decls[..2].to_vec()).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[2].clone()]).unwrap_err(); + + assert_duplicate( + &err, + ("b.rbs", &decls[2]), + &[("a.rbs", &decls[0]), ("a.rbs", &decls[1])], + ); + } + + #[test] + fn nested_class_clashing_with_a_const_reports_the_qualified_name() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "module M\n X: Integer\n class X\n end\nend\n"); + + let err = add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap_err(); + + assert_eq!(err.name(), "::M::X"); + assert_duplicate( + &err, + ("a.rbs", nested_decl(&decls[0], 1)), + &[("a.rbs", nested_decl(&decls[0], 0))], + ); + } + + #[test] + fn clash_between_const_and_class_leaves_the_environment_unchanged() { + for (first, second) in [ + ("Foo: String\n", "class Foo\nend\n"), + ("class Foo\nend\n", "Foo: String\n"), + ] { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, &format!("{first}{second}")); + add_rbs_source(&mut env, "a.rbs", decls[..1].to_vec()).unwrap(); + let nodes = env.decl_nodes.len(); + let consts = env.constant_decls.len(); + let classes = env.class_decls.len(); + + add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(env.decl_nodes.len(), nodes); + assert_eq!(env.constant_decls.len(), consts); + assert_eq!(env.class_decls.len(), classes); + } + } + + #[test] + fn constant_entry_finds_class_module_and_constant() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "class A\nend\nmodule B\nend\nC: String\n"); + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [a, b, c, d] = ["::A", "::B", "::C", "::D"].map(|n| type_name(&mut env, n)); + assert!(matches!( + env.constant_entry(a), + Some(ConstantLookup::ClassDecl(ClassDeclEntry::Class(_))) + )); + assert!(matches!( + env.constant_entry(b), + Some(ConstantLookup::ClassDecl(ClassDeclEntry::Module(_))) + )); + let Some(ConstantLookup::Constant(entry)) = env.constant_entry(c) else { + panic!("expected a constant entry"); + }; + assert_eq!(env.decl(entry.decl), &decls[2]); + assert!(env.constant_entry(d).is_none()); + + assert!(env.is_constant_decl(c)); + assert!(!env.is_constant_decl(a)); + assert!(env.is_constant_name(a)); + assert!(env.is_constant_name(b)); + assert!(env.is_constant_name(c)); + assert!(!env.is_constant_name(d)); + } + + #[test] + fn constants_are_registered_in_order_with_their_namespace() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module M\n X: Integer\nend\nB: String\nA: String\n", + ); + add_rbs_source(&mut env, "a.rbs", decls).unwrap(); + + let [x, b, a] = ["::M::X", "::B", "::A"].map(|n| type_name(&mut env, n)); + let names: Vec<_> = env.constant_entries().map(|e| e.name).collect(); + assert_eq!(names, [x, b, a]); + } + + #[test] + fn clash_with_a_module_alias_is_rejected_in_both_directions() { + for (first, second) in [ + ("module Foo = Kernel\n", "Foo: String\n"), + ("module Foo = Kernel\n", "module Foo\nend\n"), + ("module Foo = Kernel\n", "class Foo\nend\n"), + ("Foo: String\n", "module Foo = Kernel\n"), + ("module Foo = Kernel\n", "module Foo = Comparable\n"), + ("module Foo\nend\n", "module Foo = Kernel\n"), + ("class Foo\nend\n", "module Foo = Kernel\n"), + ] { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, &format!("{first}{second}")); + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "::Foo", "{first}{second}"); + assert_duplicate(&err, ("b.rbs", &decls[1]), &[("a.rbs", &decls[0])]); + } + } + + #[test] + fn module_alias_clashing_with_a_reopened_module_reports_every_declaration() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module Foo\nend\nmodule Foo\nend\nmodule Foo = Kernel\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls[..2].to_vec()).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[2].clone()]).unwrap_err(); + + assert_duplicate( + &err, + ("b.rbs", &decls[2]), + &[("a.rbs", &decls[0]), ("a.rbs", &decls[1])], + ); + } + + #[test] + fn nested_module_alias_uses_the_namespace() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "module M\n module A = Kernel\nend\n"); + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [a, top_a] = ["::M::A", "::A"].map(|n| type_name(&mut env, n)); + let Some(ModuleLookup::ModuleAlias(entry)) = env.module_entry(a) else { + panic!("expected a module alias entry"); + }; + assert_eq!(entry.name, a); + assert_eq!(env.decl(entry.decl), nested_decl(&decls[0], 0)); + assert!(env.module_entry(top_a).is_none()); + } + + #[test] + fn module_entry_finds_module_and_module_alias_but_not_constant() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module A\nend\nmodule B = Kernel\nC: String\nclass D\nend\n", + ); + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [a, b, c, d, e] = ["::A", "::B", "::C", "::D", "::E"].map(|n| type_name(&mut env, n)); + assert!(matches!(env.module_entry(a), Some(ModuleLookup::Module(_)))); + let Some(ModuleLookup::ModuleAlias(entry)) = env.module_entry(b) else { + panic!("expected a module alias entry"); + }; + assert_eq!(env.decl(entry.decl), &decls[1]); + assert!(env.module_entry(c).is_none()); + assert!(env.module_entry(d).is_none()); + assert!(env.module_entry(e).is_none()); + + let alias = env.module_entry(b).unwrap(); + assert_eq!(alias.name(), b); + assert_eq!(alias.primary_decl(), entry.decl); + assert_eq!(alias.each_decl().collect::>(), [entry.decl]); + + let module = env.module_entry(a).unwrap(); + assert_eq!(module.name(), a); + assert_eq!(env.decl(module.primary_decl()), &decls[0]); + } + + #[test] + fn constant_entry_finds_module_alias() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "module Foo = Kernel\n"); + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let foo = type_name(&mut env, "::Foo"); + let Some(ConstantLookup::ModuleAlias(entry)) = env.constant_entry(foo) else { + panic!("expected a module alias entry"); + }; + assert_eq!(env.decl(entry.decl), &decls[0]); + assert!(env.is_constant_name(foo)); + assert!(!env.is_constant_decl(foo)); + } + + #[test] + fn module_alias_predicates() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module A = Kernel\nmodule B\nend\ninterface _I\nend\ntype t = Integer\nC: String\n", + ); + add_rbs_source(&mut env, "a.rbs", decls).unwrap(); + + let [a, b, i, t, c, d] = + ["::A", "::B", "::_I", "::t", "::C", "::D"].map(|n| type_name(&mut env, n)); + assert!(env.is_module_alias(a)); + assert!(!env.is_module_alias(b)); + assert!(env.is_module_name(a)); + assert!(env.is_module_name(b)); + assert!(!env.is_module_decl(a)); + for name in [a, b, i, t] { + assert!(env.is_type_name(name)); + } + assert!(!env.is_type_name(c)); + assert!(!env.is_type_name(d)); + } + + #[test] + fn clash_with_a_module_alias_leaves_the_environment_unchanged() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "module Foo = Kernel\nFoo: String\n"); + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + let nodes = env.decl_nodes.len(); + let aliases = env.class_alias_decls.len(); + let consts = env.constant_decls.len(); + + add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(env.decl_nodes.len(), nodes); + assert_eq!(env.class_alias_decls.len(), aliases); + assert_eq!(env.constant_decls.len(), consts); + } + + #[test] + fn insert_class_module_alias() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "module RBS\n module Kernel = ::Kernel\n\n class RbObject = Object\nend\n", + ); + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [kernel, object] = ["::RBS::Kernel", "::RBS::RbObject"].map(|n| type_name(&mut env, n)); + assert!(env.is_module_alias(kernel)); + assert!(!env.is_class_alias(kernel)); + assert!(env.is_class_alias(object)); + assert!(!env.is_module_alias(object)); + assert!(env.is_module_name(object)); + assert!(env.is_type_name(object)); + assert!(env.is_constant_name(object)); + assert!(!env.is_class_decl(object)); + let Some(ConstantLookup::ClassAlias(entry)) = env.constant_entry(object) else { + panic!("expected a class alias entry"); + }; + assert_eq!(env.decl(entry.decl), nested_decl(&decls[0], 1)); + } + + #[test] + fn clash_with_a_class_alias_is_rejected_in_both_directions() { + for (first, second) in [ + ("class Foo = Object\n", "Foo: String\n"), + ("class Foo = Object\n", "class Foo\nend\n"), + ("class Foo = Object\n", "module Foo\nend\n"), + ("class Foo = Object\n", "module Foo = Kernel\n"), + ("class Foo = Object\n", "class Foo = String\n"), + ("Foo: String\n", "class Foo = Object\n"), + ("class Foo\nend\n", "class Foo = Object\n"), + ("module Foo\nend\n", "class Foo = Object\n"), + ("module Foo = Kernel\n", "class Foo = Object\n"), + ] { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, &format!("{first}{second}")); + add_rbs_source(&mut env, "a.rbs", vec![decls[0].clone()]).unwrap(); + + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[1].clone()]).unwrap_err(); + + assert_eq!(err.name(), "::Foo", "{first}{second}"); + assert_duplicate(&err, ("b.rbs", &decls[1]), &[("a.rbs", &decls[0])]); + } + } + + #[test] + fn class_entry_finds_class_and_class_alias_only() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "class A\nend\nclass B = Object\nmodule C\nend\nmodule D = Kernel\nE: String\n", + ); + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [a, b, c, d, e, f] = + ["::A", "::B", "::C", "::D", "::E", "::F"].map(|n| type_name(&mut env, n)); + let class = env.class_entry(a).unwrap(); + assert!(matches!(class, ClassLookup::Class(_))); + assert_eq!(class.name(), a); + assert_eq!(env.decl(class.primary_decl()), &decls[0]); + let alias = env.class_entry(b).unwrap(); + assert!(matches!(alias, ClassLookup::ClassAlias(_))); + assert_eq!(alias.name(), b); + assert_eq!(env.decl(alias.primary_decl()), &decls[1]); + assert_eq!(alias.each_decl().count(), 1); + for name in [c, d, e, f] { + assert!(env.class_entry(name).is_none()); + } + } + + #[test] + fn module_entry_does_not_find_a_class_alias() { + let mut env = Environment::new(); + let decls = parse_decls(&mut env, "class A = Object\n"); + add_rbs_source(&mut env, "a.rbs", decls).unwrap(); + + let a = type_name(&mut env, "::A"); + assert!(env.module_entry(a).is_none()); + } + + #[test] + fn module_class_entry_finds_everything_but_constants() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "class A\nend\nmodule B\nend\nclass C = Object\nmodule D = Kernel\nE: String\n", + ); + add_rbs_source(&mut env, "a.rbs", decls.clone()).unwrap(); + + let [a, b, c, d, e, f] = + ["::A", "::B", "::C", "::D", "::E", "::F"].map(|n| type_name(&mut env, n)); + assert!(matches!( + env.module_class_entry(a), + Some(ModuleClassLookup::ClassDecl(ClassDeclEntry::Class(_))) + )); + assert!(matches!( + env.module_class_entry(b), + Some(ModuleClassLookup::ClassDecl(ClassDeclEntry::Module(_))) + )); + assert!(matches!( + env.module_class_entry(c), + Some(ModuleClassLookup::ClassAlias(_)) + )); + assert!(matches!( + env.module_class_entry(d), + Some(ModuleClassLookup::ModuleAlias(_)) + )); + for (idx, name) in [a, b, c, d].into_iter().enumerate() { + let found = env.module_class_entry(name).unwrap(); + assert_eq!(found.name(), name); + assert_eq!(found.each_decl().count(), 1); + assert_eq!(env.decl(found.primary_decl()), &decls[idx]); + } + assert!(env.module_class_entry(e).is_none()); + assert!(env.module_class_entry(f).is_none()); + } + + #[test] + fn class_alias_clashing_with_a_reopened_class_reports_every_declaration() { + let mut env = Environment::new(); + let decls = parse_decls( + &mut env, + "class Foo\nend\nclass Foo\nend\nclass Foo = Object\n", + ); + + add_rbs_source(&mut env, "a.rbs", decls[..2].to_vec()).unwrap(); + let err = add_rbs_source(&mut env, "b.rbs", vec![decls[2].clone()]).unwrap_err(); + + assert_duplicate( + &err, + ("b.rbs", &decls[2]), + &[("a.rbs", &decls[0]), ("a.rbs", &decls[1])], + ); + } +} diff --git a/rust/ruby-rbs/src/environment/mod.rs b/rust/ruby-rbs/src/environment/mod.rs index fa2cedf10..ce4e1f258 100644 --- a/rust/ruby-rbs/src/environment/mod.rs +++ b/rust/ruby-rbs/src/environment/mod.rs @@ -1,7 +1,22 @@ +mod decl; +mod entry; +mod error; +mod insert; pub mod source; +pub use decl::{Context, DeclId}; +pub use entry::{ + AliasEntry, ClassDeclEntry, ClassDeclKind, ClassEntry, ClassLookup, ConstantLookup, + ModuleClassLookup, ModuleEntry, ModuleLookup, SingleEntry, +}; +pub use error::{DuplicatedDecl, DuplicatedDeclarationError}; pub use source::{Source, SourceKind}; +use indexmap::IndexSet; + +use decl::{ContextNode, DeclNode}; + +use crate::ids::{IdIndexMap, SymbolId, SymbolTag, TypeNameTag}; use crate::interners::Interners; use crate::loader::{EnvironmentLoader, LoadError}; @@ -12,6 +27,14 @@ use crate::loader::{EnvironmentLoader, LoadError}; pub struct Environment { interners: Interners, sources: Vec, + decl_nodes: Vec, + contexts: IndexSet, + class_decls: IdIndexMap, + class_alias_decls: IdIndexMap, + constant_decls: IdIndexMap, + interface_decls: IdIndexMap, + type_alias_decls: IdIndexMap, + global_decls: IdIndexMap>, } impl Environment { @@ -32,10 +55,6 @@ impl Environment { &mut self.interners } - pub(crate) fn add_source(&mut self, source: Source) { - self.sources.push(source); - } - pub fn from_loader(loader: &EnvironmentLoader) -> Result { let mut env = Environment::new(); loader.load(&mut env)?; diff --git a/rust/ruby-rbs/src/ids.rs b/rust/ruby-rbs/src/ids.rs index a72678b4e..efcd444a5 100644 --- a/rust/ruby-rbs/src/ids.rs +++ b/rust/ruby-rbs/src/ids.rs @@ -21,7 +21,7 @@ //! ``` use std::cmp::Ordering; -use std::hash::{Hash, Hasher}; +use std::hash::{BuildHasherDefault, Hash, Hasher}; use std::marker::PhantomData; use std::num::NonZeroU64; @@ -132,6 +132,35 @@ pub enum TypeNameTag {} /// the parent type name's hash and the last segment's hash. pub type TypeName = Id; +/// `Id` is already an xxh3 hash, so this passes it through instead of +/// re-hashing with `SipHash`. +#[derive(Default)] +pub(crate) struct IdHasher(u64); + +impl Hasher for IdHasher { + fn finish(&self) -> u64 { + self.0 + } + + fn write_u64(&mut self, n: u64) { + self.0 = n; + } + + fn write(&mut self, _bytes: &[u8]) { + unreachable!("IdHasher only hashes Id values") + } +} + +/// `HashMap` keyed by `Id` that skips re-hashing; see [`IdHasher`]. +pub(crate) type IdHashMap = std::collections::HashMap, V, BuildHasherDefault>; + +/// Insertion-ordered like Ruby's `Hash`; `Environment` consumers depend on +/// that order for deterministic results. +/// +/// Keyed by `Id` (named by its tag, e.g. `IdIndexMap`) so +/// that `IdHasher` only ever sees the single `write_u64` of an `Id`. +pub(crate) type IdIndexMap = indexmap::IndexMap, V, BuildHasherDefault>; + #[cfg(test)] mod tests { use super::*; diff --git a/rust/ruby-rbs/src/interner.rs b/rust/ruby-rbs/src/interner.rs index 16f0a9785..644e909ea 100644 --- a/rust/ruby-rbs/src/interner.rs +++ b/rust/ruby-rbs/src/interner.rs @@ -27,8 +27,7 @@ //! assert_eq!(global.resolve(b_array), "Array"); //! ``` -use crate::ids::SymbolId; -use std::collections::HashMap; +use crate::ids::{IdHashMap, SymbolId, SymbolTag}; use xxhash_rust::xxh3::xxh3_64; /// Interns strings and assigns each the content-addressed [`SymbolId`] @@ -40,7 +39,7 @@ use xxhash_rust::xxh3::xxh3_64; /// [`merge`]: Self::merge #[derive(Default)] pub struct StringInterner { - map: HashMap>, + map: IdHashMap>, } impl StringInterner { diff --git a/rust/ruby-rbs/src/loader/mod.rs b/rust/ruby-rbs/src/loader/mod.rs index c436f8e5c..dd2b516e9 100644 --- a/rust/ruby-rbs/src/loader/mod.rs +++ b/rust/ruby-rbs/src/loader/mod.rs @@ -4,7 +4,7 @@ use std::io; use std::path::{Path, PathBuf}; use crate::ast::AstConverter; -use crate::environment::{Environment, Source, SourceKind}; +use crate::environment::{DuplicatedDeclarationError, Environment, Source, SourceKind}; use crate::file_finder; use crate::interners::Interners; use crate::node; @@ -14,6 +14,7 @@ use crate::node; pub enum LoadError { Io { path: PathBuf, source: io::Error }, Parse { path: PathBuf, message: String }, + DuplicatedDeclaration(DuplicatedDeclarationError), } impl fmt::Display for LoadError { @@ -25,6 +26,7 @@ impl fmt::Display for LoadError { LoadError::Parse { path, message } => { write!(f, "Syntax error in {}: {}", path.display(), message) } + LoadError::DuplicatedDeclaration(err) => fmt::Display::fmt(err, f), } } } @@ -33,7 +35,10 @@ impl std::error::Error for LoadError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { LoadError::Io { source, .. } => Some(source), - _ => None, + // Transparent: `Display` already delegates to `err`, so returning + // it here would print the same message twice in an error chain. + LoadError::DuplicatedDeclaration(err) => std::error::Error::source(err), + LoadError::Parse { .. } => None, } } } @@ -82,7 +87,6 @@ impl EnvironmentLoader { /// Returns what this call read, in the order it read it, so a caller /// appending to a non-empty `env` still learns what it added. On `Err` the /// sources read before the failure are already in `env`, same as the Ruby - /// implementation adding sources as it walks the directories. pub fn load(&self, env: &mut Environment) -> Result, LoadError> { let mut loaded = Vec::new(); let mut seen_files: HashSet = HashSet::new(); @@ -100,7 +104,8 @@ impl EnvironmentLoader { continue; } let source = parse_one(&path, &kind, env.interners_mut())?; - env.add_source(source); + env.add_source(source) + .map_err(LoadError::DuplicatedDeclaration)?; loaded.push(LoadedFile { path, kind: kind.clone(), diff --git a/rust/ruby-rbs/src/type_name.rs b/rust/ruby-rbs/src/type_name.rs index eadb497d9..9b3ac1756 100644 --- a/rust/ruby-rbs/src/type_name.rs +++ b/rust/ruby-rbs/src/type_name.rs @@ -31,9 +31,8 @@ //! assert_eq!(names.display(foo, &strings), "::RBS::Foo"); //! ``` -use crate::ids::{SymbolId, TypeName}; +use crate::ids::{IdHashMap, SymbolId, TypeName, TypeNameTag}; use crate::interner::StringInterner; -use std::collections::HashMap; use xxhash_rust::xxh3::xxh3_64; /// Fixed sentinel hash for the absolute (`::`) namespace root. Chosen so @@ -123,7 +122,7 @@ impl Entry { /// assert_eq!(names.display(n2, &strings), "::RBS::Foo"); /// ``` pub struct TypeNameInterner { - entries: HashMap, + entries: IdHashMap, relative_root: TypeName, absolute_root: TypeName, } @@ -132,7 +131,7 @@ impl Default for TypeNameInterner { fn default() -> Self { let relative_root = TypeName::from_hash(RELATIVE_ROOT_HASH); let absolute_root = TypeName::from_hash(ABSOLUTE_ROOT_HASH); - let mut entries = HashMap::new(); + let mut entries = IdHashMap::::default(); entries.insert( relative_root, Entry::Relative(RelativeTypeNameEntry { @@ -298,6 +297,11 @@ impl TypeNameInterner { if self.is_absolute(tail) { return tail; } + if let (Some(parent), Some(segment)) = (self.parent(tail), self.last_segment(tail)) + && self.is_root(parent) + { + return self.append(head, segment); + } let tail_segs = self.segments(tail); self.extend(head, tail_segs) } diff --git a/rust/ruby-rbs/tests/loader.rs b/rust/ruby-rbs/tests/loader.rs index 3246dbec1..6cbf19738 100644 --- a/rust/ruby-rbs/tests/loader.rs +++ b/rust/ruby-rbs/tests/loader.rs @@ -127,6 +127,25 @@ fn parse_errors_carry_the_file_path() { )); } +#[test] +fn duplicated_declarations_are_reported_as_load_errors() { + let dir = tree(&[ + ("a.rbs", "interface _I\nend\n"), + ("b.rbs", "interface _I\nend\n"), + ]); + + let loader = EnvironmentLoader::new(None).add_dir(dir.path().to_path_buf()); + let Err(error) = Environment::from_loader(&loader) else { + panic!("expected a duplicated-declaration error"); + }; + + let LoadError::DuplicatedDeclaration(inner) = &error else { + panic!("expected LoadError::DuplicatedDeclaration, got {error:?}"); + }; + assert_eq!(error.to_string(), inner.to_string()); + assert!(std::error::Error::source(&error).is_none()); +} + #[test] fn loaded_sources_carry_converted_declarations_and_directives() { let dir = tree(&[(