From b3aed607cb2a386a2a899373258e323461cb086a Mon Sep 17 00:00:00 2001 From: Thompson <140930314+Godbrand0@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:01:57 -0500 Subject: [PATCH] feat(ast): add visibility support for spec definitions Previously, all spec definitions were treated as public regardless of visibility modifiers. This change updates the Symbol enum to track spec visibility through a new SpecInfo struct, allowing specs to be properly marked as public or private in the symbol table and type checking process. The AST builder now correctly extracts visibility from spec nodes, and the symbol table registration API updated to accept visibility information. Tests are added to verify both public and private spec visibility parsing. --- .cargo/config.toml | 1 + core/ast/src/builder.rs | 2 +- core/type-checker/src/symbol_table.rs | 32 +++++++++++++++++++-------- core/type-checker/src/type_checker.rs | 4 ++-- tests/src/ast/builder_features.rs | 19 +++++++++++++++- 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9583e86..42ffed4 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,6 +9,7 @@ rustflags = ["-L", "native=external/lib/linux"] [target.'cfg(target_os = "linux")'.env] LD_LIBRARY_PATH = { value = "external/lib/linux", relative = true } +LLVM_SYS_211_PREFIX = "/usr/lib/llvm-21" [target.x86_64-pc-windows-gnu] linker = "x86_64-w64-mingw32-gcc" diff --git a/core/ast/src/builder.rs b/core/ast/src/builder.rs index 426b005..79fbc56 100644 --- a/core/ast/src/builder.rs +++ b/core/ast/src/builder.rs @@ -241,7 +241,7 @@ impl<'a> Builder<'a> { let node = Rc::new(SpecDefinition::new( id, - Visibility::default(), + Self::get_visibility(node), name, definitions, location, diff --git a/core/type-checker/src/symbol_table.rs b/core/type-checker/src/symbol_table.rs index 132508f..50d7114 100644 --- a/core/type-checker/src/symbol_table.rs +++ b/core/type-checker/src/symbol_table.rs @@ -165,10 +165,18 @@ pub(crate) enum Symbol { TypeAlias(TypeInfo), Struct(StructInfo), Enum(EnumInfo), - Spec(String), + Spec(SpecInfo), Function(FuncInfo), } +/// Information about a spec definition. +#[derive(Debug, Clone)] +pub(crate) struct SpecInfo { + pub(crate) name: String, + pub(crate) visibility: Visibility, + pub(crate) definition_scope_id: u32, +} + impl Symbol { #[allow(dead_code)] #[must_use = "discarding the name has no effect"] @@ -177,7 +185,7 @@ impl Symbol { Symbol::TypeAlias(ti) => ti.to_string(), Symbol::Struct(info) => info.name.clone(), Symbol::Enum(info) => info.name.clone(), - Symbol::Spec(name) => name.clone(), + Symbol::Spec(info) => info.name.clone(), Symbol::Function(sig) => sig.name.clone(), } } @@ -221,8 +229,8 @@ impl Symbol { kind: crate::type_info::TypeInfoKind::Enum(info.name.clone()), type_params: vec![], }), - Symbol::Spec(name) => Some(TypeInfo { - kind: crate::type_info::TypeInfoKind::Spec(name.clone()), + Symbol::Spec(info) => Some(TypeInfo { + kind: crate::type_info::TypeInfoKind::Spec(info.name.clone()), type_params: vec![], }), Symbol::Function(_) => None, @@ -232,14 +240,14 @@ impl Symbol { /// Check if this symbol has public visibility. /// /// Structs, Enums, and Functions respect their visibility field. - /// Type aliases and Specs are currently treated as public. + /// Type aliases and Specs respect their visibility field. #[must_use = "this is a pure check with no side effects"] pub(crate) fn is_public(&self) -> bool { match self { Symbol::TypeAlias(_) => true, Symbol::Struct(info) => matches!(info.visibility, Visibility::Public), Symbol::Enum(info) => matches!(info.visibility, Visibility::Public), - Symbol::Spec(_) => true, + Symbol::Spec(info) => matches!(info.visibility, Visibility::Public), Symbol::Function(sig) => matches!(sig.visibility, Visibility::Public), } } @@ -565,11 +573,17 @@ impl SymbolTable { } } - pub(crate) fn register_spec(&mut self, name: &str) -> anyhow::Result<()> { + pub(crate) fn register_spec(&mut self, name: &str, visibility: Visibility) -> anyhow::Result<()> { if let Some(scope) = &self.current_scope { + let scope_id = scope.borrow().id; + let spec_info = SpecInfo { + name: name.to_string(), + visibility, + definition_scope_id: scope_id, + }; scope .borrow_mut() - .insert_symbol(name, Symbol::Spec(name.to_string())) + .insert_symbol(name, Symbol::Spec(spec_info)) } else { bail!("No active scope to register spec") } @@ -883,7 +897,7 @@ impl SymbolTable { self.register_enum(&e.name(), &variants, e.visibility.clone())?; } Definition::Spec(sp) => { - self.register_spec(&sp.name())?; + self.register_spec(&sp.name(), sp.visibility.clone())?; } Definition::Function(f) => { let type_params = f diff --git a/core/type-checker/src/type_checker.rs b/core/type-checker/src/type_checker.rs index 82de1b2..64953ee 100644 --- a/core/type-checker/src/type_checker.rs +++ b/core/type-checker/src/type_checker.rs @@ -236,7 +236,7 @@ impl TypeChecker { } Definition::Spec(spec_definition) => { self.symbol_table - .register_spec(&spec_definition.name()) + .register_spec(&spec_definition.name(), spec_definition.visibility.clone()) .unwrap_or_else(|_| { self.errors.push(TypeCheckError::RegistrationFailed { kind: RegistrationKind::Spec, @@ -1673,7 +1673,7 @@ impl TypeChecker { } Definition::Spec(spec_definition) => { self.symbol_table - .register_spec(&spec_definition.name()) + .register_spec(&spec_definition.name(), spec_definition.visibility.clone()) .unwrap_or_else(|_| { self.errors.push(TypeCheckError::RegistrationFailed { kind: RegistrationKind::Spec, diff --git a/tests/src/ast/builder_features.rs b/tests/src/ast/builder_features.rs index 552b95a..150b81b 100644 --- a/tests/src/ast/builder_features.rs +++ b/tests/src/ast/builder_features.rs @@ -993,7 +993,24 @@ fn test_parse_spec_definition_visibility_private() { assert_eq!( spec.visibility, Visibility::Private, - "Spec definitions should always be private (no grammar support for pub)" + "Spec without pub should have Private visibility" + ); + } else { + panic!("Expected spec definition"); + } +} + +#[test] +fn test_parse_spec_definition_visibility_public() { + let source = r#"pub spec MySpec { fn verify() -> bool { return true; } }"#; + let arena = build_ast(source.to_string()); + let specs = arena.filter_nodes(|node| matches!(node, AstNode::Definition(Definition::Spec(_)))); + assert_eq!(specs.len(), 1, "Should find 1 spec definition"); + if let AstNode::Definition(Definition::Spec(spec)) = &specs[0] { + assert_eq!( + spec.visibility, + Visibility::Public, + "Spec with pub should have Public visibility" ); } else { panic!("Expected spec definition");