use super::{SemanticAnalyzer, SemanticError, SymbolKind, Type, format_type};
use crate::ast::{
ExpressionKind, ExpressionNode, ImportSpec, PatternNode, StatementKind, StatementNode, TypeNode,
};
use crate::diagnostic::Files;
use crate::lexer::Span;
impl SemanticAnalyzer {
pub(super) fn analyze_block(
&mut self,
stmts: &[StatementNode],
mut files: Option<&mut Files>,
) -> Result<(), SemanticError> {
for stmt in stmts {
if let StatementKind::Function(func) = &stmt.kind {
let param_types = func
.params
.iter()
.map(|p| self.resolve_type(&p.type_))
.collect::<Result<Vec<_>, _>>()?;
let return_type = self.resolve_type(&func.return_type)?;
let mut func_type = Type::Function {
params: param_types,
returns: Box::new(return_type),
default_count: 0,
};
for (type_param_name, _) in &func.type_params {
let var = Type::Variable(type_param_name.clone());
func_type = self.substitute_type_param(&func_type, type_param_name, &var);
}
let default_count = func
.params
.iter()
.filter(|p| p.default_value.is_some())
.count();
self.symbol_table.add_symbol(
&func.name,
self.make_function_symbol(
stmt.span,
func_type,
&func.type_params,
default_count,
),
)?;
}
}
for stmt in stmts {
self.analyze_statement(stmt, files.as_deref_mut())?;
}
Ok(())
}
fn analyze_auto_decl_statement(
&mut self,
name: &str,
expr: &ExpressionNode,
span: Span,
) -> Result<(), SemanticError> {
self.analyze_expression(expr)?;
let expr_type = self.get_expression_type(expr)?;
if matches!(
expr_type,
Type::EmptyList | Type::EmptyMap | Type::EmptySet | Type::EmptySetOrMap
) {
let collection_type = match expr_type {
Type::EmptyList => "list",
Type::EmptyMap => "map",
Type::EmptySet => "set",
Type::EmptySetOrMap => "set or map",
_ => unreachable!(),
};
return Err(SemanticError::with_help(
format!("Cannot infer type for empty {} literal", collection_type),
expr.span,
format!(
"Use an explicit type annotation, e.g. {}<int> myVar = {{}}",
collection_type
),
));
}
if Self::type_contains_empty_set_or_map(&expr_type) {
return Err(SemanticError::with_help(
"Cannot infer type for expression containing empty set/map literal".to_string(),
expr.span,
"Use explicit type annotations for all empty collection literals.",
));
}
self.symbol_table.add_symbol(
name,
Self::make_symbol(SymbolKind::Variable, span, Some(expr_type)),
)?;
Ok(())
}
fn type_contains_empty_set_or_map(ty: &Type) -> bool {
match ty {
Type::EmptySetOrMap => true,
Type::Map(key_type, value_type) => {
Self::type_contains_empty_set_or_map(key_type)
|| Self::type_contains_empty_set_or_map(value_type)
}
Type::Set(elem_type) => Self::type_contains_empty_set_or_map(elem_type),
Type::List(elem_type) => Self::type_contains_empty_set_or_map(elem_type),
Type::Optional(inner) => Self::type_contains_empty_set_or_map(inner),
Type::Result(ok, err) => {
Self::type_contains_empty_set_or_map(ok)
|| Self::type_contains_empty_set_or_map(err)
}
Type::Tuple(left, right) => {
Self::type_contains_empty_set_or_map(left)
|| Self::type_contains_empty_set_or_map(right)
}
_ => false,
}
}
fn analyze_typed_decl_statement(
&mut self,
name: &str,
type_node: &TypeNode,
expr: &ExpressionNode,
span: Span,
) -> Result<(), SemanticError> {
let declared_type = self.resolve_type(type_node)?;
self.analyze_expression(expr)?;
self.resolve_empty_collection_types(&declared_type, expr)?;
let expr_type = self.get_expression_type(expr)?;
self.check_type_compatibility(&declared_type, &expr_type, expr.span)?;
self.symbol_table.add_symbol(
name,
Self::make_symbol(SymbolKind::Variable, span, Some(declared_type)),
)?;
Ok(())
}
pub(super) fn resolve_empty_collection_types(
&mut self,
expected_type: &Type,
expr: &ExpressionNode,
) -> Result<(), SemanticError> {
match &expr.kind {
ExpressionKind::SetOrMapLiteral(elements) if elements.is_empty() => {
self.resolve_empty_set_or_map(expected_type, expr.span);
}
ExpressionKind::MapLiteral { entries, .. } => {
self.resolve_map_literal_children(expected_type, entries)?;
}
ExpressionKind::SetOrMapLiteral(elements) => {
self.resolve_typed_collection_elements(expected_type, elements)?;
}
ExpressionKind::ListLiteral(elements) => {
self.resolve_typed_collection_elements(expected_type, elements)?;
}
_ => {}
}
Ok(())
}
fn resolve_empty_set_or_map(&mut self, expected_type: &Type, span: Span) {
let resolved = match expected_type {
Type::Map(_, _) => Type::EmptyMap,
Type::Set(_) => Type::EmptySet,
_ => return,
};
self.expression_type_overrides.insert(span, resolved);
}
fn resolve_map_literal_children(
&mut self,
expected_type: &Type,
entries: &[(ExpressionNode, ExpressionNode)],
) -> Result<(), SemanticError> {
if let Type::Map(_, value_type) = expected_type {
for (_, value_expr) in entries {
self.resolve_empty_collection_types(value_type, value_expr)?;
}
}
Ok(())
}
fn resolve_typed_collection_elements(
&mut self,
expected_type: &Type,
elements: &[ExpressionNode],
) -> Result<(), SemanticError> {
let elem_type = match expected_type {
Type::Set(elem) => elem,
Type::List(elem) => elem,
_ => return Ok(()),
};
for element in elements {
self.resolve_empty_collection_types(elem_type, element)?;
}
Ok(())
}
fn analyze_const_decl_statement(
&mut self,
name: &str,
type_node: &TypeNode,
expr: &ExpressionNode,
span: Span,
) -> Result<(), SemanticError> {
let declared_type = self.resolve_type(type_node)?;
self.analyze_expression(expr)?;
self.resolve_empty_collection_types(&declared_type, expr)?;
let expr_type = self.get_expression_type(expr)?;
self.check_type_compatibility(&declared_type, &expr_type, expr.span)?;
self.symbol_table.add_symbol(
name,
Self::make_symbol(SymbolKind::Constant, span, Some(declared_type)),
)?;
Ok(())
}
fn analyze_if_statement(
&mut self,
cond: &ExpressionNode,
then_block: &[StatementNode],
else_block: &Option<Vec<StatementNode>>,
mut files: Option<&mut Files>,
) -> Result<(), SemanticError> {
self.analyze_expression(cond)?;
self.symbol_table.push_scope()?;
self.analyze_block(then_block, files.as_deref_mut())?;
self.symbol_table.pop_scope()?;
if let Some(else_block) = else_block {
self.symbol_table.push_scope()?;
self.analyze_block(else_block, files)?;
self.symbol_table.pop_scope()?;
}
Ok(())
}
fn analyze_for_statement(
&mut self,
var: &str,
iter: &ExpressionNode,
body: &[StatementNode],
span: Span,
files: Option<&mut Files>,
) -> Result<(), SemanticError> {
self.symbol_table.push_scope()?;
self.analyze_expression(iter)?;
let iter_type = self.get_expression_type(iter)?;
let var_type = match iter_type {
Type::List(elem_type) => *elem_type,
_ => {
return Err(SemanticError::with_help(
format!("Cannot iterate over type {}", format_type(&iter_type)),
iter.span,
"The 'for' loop can only iterate over list types. Use .to_list() for sets. For maps, use .get_keys(), .get_values(), or .get_pairs() to get a list. For numeric ranges, use range(start, end).",
));
}
};
self.symbol_table.add_symbol(
var,
Self::make_symbol(SymbolKind::Variable, span, Some(var_type)),
)?;
self.analyze_block(body, files)?;
self.symbol_table.pop_scope()?;
Ok(())
}
fn analyze_while_statement(
&mut self,
cond: &ExpressionNode,
body: &[StatementNode],
files: Option<&mut Files>,
) -> Result<(), SemanticError> {
self.analyze_expression(cond)?;
self.symbol_table.push_scope()?;
self.analyze_block(body, files)?;
self.symbol_table.pop_scope()?;
Ok(())
}
fn collect_match_arm_returns(
&mut self,
arm: &crate::ast::MatchArm,
expr_span: Span,
expecting_return: bool,
arm_return_types: &mut Vec<(Type, Span)>,
) -> Result<(), SemanticError> {
if !expecting_return {
return Ok(());
}
let mut has_return = false;
for stmt in &arm.body {
if let StatementKind::Return(Some(ret_expr)) = &stmt.kind {
let ret_type = self.get_expression_type(ret_expr)?;
arm_return_types.push((ret_type, stmt.span));
has_return = true;
break;
}
if let StatementKind::Return(None) = &stmt.kind {
has_return = true;
}
}
if has_return {
return Ok(());
}
Err(SemanticError::with_help(
"Match arm must return a value in a function with non-void return type",
arm.body.first().map(|s| s.span).unwrap_or(expr_span),
"Add a return statement to this match arm",
))
}
fn validate_match_arm_return_types(
&self,
arm_return_types: &[(Type, Span)],
) -> Result<(), SemanticError> {
if arm_return_types.is_empty() {
return Ok(());
}
let (first_type, _) = &arm_return_types[0];
for (i, (arm_type, arm_span)) in arm_return_types.iter().enumerate().skip(1) {
if self
.check_type_compatibility(first_type, arm_type, *arm_span)
.is_err()
{
return Err(SemanticError::with_help(
format!(
"Match arms have incompatible return types: first arm returns '{}', but arm {} returns '{}'",
format_type(first_type),
i + 1,
format_type(arm_type)
),
*arm_span,
"All match arms must return the same type when used as an expression or in a returning context",
));
}
}
Ok(())
}
fn analyze_match_statement(
&mut self,
expr: &ExpressionNode,
arms: &[crate::ast::MatchArm],
mut files: Option<&mut Files>,
) -> Result<(), SemanticError> {
self.analyze_expression(expr)?;
let expr_type = self.get_expression_type(expr)?;
let expecting_return = self.current_return_type.is_some()
&& !matches!(self.current_return_type, Some(Type::Void));
let mut arm_return_types = Vec::new();
for arm in arms {
self.symbol_table.push_scope()?;
self.set_pattern_types(&arm.pattern, &expr_type, expr.span)?;
self.analyze_pattern(&arm.pattern)?;
if let Some(guard) = &arm.guard {
self.analyze_expression(guard)?;
}
self.analyze_block(&arm.body, files.as_deref_mut())?;
self.collect_match_arm_returns(
arm,
expr.span,
expecting_return,
&mut arm_return_types,
)?;
self.symbol_table.pop_scope()?;
}
self.validate_match_arm_return_types(&arm_return_types)?;
self.check_match_exhaustiveness(&expr_type, arms, expr.span)
}
fn analyze_return_with_value(&mut self, expr: &ExpressionNode) -> Result<(), SemanticError> {
if self.current_return_type.is_none() {
return Err(SemanticError::with_help(
"Cannot use 'return' outside of a function",
expr.span,
"'return' can only be used inside a function body",
));
}
self.analyze_expression(expr)?;
if matches!(self.current_return_type, Some(Type::Void)) {
return Err(SemanticError::with_help(
"Cannot return a value from a void function",
expr.span,
"This function is declared as 'returns void'. Either remove the return value or change the function's return type.",
));
}
if let Some(expected_type) = self.current_return_type.clone() {
let actual_type = self.get_expression_type(expr)?;
self.check_type_compatibility(&expected_type, &actual_type, expr.span)?;
}
Ok(())
}
fn analyze_return_without_value(&mut self, span: Span) -> Result<(), SemanticError> {
if self.current_return_type.is_none() {
return Err(SemanticError::with_help(
"Cannot use 'return' outside of a function",
span,
"'return' can only be used inside a function body",
));
}
if !matches!(self.current_return_type, Some(Type::Void))
&& let Some(expected_type) = &self.current_return_type
{
return Err(SemanticError::with_help(
format!(
"Missing return value; function expects '{}', but return has no value",
format_type(expected_type)
),
span,
format!(
"Add a value after 'return' that matches the function's return type '{}'. Example: return some_value",
format_type(expected_type)
),
));
}
Ok(())
}
pub(super) fn analyze_import_statement(
&mut self,
module_path: &str,
spec: &ImportSpec,
span: Span,
files: Option<&mut Files>,
) -> Result<(), SemanticError> {
if module_path == "std" || module_path.starts_with("std.") {
self.handle_std_import(module_path, spec, span, files)?;
return Ok(());
}
let resolver = self
.module_resolver
.clone()
.ok_or_else(|| SemanticError::new("Module resolver not available", span))?;
let (has_file, has_directory) = resolver.borrow().check_module_path(module_path);
if has_file && has_directory {
return Err(SemanticError::with_help(
format!("Ambiguous import: '{}'", module_path),
span,
format!(
"Both {}.mux and {}/ directory exist. Please remove one.",
module_path.replace('.', "/"),
module_path.replace('.', "/")
),
));
}
let files = files.ok_or_else(|| {
SemanticError::new(
"Files registry must be available for import processing",
span,
)
})?;
if has_directory {
self.handle_directory_import(module_path, spec, span, resolver, files)?;
return Ok(());
}
let module_nodes = resolver
.borrow_mut()
.resolve_import_path(module_path, self.current_file.as_deref(), files)
.map_err(|e| {
SemanticError::with_help(
format!("Failed to import module '{}'", module_path),
span,
e.to_string(),
)
})?;
let mut module_analyzer = SemanticAnalyzer::new_for_module(resolver.clone());
module_analyzer.set_current_file(std::path::PathBuf::from(
module_path.replace('.', "/") + ".mux",
));
let errors = module_analyzer.analyze(&module_nodes, Some(files));
if !errors.is_empty() {
let error_messages: Vec<String> = errors.iter().map(|e| e.message.clone()).collect();
return Err(SemanticError::with_help(
format!("Errors in imported module '{}'", module_path),
span,
format!(
"Fix the following errors in '{}':\n {}",
module_path,
error_messages.join("\n ")
),
));
}
let module_symbols =
self.filter_module_export_symbols(&module_analyzer.symbol_table.all_symbols);
self.required_runtime_features
.extend(module_analyzer.required_runtime_features.iter().cloned());
match spec {
ImportSpec::Module { alias } => {
if let Some(namespace) = alias {
self.add_module_namespace(namespace, module_symbols, module_path, span)?;
}
}
ImportSpec::Item { item, alias } => {
let symbol_name = alias.as_ref().unwrap_or(item);
self.import_single_symbol(&module_symbols, item, symbol_name, module_path, span)?;
}
ImportSpec::Items { items } => {
for (item, alias) in items {
let symbol_name = alias.as_ref().unwrap_or(item);
self.import_single_symbol(
&module_symbols,
item,
symbol_name,
module_path,
span,
)?;
}
}
ImportSpec::Wildcard => {
self.import_all_symbols(&module_symbols, module_path, span)?;
}
}
resolver
.borrow_mut()
.cache_module(module_path, module_nodes.clone());
resolver.borrow_mut().finish_import(module_path);
self.all_module_asts
.insert(module_path.to_string(), module_nodes);
if !self.module_dependencies.contains(&module_path.to_string()) {
self.module_dependencies.push(module_path.to_string());
}
Ok(())
}
pub(super) fn analyze_statement(
&mut self,
stmt: &StatementNode,
mut files: Option<&mut Files>,
) -> Result<(), SemanticError> {
match &stmt.kind {
StatementKind::AutoDecl(name, _, expr) => {
self.analyze_auto_decl_statement(name, expr, stmt.span)?;
}
StatementKind::TypedDecl(name, type_node, expr) => {
self.analyze_typed_decl_statement(name, type_node, expr, stmt.span)?;
}
StatementKind::Expression(expr) => {
self.analyze_expression(expr)?;
}
StatementKind::Block(stmts) => {
self.symbol_table.push_scope()?;
self.analyze_block(stmts, files.as_deref_mut())?;
self.symbol_table.pop_scope()?;
}
StatementKind::ConstDecl(name, type_node, expr) => {
self.analyze_const_decl_statement(name, type_node, expr, stmt.span)?;
}
StatementKind::If {
cond,
then_block,
else_block,
} => {
self.analyze_if_statement(cond, then_block, else_block, files.as_deref_mut())?;
}
StatementKind::For {
var, iter, body, ..
} => {
self.analyze_for_statement(var, iter, body, stmt.span, files.as_deref_mut())?;
}
StatementKind::While { cond, body } => {
self.analyze_while_statement(cond, body, files.as_deref_mut())?;
}
StatementKind::Match { expr, arms } => {
self.analyze_match_statement(expr, arms, files.as_deref_mut())?;
}
StatementKind::Return(Some(expr)) => {
self.analyze_return_with_value(expr)?;
}
StatementKind::Return(None) => {
self.analyze_return_without_value(stmt.span)?;
}
StatementKind::Import { module_path, spec } => {
if !self.hoisted_import_spans.contains(&stmt.span) {
self.analyze_import_statement(module_path, spec, stmt.span, files)?;
}
}
StatementKind::Function(func) => {
self.analyze_function(func, None)?;
}
_ => {}
}
Ok(())
}
fn check_match_exhaustiveness(
&self,
expr_type: &Type,
arms: &[crate::ast::MatchArm],
expr_span: Span,
) -> Result<(), SemanticError> {
match expr_type {
Type::Result(_, _) => self.check_result_exhaustiveness(arms, expr_span),
Type::Named(type_name, _) => {
self.check_named_enum_exhaustiveness(type_name, arms, expr_type, expr_span)
}
Type::Optional(_) => self.check_optional_exhaustiveness(arms, expr_span),
_ => self.require_wildcard_pattern(arms, expr_type, expr_span),
}
}
fn check_result_exhaustiveness(
&self,
arms: &[crate::ast::MatchArm],
expr_span: Span,
) -> Result<(), SemanticError> {
let has_ok = arms.iter().any(|arm| {
arm.guard.is_none()
&& matches!(&arm.pattern, PatternNode::EnumVariant { name, .. } if name == "ok")
});
let has_err = arms.iter().any(|arm| {
arm.guard.is_none()
&& matches!(&arm.pattern, PatternNode::EnumVariant { name, .. } if name == "err")
});
let has_wildcard = arms
.iter()
.any(|arm| matches!(&arm.pattern, PatternNode::Wildcard));
if has_wildcard || (has_ok && has_err) {
return Ok(());
}
let mut missing = Vec::new();
if !has_ok {
missing.push("ok");
}
if !has_err {
missing.push("err");
}
Err(SemanticError::with_help(
format!(
"Non-exhaustive match: missing pattern{} for Result: {}",
if missing.len() > 1 { "s" } else { "" },
missing.join(", ")
),
expr_span,
format!(
"Add match arm{} for: {}, or add a wildcard '_' pattern to cover all remaining cases",
if missing.len() > 1 { "s" } else { "" },
missing.join(", ")
),
))
}
fn check_optional_exhaustiveness(
&self,
arms: &[crate::ast::MatchArm],
expr_span: Span,
) -> Result<(), SemanticError> {
let has_some = arms.iter().any(|arm| {
arm.guard.is_none()
&& matches!(&arm.pattern, PatternNode::EnumVariant { name, .. } if name == "some")
});
let has_none = arms.iter().any(|arm| {
arm.guard.is_none()
&& matches!(&arm.pattern, PatternNode::EnumVariant { name, .. } if name == "none")
});
let has_wildcard = arms
.iter()
.any(|arm| matches!(&arm.pattern, PatternNode::Wildcard));
if has_wildcard || (has_some && has_none) {
return Ok(());
}
let mut missing = Vec::new();
if !has_some {
missing.push("some");
}
if !has_none {
missing.push("none");
}
Err(SemanticError::with_help(
format!(
"Non-exhaustive match: missing pattern{} for Optional: {}",
if missing.len() > 1 { "s" } else { "" },
missing.join(", ")
),
expr_span,
format!(
"Add match arm{} for: {}, or add a wildcard '_' pattern",
if missing.len() > 1 { "s" } else { "" },
missing.join(", ")
),
))
}
fn check_named_enum_exhaustiveness(
&self,
type_name: &str,
arms: &[crate::ast::MatchArm],
expr_type: &Type,
expr_span: Span,
) -> Result<(), SemanticError> {
let Some(symbol) = self.symbol_table.lookup(type_name) else {
return self.require_wildcard_pattern(arms, expr_type, expr_span);
};
let Some(variant_names) = &symbol.variants else {
return self.require_wildcard_pattern(arms, expr_type, expr_span);
};
let (covered, has_wildcard) = self.collect_enum_covered_variants(arms, variant_names);
if has_wildcard {
return Ok(());
}
let uncovered: Vec<&String> = variant_names
.iter()
.filter(|v| !covered.contains(*v))
.collect();
if !uncovered.is_empty() {
let uncovered_list = uncovered
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ");
return Err(SemanticError::with_help(
format!(
"Non-exhaustive match: missing variant{} of '{}': {}",
if uncovered.len() > 1 { "s" } else { "" },
type_name,
uncovered_list
),
expr_span,
format!(
"Add match arm{} for: {}, or add a wildcard '_' pattern",
if uncovered.len() > 1 { "s" } else { "" },
uncovered_list
),
));
}
Ok(())
}
fn collect_enum_covered_variants(
&self,
arms: &[crate::ast::MatchArm],
variant_names: &[String],
) -> (std::collections::HashSet<String>, bool) {
let mut covered: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut has_wildcard = false;
for arm in arms {
match &arm.pattern {
PatternNode::Wildcard => {
has_wildcard = true;
break;
}
PatternNode::EnumVariant { name, .. } => {
if arm.guard.is_none() {
covered.insert(name.clone());
}
}
PatternNode::Identifier(name) => {
if arm.guard.is_none() && variant_names.contains(name) {
covered.insert(name.clone());
}
}
_ => {}
}
}
(covered, has_wildcard)
}
fn require_wildcard_pattern(
&self,
arms: &[crate::ast::MatchArm],
expr_type: &Type,
expr_span: Span,
) -> Result<(), SemanticError> {
let has_wildcard = arms
.iter()
.any(|arm| matches!(arm.pattern, PatternNode::Wildcard));
if !has_wildcard {
return Err(SemanticError::with_help(
format!("Non-exhaustive match on type '{}'", format_type(expr_type)),
expr_span,
"Add a wildcard '_' pattern as the last match arm to handle all remaining cases",
));
}
Ok(())
}
}