feat: support module-qualified constructor patterns in match expressions (issue 3)
Added module: Option<Ident> to Pattern::Constructor, updated parser to handle module.Constructor(args) syntax in patterns, exported ADT constructors from modules, and copied type definitions during module import so types like Shape are usable in importing files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -697,8 +697,9 @@ pub enum Pattern {
|
|||||||
Var(Ident),
|
Var(Ident),
|
||||||
/// Literal: 42, "hello", true
|
/// Literal: 42, "hello", true
|
||||||
Literal(Literal),
|
Literal(Literal),
|
||||||
/// Constructor: Some(x), None, Ok(v)
|
/// Constructor: Some(x), None, Ok(v), module.Constructor(x)
|
||||||
Constructor {
|
Constructor {
|
||||||
|
module: Option<Ident>,
|
||||||
name: Ident,
|
name: Ident,
|
||||||
fields: Vec<Pattern>,
|
fields: Vec<Pattern>,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
|||||||
@@ -333,11 +333,13 @@ mod tests {
|
|||||||
fn test_option_exhaustive() {
|
fn test_option_exhaustive() {
|
||||||
let patterns = vec![
|
let patterns = vec![
|
||||||
Pattern::Constructor {
|
Pattern::Constructor {
|
||||||
|
module: None,
|
||||||
name: make_ident("None"),
|
name: make_ident("None"),
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
span: span(),
|
span: span(),
|
||||||
},
|
},
|
||||||
Pattern::Constructor {
|
Pattern::Constructor {
|
||||||
|
module: None,
|
||||||
name: make_ident("Some"),
|
name: make_ident("Some"),
|
||||||
fields: vec![Pattern::Wildcard(span())],
|
fields: vec![Pattern::Wildcard(span())],
|
||||||
span: span(),
|
span: span(),
|
||||||
@@ -352,6 +354,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_option_missing_none() {
|
fn test_option_missing_none() {
|
||||||
let patterns = vec![Pattern::Constructor {
|
let patterns = vec![Pattern::Constructor {
|
||||||
|
module: None,
|
||||||
name: make_ident("Some"),
|
name: make_ident("Some"),
|
||||||
fields: vec![Pattern::Wildcard(span())],
|
fields: vec![Pattern::Wildcard(span())],
|
||||||
span: span(),
|
span: span(),
|
||||||
@@ -391,11 +394,13 @@ mod tests {
|
|||||||
fn test_result_exhaustive() {
|
fn test_result_exhaustive() {
|
||||||
let patterns = vec![
|
let patterns = vec![
|
||||||
Pattern::Constructor {
|
Pattern::Constructor {
|
||||||
|
module: None,
|
||||||
name: make_ident("Ok"),
|
name: make_ident("Ok"),
|
||||||
fields: vec![Pattern::Wildcard(span())],
|
fields: vec![Pattern::Wildcard(span())],
|
||||||
span: span(),
|
span: span(),
|
||||||
},
|
},
|
||||||
Pattern::Constructor {
|
Pattern::Constructor {
|
||||||
|
module: None,
|
||||||
name: make_ident("Err"),
|
name: make_ident("Err"),
|
||||||
fields: vec![Pattern::Wildcard(span())],
|
fields: vec![Pattern::Wildcard(span())],
|
||||||
span: span(),
|
span: span(),
|
||||||
|
|||||||
@@ -772,12 +772,22 @@ impl Formatter {
|
|||||||
Pattern::Wildcard(_) => "_".to_string(),
|
Pattern::Wildcard(_) => "_".to_string(),
|
||||||
Pattern::Var(ident) => ident.name.clone(),
|
Pattern::Var(ident) => ident.name.clone(),
|
||||||
Pattern::Literal(lit) => self.format_literal(lit),
|
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() {
|
if fields.is_empty() {
|
||||||
name.name.clone()
|
format!("{}{}", prefix, name.name)
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"{}({})",
|
"{}{}({})",
|
||||||
|
prefix,
|
||||||
name.name,
|
name.name,
|
||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -279,6 +279,12 @@ impl ModuleLoader {
|
|||||||
}
|
}
|
||||||
Declaration::Type(t) if t.visibility == Visibility::Public => {
|
Declaration::Type(t) if t.visibility == Visibility::Public => {
|
||||||
exports.insert(t.name.name.clone());
|
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) => {
|
Declaration::Effect(e) => {
|
||||||
// Effects are always exported
|
// Effects are always exported
|
||||||
|
|||||||
@@ -1922,9 +1922,27 @@ impl Parser {
|
|||||||
TokenKind::Ident(name) => {
|
TokenKind::Ident(name) => {
|
||||||
// Check if it starts with uppercase (constructor) or lowercase (variable)
|
// Check if it starts with uppercase (constructor) or lowercase (variable)
|
||||||
if name.chars().next().map_or(false, |c| c.is_uppercase()) {
|
if name.chars().next().map_or(false, |c| c.is_uppercase()) {
|
||||||
self.parse_constructor_pattern()
|
self.parse_constructor_pattern_with_module(None)
|
||||||
} else {
|
} else {
|
||||||
let ident = self.parse_ident()?;
|
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))
|
Ok(Pattern::Var(ident))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1934,8 +1952,14 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_constructor_pattern(&mut self) -> Result<Pattern, ParseError> {
|
fn parse_constructor_pattern_with_module(
|
||||||
let start = self.current_span();
|
&mut self,
|
||||||
|
module: Option<Ident>,
|
||||||
|
) -> Result<Pattern, ParseError> {
|
||||||
|
let start = module
|
||||||
|
.as_ref()
|
||||||
|
.map(|m| m.span)
|
||||||
|
.unwrap_or_else(|| self.current_span());
|
||||||
let name = self.parse_ident()?;
|
let name = self.parse_ident()?;
|
||||||
|
|
||||||
if self.check(TokenKind::LParen) {
|
if self.check(TokenKind::LParen) {
|
||||||
@@ -1952,10 +1976,16 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
self.expect(TokenKind::RParen)?;
|
self.expect(TokenKind::RParen)?;
|
||||||
let span = start.merge(self.previous_span());
|
let span = start.merge(self.previous_span());
|
||||||
Ok(Pattern::Constructor { name, fields, span })
|
|
||||||
} else {
|
|
||||||
let span = name.span;
|
|
||||||
Ok(Pattern::Constructor {
|
Ok(Pattern::Constructor {
|
||||||
|
module,
|
||||||
|
name,
|
||||||
|
fields,
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let span = start.merge(name.span);
|
||||||
|
Ok(Pattern::Constructor {
|
||||||
|
module,
|
||||||
name,
|
name,
|
||||||
fields: Vec::new(),
|
fields: Vec::new(),
|
||||||
span,
|
span,
|
||||||
|
|||||||
@@ -981,6 +981,13 @@ impl TypeChecker {
|
|||||||
if !fields.is_empty() {
|
if !fields.is_empty() {
|
||||||
self.env.bind(&name, TypeScheme::mono(Type::Record(fields)));
|
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 => {
|
ImportKind::Direct => {
|
||||||
// Import a specific name directly
|
// Import a specific name directly
|
||||||
@@ -2476,7 +2483,7 @@ impl TypeChecker {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
Pattern::Constructor { name, fields, span } => {
|
Pattern::Constructor { name, fields, span, .. } => {
|
||||||
// Look up constructor
|
// Look up constructor
|
||||||
// For now, handle Option specially
|
// For now, handle Option specially
|
||||||
match name.name.as_str() {
|
match name.name.as_str() {
|
||||||
|
|||||||
Reference in New Issue
Block a user