diff --git a/src/ast.rs b/src/ast.rs index 9df0434..08392b0 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -697,8 +697,9 @@ pub enum Pattern { Var(Ident), /// Literal: 42, "hello", true Literal(Literal), - /// Constructor: Some(x), None, Ok(v) + /// Constructor: Some(x), None, Ok(v), module.Constructor(x) Constructor { + module: Option, name: Ident, fields: Vec, span: Span, diff --git a/src/exhaustiveness.rs b/src/exhaustiveness.rs index a101522..112ae9c 100644 --- a/src/exhaustiveness.rs +++ b/src/exhaustiveness.rs @@ -333,11 +333,13 @@ mod tests { fn test_option_exhaustive() { let patterns = vec![ Pattern::Constructor { + module: None, name: make_ident("None"), fields: vec![], span: span(), }, Pattern::Constructor { + module: None, name: make_ident("Some"), fields: vec![Pattern::Wildcard(span())], span: span(), @@ -352,6 +354,7 @@ mod tests { #[test] fn test_option_missing_none() { let patterns = vec![Pattern::Constructor { + module: None, name: make_ident("Some"), fields: vec![Pattern::Wildcard(span())], span: span(), @@ -391,11 +394,13 @@ mod tests { fn test_result_exhaustive() { let patterns = vec![ Pattern::Constructor { + module: None, name: make_ident("Ok"), fields: vec![Pattern::Wildcard(span())], span: span(), }, Pattern::Constructor { + module: None, name: make_ident("Err"), fields: vec![Pattern::Wildcard(span())], span: span(), diff --git a/src/formatter.rs b/src/formatter.rs index b6ff1e0..42e734d 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -772,12 +772,22 @@ impl Formatter { Pattern::Wildcard(_) => "_".to_string(), Pattern::Var(ident) => ident.name.clone(), Pattern::Literal(lit) => self.format_literal(lit), - Pattern::Constructor { name, fields, .. } => { + Pattern::Constructor { + module, + name, + fields, + .. + } => { + let prefix = match module { + Some(m) => format!("{}.", m.name), + None => String::new(), + }; if fields.is_empty() { - name.name.clone() + format!("{}{}", prefix, name.name) } else { format!( - "{}({})", + "{}{}({})", + prefix, name.name, fields .iter() diff --git a/src/modules.rs b/src/modules.rs index 2956b4b..4943390 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -279,6 +279,12 @@ impl ModuleLoader { } Declaration::Type(t) if t.visibility == Visibility::Public => { exports.insert(t.name.name.clone()); + // Also export constructors for ADT types + if let crate::ast::TypeDef::Enum(variants) = &t.definition { + for variant in variants { + exports.insert(variant.name.name.clone()); + } + } } Declaration::Effect(e) => { // Effects are always exported diff --git a/src/parser.rs b/src/parser.rs index 2655fd5..608d8b9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1922,9 +1922,27 @@ impl Parser { TokenKind::Ident(name) => { // Check if it starts with uppercase (constructor) or lowercase (variable) if name.chars().next().map_or(false, |c| c.is_uppercase()) { - self.parse_constructor_pattern() + self.parse_constructor_pattern_with_module(None) } else { let ident = self.parse_ident()?; + // Check for module-qualified constructor: module.Constructor + if self.check(TokenKind::Dot) { + // Peek ahead to see if next is an uppercase identifier + let dot_pos = self.pos; + self.advance(); // skip dot + if let TokenKind::Ident(next_name) = self.peek_kind() { + if next_name + .chars() + .next() + .map_or(false, |c| c.is_uppercase()) + { + return self + .parse_constructor_pattern_with_module(Some(ident)); + } + } + // Not a module-qualified constructor, backtrack + self.pos = dot_pos; + } Ok(Pattern::Var(ident)) } } @@ -1934,8 +1952,14 @@ impl Parser { } } - fn parse_constructor_pattern(&mut self) -> Result { - let start = self.current_span(); + fn parse_constructor_pattern_with_module( + &mut self, + module: Option, + ) -> Result { + let start = module + .as_ref() + .map(|m| m.span) + .unwrap_or_else(|| self.current_span()); let name = self.parse_ident()?; if self.check(TokenKind::LParen) { @@ -1952,10 +1976,16 @@ impl Parser { } self.expect(TokenKind::RParen)?; let span = start.merge(self.previous_span()); - Ok(Pattern::Constructor { name, fields, span }) - } else { - let span = name.span; Ok(Pattern::Constructor { + module, + name, + fields, + span, + }) + } else { + let span = start.merge(name.span); + Ok(Pattern::Constructor { + module, name, fields: Vec::new(), span, diff --git a/src/typechecker.rs b/src/typechecker.rs index 049bef9..7f9f8cb 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -981,6 +981,13 @@ impl TypeChecker { if !fields.is_empty() { self.env.bind(&name, TypeScheme::mono(Type::Record(fields))); } + + // Also copy type definitions so imported types are usable + for (type_name, type_def) in &module_checker.env.types { + if !self.env.types.contains_key(type_name) { + self.env.types.insert(type_name.clone(), type_def.clone()); + } + } } ImportKind::Direct => { // Import a specific name directly @@ -2476,7 +2483,7 @@ impl TypeChecker { Vec::new() } - Pattern::Constructor { name, fields, span } => { + Pattern::Constructor { name, fields, span, .. } => { // Look up constructor // For now, handle Option specially match name.name.as_str() {