pub mod declarations;
pub mod error;
pub mod expressions;
pub mod format;
pub mod free_vars;
pub mod imports;
pub mod patterns;
pub mod statements;
pub mod std_registry;
pub mod stdlib;
pub mod symbol_table;
pub mod types;
pub mod unifier;
pub use error::SemanticError;
pub use format::{format_binary_op, format_type};
pub use symbol_table::SymbolTable;
pub use types::{BuiltInSig, GenericContext, MethodSig, Symbol, SymbolKind, Type};
pub use unifier::Unifier;
use crate::ast::{
AstNode, BinaryOp, ExpressionKind, ExpressionNode, Param, PrimitiveType, StatementKind,
StatementNode, TraitBound, TypeKind, TypeNode, UnaryOp,
};
use crate::diagnostic::Files;
use crate::lexer::Span;
use crate::semantics::std_registry::std_module_registry;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
type GenericBound = (String, Vec<Type>);
type GenericBounds = Vec<GenericBound>;
type ResolvedInterface = (Vec<Type>, HashMap<String, MethodSig>);
type ClassFieldInfo = (Type, bool);
pub struct SemanticAnalyzer {
pub(super) symbol_table: SymbolTable,
current_bounds: std::collections::HashMap<String, GenericBounds>,
errors: Vec<SemanticError>,
is_in_static_method: bool,
pub current_self_type: Option<Type>,
pub module_resolver: Option<Rc<RefCell<crate::module_resolver::ModuleResolver>>>,
pub imported_symbols:
std::collections::HashMap<String, std::collections::HashMap<String, Symbol>>,
pub all_module_asts: std::collections::HashMap<String, Vec<AstNode>>,
pub module_dependencies: Vec<String>,
pub(super) required_runtime_features: HashSet<String>,
pub(super) current_file: Option<std::path::PathBuf>, pub lambda_captures: std::collections::HashMap<Span, Vec<(String, Type)>>, pub current_return_type: Option<Type>, pub current_class_type_params: Option<Vec<(String, GenericBounds)>>, expression_type_overrides: std::collections::HashMap<Span, Type>, fresh_type_var_counter: usize, pub(super) hoisted_import_spans: HashSet<Span>,
}
impl Default for SemanticAnalyzer {
fn default() -> Self {
Self::new()
}
}
impl SemanticAnalyzer {
pub(super) fn sanitize_module_path(module_path: &str) -> String {
module_path.replace(['.', '/'], "_")
}
pub fn new() -> Self {
let symbol_table = SymbolTable::new();
Self {
symbol_table,
current_bounds: std::collections::HashMap::new(),
errors: Vec::new(),
is_in_static_method: false,
current_self_type: None,
module_resolver: None,
imported_symbols: std::collections::HashMap::new(),
all_module_asts: std::collections::HashMap::new(),
module_dependencies: Vec::new(),
required_runtime_features: HashSet::new(),
current_file: None,
lambda_captures: std::collections::HashMap::new(),
current_return_type: None,
current_class_type_params: None,
expression_type_overrides: std::collections::HashMap::new(),
fresh_type_var_counter: 0,
hoisted_import_spans: HashSet::new(),
}
}
pub fn new_with_resolver(
resolver: Rc<RefCell<crate::module_resolver::ModuleResolver>>,
) -> Self {
Self {
module_resolver: Some(resolver),
..Self::new()
}
}
pub(super) fn make_symbol(kind: SymbolKind, span: Span, type_: Option<Type>) -> Symbol {
Symbol {
kind,
span,
type_,
interfaces: std::collections::HashMap::new(),
methods: std::collections::HashMap::new(),
fields: std::collections::HashMap::new(),
type_params: Vec::new(),
original_name: None,
llvm_name: None,
default_param_count: 0,
variants: None,
}
}
pub(super) fn make_module_symbol(&self, module_name: &str, span: Span) -> Symbol {
Self::make_symbol(
SymbolKind::Import,
span,
Some(Type::Module(module_name.to_string())),
)
}
fn make_function_symbol(
&self,
span: Span,
function_type: Type,
type_params: &[(String, Vec<crate::ast::TraitBound>)],
default_param_count: usize,
) -> Symbol {
Symbol {
kind: SymbolKind::Function,
span,
type_: Some(function_type),
interfaces: std::collections::HashMap::new(),
methods: std::collections::HashMap::new(),
fields: std::collections::HashMap::new(),
type_params: type_params
.iter()
.map(|(name, bounds)| {
(
name.clone(),
bounds.iter().map(|bound| bound.name.clone()).collect(),
)
})
.collect(),
original_name: None,
llvm_name: None,
default_param_count,
variants: None,
}
}
fn stdlib_nested_modules_map() -> std::collections::HashMap<String, Vec<String>> {
let mut m = std::collections::HashMap::new();
let registry = std_module_registry();
for full_name in registry.keys() {
if let Some(rest) = full_name.strip_prefix("std.")
&& let Some(pos) = rest.find('.')
{
let parent = &rest[..pos];
let child = rest; m.entry(parent.to_string())
.or_insert_with(Vec::new)
.push(child.to_string());
}
}
m
}
pub(super) fn inject_nested_stdlib_children(&mut self, parent_module: &str, span: Span) {
let map = Self::stdlib_nested_modules_map();
if let Some(children) = map.get(parent_module) {
for child in children {
let child_symbols = self.collect_stdlib_module_symbols(child, span);
self.imported_symbols
.insert(child.to_string(), child_symbols.clone());
if let Some(short_name) = child.split('.').next_back()
&& !self.imported_symbols.contains_key(short_name)
{
self.imported_symbols
.insert(short_name.to_string(), child_symbols);
let _ = self
.symbol_table
.add_symbol(short_name, self.make_module_symbol(short_name, span));
}
}
}
}
pub fn set_current_file(&mut self, file: std::path::PathBuf) {
self.current_file = Some(file);
}
fn new_for_module(resolver: Rc<RefCell<crate::module_resolver::ModuleResolver>>) -> Self {
let symbol_table = SymbolTable::new();
Self {
symbol_table,
current_bounds: std::collections::HashMap::new(),
errors: Vec::new(),
is_in_static_method: false,
current_self_type: None,
module_resolver: Some(resolver),
imported_symbols: std::collections::HashMap::new(),
all_module_asts: std::collections::HashMap::new(),
module_dependencies: Vec::new(),
required_runtime_features: HashSet::new(),
current_file: None,
lambda_captures: std::collections::HashMap::new(),
current_return_type: None,
current_class_type_params: None,
expression_type_overrides: std::collections::HashMap::new(),
fresh_type_var_counter: 0,
hoisted_import_spans: HashSet::new(),
}
}
pub fn symbol_table(&self) -> &SymbolTable {
&self.symbol_table
}
pub fn all_symbols(&self) -> &std::collections::HashMap<String, Symbol> {
&self.symbol_table.all_symbols
}
pub fn imported_symbols(
&self,
) -> &std::collections::HashMap<String, std::collections::HashMap<String, Symbol>> {
&self.imported_symbols
}
pub fn all_module_asts(&self) -> &std::collections::HashMap<String, Vec<AstNode>> {
&self.all_module_asts
}
pub fn required_runtime_features(&self) -> Vec<String> {
let mut features: Vec<String> = self.required_runtime_features.iter().cloned().collect();
features.sort();
features
}
pub(super) fn track_runtime_features_for_std_module_name(&mut self, module_name: &str) {
let registry = std_module_registry();
let full_name = if module_name.starts_with("std.") {
module_name.to_string()
} else {
format!("std.{}", module_name)
};
if let Some(def) = registry.get(full_name.as_str()) {
for feature in def.runtime_features {
self.required_runtime_features
.insert((*feature).to_string());
}
}
}
fn binary_op_help(left: &Type, right: &Type, op: &crate::ast::BinaryOp) -> String {
match (left, right) {
(Type::Primitive(crate::ast::PrimitiveType::Str), Type::Primitive(crate::ast::PrimitiveType::Int))
| (Type::Primitive(crate::ast::PrimitiveType::Int), Type::Primitive(crate::ast::PrimitiveType::Str)) => {
"Strings and integers cannot be combined directly. Use int_to_string() to convert the integer first, then use '+' for concatenation.".to_string()
}
(Type::Primitive(crate::ast::PrimitiveType::Str), Type::Primitive(crate::ast::PrimitiveType::Float))
| (Type::Primitive(crate::ast::PrimitiveType::Float), Type::Primitive(crate::ast::PrimitiveType::Str)) => {
"Strings and floats cannot be combined directly. Use float_to_string() to convert the float first, then use '+' for concatenation.".to_string()
}
(Type::Primitive(crate::ast::PrimitiveType::Int), Type::Primitive(crate::ast::PrimitiveType::Float))
| (Type::Primitive(crate::ast::PrimitiveType::Float), Type::Primitive(crate::ast::PrimitiveType::Int)) => {
"Cannot mix int and float in arithmetic. Use int_to_float() or float_to_int() to convert one operand.".to_string()
}
(Type::Primitive(crate::ast::PrimitiveType::Str), Type::Primitive(crate::ast::PrimitiveType::Str)) => {
format!("The '{}' operator is not supported between two strings.", format_binary_op(op))
}
_ => {
format!(
"Ensure both operands have compatible types. Left is {}, right is {}.",
format_type(left),
format_type(right)
)
}
}
}
fn undefined_symbol_error(&self, kind: &str, name: &str, span: Span) -> SemanticError {
if let Some(suggestion) = self.symbol_table.find_similar(name) {
SemanticError::with_help(
format!("Undefined {} '{}'", kind, name),
span,
format!("Did you mean '{}'?", suggestion),
)
} else {
SemanticError::new(format!("Undefined {} '{}'", kind, name), span)
}
}
fn item_not_found_error<F, M>(
&self,
item_type: &str,
item: &str,
type_name: &str,
span: Span,
get_available: F,
message_format: M,
) -> SemanticError
where
F: Fn(&str) -> Vec<String>,
M: Fn(&str, &str, &str) -> String,
{
let available_items = get_available(type_name);
if available_items.is_empty() {
SemanticError::new(message_format(item_type, item, type_name), span)
} else {
let threshold = calculate_similarity_threshold(item);
let suggestion = available_items
.iter()
.map(|f| (f, levenshtein_distance(item, f)))
.filter(|(_, dist)| *dist <= threshold)
.min_by_key(|(_, dist)| *dist)
.map(|(f, _)| f);
let available = available_items.join(", ");
if let Some(similar) = suggestion {
SemanticError::with_help(
message_format(item_type, item, type_name),
span,
format!(
"Did you mean '{}'? Available {}s: {}",
similar,
item_type.to_lowercase(),
available
),
)
} else {
SemanticError::with_help(
message_format(item_type, item, type_name),
span,
format!("Available {}s: {}", item_type.to_lowercase(), available),
)
}
}
}
fn field_not_found_error(&self, field: &str, type_name: &str, span: Span) -> SemanticError {
self.item_not_found_error(
"Field",
field,
type_name,
span,
|t| self.get_available_fields(t),
|_item_type, item, type_name| {
format!("Field '{}' not found on type '{}'", item, type_name)
},
)
}
fn get_available_fields(&self, type_name: &str) -> Vec<String> {
if let Some(symbol) = self.symbol_table.lookup(type_name) {
symbol.fields.keys().cloned().collect()
} else {
Vec::new()
}
}
fn method_not_found_error(&self, method: &str, type_name: &str, span: Span) -> SemanticError {
self.item_not_found_error(
"Method",
method,
type_name,
span,
|t| self.get_available_methods(t),
|_item_type, item, type_name| {
format!("Undefined method '{}' on type '{}'", item, type_name)
},
)
}
fn get_available_methods(&self, type_name: &str) -> Vec<String> {
if let Some(symbol) = self.symbol_table.lookup(type_name) {
symbol.methods.keys().cloned().collect()
} else {
Vec::new()
}
}
pub fn set_class_type_params(&mut self, params: Vec<(String, GenericBounds)>) {
self.current_class_type_params = Some(params.clone());
for (param, bounds) in params {
self.current_bounds.insert(param, bounds);
}
}
pub fn clear_class_type_params(&mut self) {
if let Some(params) = &self.current_class_type_params {
for (param, _) in params {
self.current_bounds.remove(param);
}
}
self.current_class_type_params = None;
}
fn normalize_type_for_bound(&self, type_: &Type, known_type_params: &[String]) -> Type {
match type_ {
Type::Named(name, args)
if args.is_empty() && known_type_params.iter().any(|p| p == name) =>
{
Type::Variable(name.clone())
}
Type::Named(name, args) => Type::Named(
name.clone(),
args.iter()
.map(|arg| self.normalize_type_for_bound(arg, known_type_params))
.collect(),
),
Type::List(inner) => Type::List(Box::new(
self.normalize_type_for_bound(inner, known_type_params),
)),
Type::Set(inner) => Type::Set(Box::new(
self.normalize_type_for_bound(inner, known_type_params),
)),
Type::Map(key, value) => Type::Map(
Box::new(self.normalize_type_for_bound(key, known_type_params)),
Box::new(self.normalize_type_for_bound(value, known_type_params)),
),
Type::Optional(inner) => Type::Optional(Box::new(
self.normalize_type_for_bound(inner, known_type_params),
)),
Type::Reference(inner) => Type::Reference(Box::new(
self.normalize_type_for_bound(inner, known_type_params),
)),
Type::Function {
params,
returns,
default_count,
} => Type::Function {
params: params
.iter()
.map(|param| self.normalize_type_for_bound(param, known_type_params))
.collect(),
returns: Box::new(self.normalize_type_for_bound(returns, known_type_params)),
default_count: *default_count,
},
_ => type_.clone(),
}
}
fn resolve_type_param_bounds(
&self,
type_params: &[(String, Vec<TraitBound>)],
) -> Result<Vec<(String, GenericBounds)>, SemanticError> {
let mut resolved = Vec::new();
let mut known_type_params = Vec::new();
for (param, bounds) in type_params {
let mut resolved_bounds = Vec::new();
for bound in bounds {
let resolved_type_args = bound
.type_params
.iter()
.map(|type_param| self.resolve_type(type_param))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(|ty| self.normalize_type_for_bound(&ty, &known_type_params))
.collect();
resolved_bounds.push((bound.name.clone(), resolved_type_args));
}
resolved.push((param.clone(), resolved_bounds));
known_type_params.push(param.clone());
}
Ok(resolved)
}
fn infer_missing_type_params_from_function_bounds(
&self,
func_name: &str,
substitutions: &mut std::collections::HashMap<String, Type>,
) {
let mut func_node_opt = None;
for module_nodes in self.all_module_asts.values() {
for node in module_nodes {
if let AstNode::Function(func) = node
&& func.name == func_name
{
func_node_opt = Some(func);
break;
}
}
if func_node_opt.is_some() {
break;
}
}
let Some(func_node) = func_node_opt else {
return;
};
infer_missing_type_params_from_bounds(&func_node.type_params, substitutions);
}
pub(super) fn get_builtin_sig(&self, name: &str) -> Option<&BuiltInSig> {
crate::semantics::stdlib::BUILT_IN_FUNCTIONS.get(name)
}
pub fn analyze(
&mut self,
ast: &[AstNode],
mut files: Option<&mut Files>,
) -> Vec<SemanticError> {
self.add_builtin_functions();
if let Err(e) = self.collect_hoistable_declarations(ast, files.as_deref_mut()) {
self.errors.push(e);
}
self.analyze_nodes(ast, files);
std::mem::take(&mut self.errors)
}
fn add_builtin_functions(&mut self) {
let span = Span::new(0, 0);
for (name, sig) in crate::semantics::stdlib::BUILT_IN_FUNCTIONS.iter() {
self.register_builtin_function(name, sig, span);
}
self.add_sync_builtin_types();
self.add_csv_builtin_types();
}
fn add_sync_builtin_types(&mut self) {
let span = Span::new(0, 0);
let classes = crate::semantics::stdlib::sync_module_class_symbols(span);
for (name, sym) in classes {
let _ = self.symbol_table.add_symbol(&name, sym);
}
}
fn add_csv_builtin_types(&mut self) {
let span = Span::new(0, 0);
let symbol = Self::make_csv_symbol(span);
let _ = self.symbol_table.add_symbol("Csv", symbol);
}
#[allow(clippy::only_used_in_recursion)]
pub fn resolve_type(&self, type_node: &TypeNode) -> Result<Type, SemanticError> {
match &type_node.kind {
TypeKind::Primitive(prim) => match prim {
crate::ast::PrimitiveType::Int => {
Ok(Type::Primitive(crate::ast::PrimitiveType::Int))
}
crate::ast::PrimitiveType::Float => {
Ok(Type::Primitive(crate::ast::PrimitiveType::Float))
}
crate::ast::PrimitiveType::Bool => {
Ok(Type::Primitive(crate::ast::PrimitiveType::Bool))
}
crate::ast::PrimitiveType::Char => {
Ok(Type::Primitive(crate::ast::PrimitiveType::Char))
}
crate::ast::PrimitiveType::Str => {
Ok(Type::Primitive(crate::ast::PrimitiveType::Str))
}
crate::ast::PrimitiveType::Void => Ok(Type::Void),
crate::ast::PrimitiveType::Auto => Err(SemanticError::with_help(
"The 'auto' type is not allowed in this context",
type_node.span,
"Use an explicit type annotation instead of 'auto'",
)),
},
TypeKind::Named(name, type_args) => {
if type_args.is_empty()
&& let Some(symbol) = self.symbol_table.lookup(name)
&& matches!(symbol.kind, SymbolKind::Type)
{
return Ok(Type::Variable(name.clone()));
}
if name == "optional" && type_args.len() == 1 {
let resolved_arg = self.resolve_type(&type_args[0])?;
return Ok(Type::Optional(Box::new(resolved_arg)));
} else if name == "result" && type_args.len() == 2 {
let resolved_ok = self.resolve_type(&type_args[0])?;
let resolved_err = self.resolve_type(&type_args[1])?;
if !self.type_implements_interface(&resolved_err, "Error") {
return Err(SemanticError::with_help(
format!(
"Result error type must implement Error, but found {}",
format_type(&resolved_err)
),
type_node.span,
"Use an error type that implements Error (requires message() -> string).",
));
}
return Ok(Type::Result(Box::new(resolved_ok), Box::new(resolved_err)));
}
let resolved_args = type_args
.iter()
.map(|arg| self.resolve_type(arg))
.collect::<Result<Vec<_>, _>>()?;
Ok(Type::Named(name.clone(), resolved_args))
}
TypeKind::Function { params, returns } => {
let resolved_params = params
.iter()
.map(|p| self.resolve_type(p))
.collect::<Result<Vec<_>, _>>()?;
let resolved_return = self.resolve_type(returns)?;
Ok(Type::Function {
params: resolved_params,
returns: Box::new(resolved_return),
default_count: 0,
})
}
TypeKind::Reference(inner) => {
let resolved_inner = self.resolve_type(inner)?;
Ok(Type::Reference(Box::new(resolved_inner)))
}
TypeKind::List(inner) => {
let resolved_inner = self.resolve_type(inner)?;
Ok(Type::List(Box::new(resolved_inner)))
}
TypeKind::Map(key, value) => {
let resolved_key = self.resolve_type(key)?;
let resolved_value = self.resolve_type(value)?;
Ok(Type::Map(Box::new(resolved_key), Box::new(resolved_value)))
}
TypeKind::Set(inner) => {
let resolved_inner = self.resolve_type(inner)?;
Ok(Type::Set(Box::new(resolved_inner)))
}
TypeKind::Tuple(left, right) => {
let resolved_left = self.resolve_type(left)?;
let resolved_right = self.resolve_type(right)?;
Ok(Type::Tuple(
Box::new(resolved_left),
Box::new(resolved_right),
))
}
TypeKind::TraitObject(_) => Err(SemanticError::new(
"Trait objects are not yet supported",
type_node.span,
)),
TypeKind::Auto => Err(SemanticError::with_help(
"The 'auto' type is not allowed in this context",
type_node.span,
"Use an explicit type annotation instead of 'auto'",
)),
}
}
pub fn get_expression_type(&mut self, expr: &ExpressionNode) -> Result<Type, SemanticError> {
if let Some(override_type) = self.expression_type_overrides.get(&expr.span) {
return Ok(override_type.clone());
}
match &expr.kind {
ExpressionKind::Literal(_) => self.infer_literal_type(expr),
ExpressionKind::None => Ok(Type::Optional(Box::new(Type::Never))),
ExpressionKind::Identifier(name) => {
self.resolve_identifier_expression_type(name, expr.span)
}
ExpressionKind::Binary {
left,
right,
op,
op_span,
..
} => self.resolve_binary_expression_type(left, right, op, op_span, expr.span),
ExpressionKind::Unary {
expr, op, op_span, ..
} => self.resolve_unary_expression_type(expr, op, op_span),
ExpressionKind::Call { func, args } => {
self.resolve_call_expression_type(func, args, expr.span)
}
ExpressionKind::FieldAccess { expr, field } => {
self.resolve_field_access_type(expr, field, expr.span)
}
ExpressionKind::ListAccess { expr, index: _ } => {
self.resolve_list_access_type(expr, expr.span)
}
ExpressionKind::ListLiteral(elements) => self.resolve_list_literal_type(elements),
ExpressionKind::MapLiteral { entries, .. } => self.resolve_map_literal_type(entries),
ExpressionKind::If {
then_expr,
else_expr,
..
} => self.resolve_if_expression_type(then_expr, else_expr, expr.span),
ExpressionKind::Lambda {
params,
return_type,
body,
} => self.resolve_lambda_type(params, return_type, body, expr.span),
ExpressionKind::SetOrMapLiteral(elements) => self.resolve_set_literal_type(elements),
ExpressionKind::TupleLiteral(elements) => {
self.resolve_tuple_literal_type(elements, expr.span)
}
ExpressionKind::GenericType(name, type_args) => {
self.resolve_generic_type(name, type_args, expr.span)
}
}
}
fn resolve_field_access_type(
&mut self,
expr: &ExpressionNode,
field: &str,
span: Span,
) -> Result<Type, SemanticError> {
let expr_type_res = self.get_expression_type(expr);
let expr_type = match expr_type_res {
Ok(t) => t,
Err(e) => {
if let crate::ast::ExpressionKind::Identifier(name) = &expr.kind
&& let Some(func_type) = self.try_stdlib_method_lookup(name, field)
{
return Ok(func_type);
}
return Err(e);
}
};
if let crate::ast::ExpressionKind::Identifier(name) = &expr.kind
&& matches!(expr_type, Type::Function { .. })
&& let Some(func_type) = self.try_stdlib_method_lookup(name, field)
{
return Ok(func_type);
}
self.resolve_field_access_by_type(&expr_type, field, span)
}
fn try_stdlib_method_lookup(&self, name: &str, field: &str) -> Option<Type> {
if let Some(symbol) = self.symbol_table.lookup(name)
&& matches!(symbol.kind, SymbolKind::Function)
{
use crate::semantics::stdlib::{StdlibItem, lookup_stdlib_item};
let full_name = format!("{}.{}", name, field);
if let Some(StdlibItem::Function { params, ret, .. }) = lookup_stdlib_item(&full_name) {
return Some(Type::Function {
params: params.clone(),
returns: Box::new(ret.clone()),
default_count: 0,
});
}
}
let stdlib_names: std::collections::HashSet<String> = std_module_registry()
.keys()
.filter_map(|s| s.strip_prefix("std.").map(|name| name.to_string()))
.collect();
for (ns, module_symbols) in &self.imported_symbols {
if !stdlib_names.contains(ns) {
continue;
}
if let Some(class_sym) = module_symbols.get(name)
&& matches!(class_sym.kind, SymbolKind::Class)
&& let Some(method_sig) = class_sym.methods.get(field)
{
return Some(Type::Function {
params: method_sig.params.clone(),
returns: Box::new(method_sig.return_type.clone()),
default_count: 0,
});
}
}
None
}
fn resolve_field_access_by_type(
&mut self,
expr_type: &Type,
field: &str,
span: Span,
) -> Result<Type, SemanticError> {
if let Type::Module(module_name) = expr_type {
return self.resolve_module_field(module_name, field, span);
}
if let Type::Reference(inner) = expr_type {
let inner_type = (*inner).clone();
return self.resolve_reference_field(&inner_type, field, span);
}
if let Some(method_sig) = self.get_method_sig(expr_type, field) {
return Ok(Type::Function {
params: method_sig.params,
returns: Box::new(method_sig.return_type),
default_count: 0,
});
}
if let Type::Named(name, args) = expr_type {
return self.resolve_named_field(expr_type, name, args, field, span);
}
if let Type::Tuple(left_type, right_type) = expr_type {
return match field {
"left" => Ok(*left_type.clone()),
"right" => Ok(*right_type.clone()),
_ => Err(SemanticError::with_help(
format!("Unknown field '{}' on tuple type", field),
span,
"Tuples only have two fields: 'left' and 'right'. Example: auto pair = (1, 2); print(int_to_string(pair.left))",
)),
};
}
let type_name = format_type(expr_type);
Err(self.method_not_found_error(field, &type_name, span))
}
fn resolve_list_access_type(
&mut self,
expr: &ExpressionNode,
span: Span,
) -> Result<Type, SemanticError> {
let target_type = self.get_expression_type(expr)?;
match target_type {
Type::List(elem_type) => Ok(*elem_type),
Type::Map(_, value_type) => Ok(*value_type),
Type::EmptyMap => Err(SemanticError::with_help(
"Cannot index empty map",
span,
"The map type is unknown. Provide type annotations or add entries to the map literal.",
)),
_ => Err(SemanticError::with_help(
"Cannot index non-list type",
span,
"Only lists and maps can be indexed with '[]'. Examples: my_list[0], my_map['key']",
)),
}
}
fn resolve_list_literal_type(
&mut self,
elements: &[ExpressionNode],
) -> Result<Type, SemanticError> {
if elements.is_empty() {
return Ok(Type::EmptyList);
}
let first_type = self.get_expression_type(&elements[0])?;
for (index, element) in elements.iter().enumerate() {
let element_type = self.get_expression_type(element)?;
if self
.check_type_compatibility(&first_type, &element_type, element.span)
.is_err()
{
return Err(SemanticError::with_help(
format!(
"List element type mismatch: expected {}, but element at index {} has type {}",
format_type(&first_type),
index,
format_type(&element_type)
),
element.span,
"All elements in a list must have the same type. The list type is inferred from the first element.",
));
}
}
Ok(Type::List(Box::new(first_type)))
}
fn resolve_map_literal_type(
&mut self,
entries: &[(ExpressionNode, ExpressionNode)],
) -> Result<Type, SemanticError> {
if entries.is_empty() {
return Ok(Type::EmptyMap);
}
let (key, value) = &entries[0];
let key_type = self.get_expression_type(key)?;
let value_type = self.get_expression_type(value)?;
Ok(Type::Map(Box::new(key_type), Box::new(value_type)))
}
fn resolve_if_expression_type(
&mut self,
then_expr: &ExpressionNode,
else_expr: &ExpressionNode,
span: Span,
) -> Result<Type, SemanticError> {
let then_type = self.get_expression_type(then_expr)?;
let else_type = self.get_expression_type(else_expr)?;
if then_type == else_type {
Ok(then_type)
} else {
Err(SemanticError::with_help(
"If expression branches must have the same type",
span,
format!(
"Then branch has type {}, else branch has type {}",
format_type(&then_type),
format_type(&else_type)
),
))
}
}
fn resolve_lambda_type(
&mut self,
params: &[Param],
return_type: &TypeNode,
body: &[StatementNode],
span: Span,
) -> Result<Type, SemanticError> {
if self.lambda_captures.contains_key(&span) {
let param_types = params
.iter()
.map(|p| self.resolve_type(&p.type_))
.collect::<Result<Vec<_>, _>>()?;
let resolved_return_type = self.resolve_type(return_type)?;
let default_count = params.iter().filter(|p| p.default_value.is_some()).count();
return Ok(Type::Function {
params: param_types,
returns: Box::new(resolved_return_type),
default_count,
});
}
let mut local_vars = std::collections::HashSet::new();
for param in params {
local_vars.insert(param.name.clone());
}
self.symbol_table.push_scope()?;
for param in params {
let param_type = self.resolve_type(¶m.type_)?;
self.symbol_table.add_symbol(
¶m.name,
Self::make_symbol(SymbolKind::Variable, param.type_.span, Some(param_type)),
)?;
}
self.analyze_block(body, None)?;
let captures = self.find_free_variables_in_block(body, &local_vars)?;
self.lambda_captures.insert(span, captures);
let param_types = params
.iter()
.map(|p| self.resolve_type(&p.type_))
.collect::<Result<Vec<_>, _>>()?;
let return_type_resolved = if body.is_empty() {
Type::Void
} else {
match &body.last().expect("body is not empty").kind {
StatementKind::Expression(expr) => self.get_expression_type(expr)?,
StatementKind::Return(Some(expr)) => self.get_expression_type(expr)?,
_ => Type::Void,
}
};
self.symbol_table.pop_scope()?;
let default_count = params.iter().filter(|p| p.default_value.is_some()).count();
Ok(Type::Function {
params: param_types,
returns: Box::new(return_type_resolved),
default_count,
})
}
fn resolve_set_literal_type(
&mut self,
elements: &[ExpressionNode],
) -> Result<Type, SemanticError> {
if elements.is_empty() {
return Ok(Type::EmptySetOrMap);
}
let elem_type = self.get_expression_type(&elements[0])?;
Ok(Type::Set(Box::new(elem_type)))
}
fn resolve_tuple_literal_type(
&mut self,
elements: &[ExpressionNode],
span: Span,
) -> Result<Type, SemanticError> {
if elements.len() != 2 {
return Err(SemanticError::with_help(
format!(
"Tuple must have exactly 2 elements, found {}",
elements.len()
),
span,
"Tuples in Mux always contain exactly 2 elements: (left, right). Example: auto pair = (1, \"hello\")",
));
}
let left_type = self.get_expression_type(&elements[0])?;
let right_type = self.get_expression_type(&elements[1])?;
Ok(Type::Tuple(Box::new(left_type), Box::new(right_type)))
}
fn resolve_generic_type(
&mut self,
name: &str,
type_args: &[TypeNode],
span: Span,
) -> Result<Type, SemanticError> {
if name == "tuple" {
return self.resolve_tuple_type_annotation(type_args, span);
}
let (lookup_name, symbol) = self.resolve_generic_type_symbol(name, span)?;
self.validate_type_argument_count(&lookup_name, &symbol, type_args, span)?;
let resolved_args = type_args
.iter()
.map(|arg| self.resolve_type(arg))
.collect::<Result<Vec<_>, _>>()?;
Ok(Type::Named(lookup_name, resolved_args))
}
fn resolve_tuple_type_annotation(
&self,
type_args: &[TypeNode],
span: Span,
) -> Result<Type, SemanticError> {
if type_args.len() != 2 {
return Err(SemanticError::with_help(
format!(
"Tuple type requires exactly 2 type arguments, got {}",
type_args.len()
),
span,
"Tuple types always have exactly 2 type parameters. Example: tuple<int, string>",
));
}
let left_type = Box::new(self.resolve_type(&type_args[0])?);
let right_type = Box::new(self.resolve_type(&type_args[1])?);
Ok(Type::Tuple(left_type, right_type))
}
fn resolve_generic_type_symbol(
&mut self,
name: &str,
span: Span,
) -> Result<(String, Symbol), SemanticError> {
if let Some((module_name, type_name)) = name.split_once('.') {
let module_symbols = self
.imported_symbols
.get(module_name)
.ok_or_else(|| self.undefined_symbol_error("module", module_name, span))?;
let symbol = module_symbols
.get(type_name)
.ok_or_else(|| self.undefined_symbol_error("type", type_name, span))?;
if self.symbol_table.lookup(type_name).is_none() {
let _ = self.symbol_table.add_symbol(type_name, symbol.clone());
}
Ok((type_name.to_string(), symbol.clone()))
} else if let Some(symbol) = self.symbol_table.lookup(name) {
Ok((name.to_string(), symbol))
} else {
Err(self.undefined_symbol_error("type", name, span))
}
}
fn validate_type_argument_count(
&self,
lookup_name: &str,
symbol: &Symbol,
type_args: &[TypeNode],
span: Span,
) -> Result<(), SemanticError> {
let expected_count = symbol.type_params.len();
let actual_count = type_args.len();
if expected_count != actual_count {
return Err(SemanticError::with_help(
format!(
"Generic type '{}' requires {} type argument(s), got {}",
lookup_name, expected_count, actual_count
),
span,
format!(
"Provide exactly {} type argument{} in angle brackets, e.g. {}<{}>",
expected_count,
if expected_count == 1 { "" } else { "s" },
lookup_name,
symbol
.type_params
.iter()
.map(|(p, _)| p.as_str())
.collect::<Vec<_>>()
.join(", ")
),
));
}
Ok(())
}
fn resolve_unary_expression_type(
&mut self,
expr: &ExpressionNode,
op: &UnaryOp,
op_span: &Span,
) -> Result<Type, SemanticError> {
match op {
UnaryOp::Not => Ok(Type::Primitive(crate::ast::PrimitiveType::Bool)),
UnaryOp::Neg => {
let operand_type = self.get_expression_type(expr)?;
match operand_type {
Type::Primitive(crate::ast::PrimitiveType::Int)
| Type::Primitive(crate::ast::PrimitiveType::Float) => Ok(operand_type),
_ => Err(SemanticError::with_help(
format!(
"Negation operator '-' requires a numeric operand, found {}",
format_type(&operand_type)
),
*op_span,
"The unary '-' operator can only be applied to int or float values",
)),
}
}
UnaryOp::Ref => {
let operand_type = self.get_expression_type(expr)?;
Ok(Type::Reference(Box::new(operand_type)))
}
UnaryOp::Deref => {
let operand_type = self.get_expression_type(expr)?;
if let Type::Reference(inner) = operand_type {
Ok(*inner)
} else {
Err(SemanticError::with_help(
format!(
"Cannot dereference type {}, which is not a reference",
format_type(&operand_type)
),
*op_span,
"The dereference operator '*' can only be applied to reference types (e.g., ref int)",
))
}
}
UnaryOp::Incr | UnaryOp::Decr => {
self.check_not_modifying_constant(expr, op_span)?;
let operand_type = self.get_expression_type(expr)?;
match operand_type {
Type::Primitive(crate::ast::PrimitiveType::Int) => Ok(operand_type),
_ => Err(SemanticError::with_help(
format!(
"Increment/decrement operators require an int operand, found {}",
format_type(&operand_type)
),
*op_span,
"The '++' and '--' operators can only be applied to int variables",
)),
}
}
}
}
fn resolve_call_expression_type(
&mut self,
func: &ExpressionNode,
args: &[ExpressionNode],
expr_span: Span,
) -> Result<Type, SemanticError> {
let func_type = self.resolve_called_function_type(func)?;
match func_type {
Type::Function {
params,
returns,
default_count,
..
} => self.resolve_function_call_type(
func,
args,
params,
returns,
default_count,
expr_span,
),
_ => Err(SemanticError::with_help(
"Cannot call non-function type",
expr_span,
"Only functions can be called with '()'. Ensure the expression before '()' is a function.",
)),
}
}
fn resolve_called_function_type(
&mut self,
func: &ExpressionNode,
) -> Result<Type, SemanticError> {
match &func.kind {
ExpressionKind::GenericType(name, type_args) => {
self.get_instantiated_constructor_type(name, type_args, func.span)
}
ExpressionKind::Identifier(name) => match self.get_expression_type(func) {
Ok(t) => Ok(t),
Err(e) if e.message.contains("Undefined variable") => {
Err(self.undefined_symbol_error("function", name, func.span))
}
Err(e) => Err(e),
},
_ => self.get_expression_type(func),
}
}
fn resolve_function_call_type(
&mut self,
func: &ExpressionNode,
args: &[ExpressionNode],
params: Vec<Type>,
returns: Box<Type>,
default_count: usize,
expr_span: Span,
) -> Result<Type, SemanticError> {
let actual_default_count = self.call_default_param_count(func, default_count);
let min_args = params.len() - actual_default_count;
let max_args = params.len();
if args.len() < min_args || args.len() > max_args {
self.report_call_arity_error(
func,
args.len(),
min_args,
max_args,
actual_default_count,
expr_span,
)?;
}
let should_rename = matches!(func.kind, ExpressionKind::Identifier(_));
let mut rename_map = std::collections::HashMap::new();
let renamed_params: Vec<Type> = if should_rename {
params
.iter()
.map(|p| self.rename_type_vars(p, &mut rename_map))
.collect()
} else {
params.clone()
};
let renamed_returns = if should_rename {
self.rename_type_vars(&returns, &mut rename_map)
} else {
*returns.clone()
};
let mut unifier = Unifier::new();
for (param, arg) in renamed_params.iter().zip(args.iter()) {
let arg_type = self.get_expression_type(arg)?;
unifier.unify(param, &arg_type, expr_span)?;
}
if let Some(func_name) = self.call_function_name(func) {
let reverse_rename_map: std::collections::HashMap<String, String> = rename_map
.iter()
.map(|(original, fresh)| (fresh.clone(), original.clone()))
.collect();
let mut substitutions_by_original_name: std::collections::HashMap<String, Type> =
unifier
.substitutions
.iter()
.map(|(fresh, ty)| {
let original = reverse_rename_map
.get(fresh)
.cloned()
.unwrap_or_else(|| fresh.clone());
(original, ty.clone())
})
.collect();
self.infer_missing_type_params_from_function_bounds(
func_name,
&mut substitutions_by_original_name,
);
for (original, ty) in substitutions_by_original_name {
let fresh = rename_map.get(&original).cloned().unwrap_or(original);
unifier.substitutions.entry(fresh).or_insert(ty);
}
}
Ok(unifier.apply(&renamed_returns))
}
fn rename_type_vars(
&mut self,
t: &Type,
mapping: &mut std::collections::HashMap<String, String>,
) -> Type {
match t {
Type::Variable(name) => Type::Variable(self.fresh_name_for(name, mapping)),
Type::Generic(name) => Type::Generic(self.fresh_name_for(name, mapping)),
Type::List(inner) => Type::List(Box::new(self.rename_type_vars(inner, mapping))),
Type::Set(inner) => Type::Set(Box::new(self.rename_type_vars(inner, mapping))),
Type::Optional(inner) => {
Type::Optional(Box::new(self.rename_type_vars(inner, mapping)))
}
Type::Reference(inner) => {
Type::Reference(Box::new(self.rename_type_vars(inner, mapping)))
}
Type::Map(key, value) => Type::Map(
Box::new(self.rename_type_vars(key, mapping)),
Box::new(self.rename_type_vars(value, mapping)),
),
Type::Tuple(left, right) => Type::Tuple(
Box::new(self.rename_type_vars(left, mapping)),
Box::new(self.rename_type_vars(right, mapping)),
),
Type::Result(ok, err) => Type::Result(
Box::new(self.rename_type_vars(ok, mapping)),
Box::new(self.rename_type_vars(err, mapping)),
),
Type::Named(name, args) => Type::Named(
name.clone(),
args.iter()
.map(|arg| self.rename_type_vars(arg, mapping))
.collect(),
),
Type::Instantiated(name, args) => Type::Instantiated(
name.clone(),
args.iter()
.map(|arg| self.rename_type_vars(arg, mapping))
.collect(),
),
Type::Function {
params,
returns,
default_count,
} => Type::Function {
params: params
.iter()
.map(|p| self.rename_type_vars(p, mapping))
.collect(),
returns: Box::new(self.rename_type_vars(returns, mapping)),
default_count: *default_count,
},
other => other.clone(),
}
}
fn fresh_name_for(
&mut self,
original: &str,
mapping: &mut std::collections::HashMap<String, String>,
) -> String {
if let Some(existing) = mapping.get(original) {
return existing.clone();
}
self.fresh_type_var_counter += 1;
let fresh = format!("{}#{}", original, self.fresh_type_var_counter);
mapping.insert(original.to_string(), fresh.clone());
fresh
}
fn call_default_param_count(&self, func: &ExpressionNode, default_count: usize) -> usize {
match &func.kind {
ExpressionKind::Identifier(name) => {
let symbol_default = self
.symbol_table
.lookup(name)
.map(|s| s.default_param_count)
.unwrap_or(0);
std::cmp::max(default_count, symbol_default)
}
_ => default_count,
}
}
fn call_function_name<'a>(&self, func: &'a ExpressionNode) -> Option<&'a str> {
match &func.kind {
ExpressionKind::Identifier(name) => Some(name.as_str()),
ExpressionKind::FieldAccess { field, .. } => Some(field.as_str()),
_ => None,
}
}
fn report_call_arity_error(
&self,
func: &ExpressionNode,
arg_count: usize,
min_args: usize,
max_args: usize,
actual_default_count: usize,
expr_span: Span,
) -> Result<(), SemanticError> {
let func_name = match &func.kind {
ExpressionKind::Identifier(name) => format!("'{}'", name),
ExpressionKind::FieldAccess { field, .. } => format!("'{}'", field),
_ => "this function".to_string(),
};
if actual_default_count > 0 {
Err(SemanticError::with_help(
format!(
"{} expects {} to {} arguments, but {} {} provided",
func_name,
min_args,
max_args,
arg_count,
if arg_count == 1 { "was" } else { "were" }
),
expr_span,
format!(
"{} has {} required parameter(s) and {} optional parameter(s) with defaults",
func_name, min_args, actual_default_count
),
))
} else {
Err(SemanticError::with_help(
format!(
"{} expects {} argument(s), but {} {} provided",
func_name,
max_args,
arg_count,
if arg_count == 1 { "was" } else { "were" }
),
expr_span,
if arg_count > max_args {
"Too many arguments. Remove the extra argument(s).".to_string()
} else {
format!(
"Not enough arguments. {} requires {} argument(s).",
func_name, max_args
)
},
))
}
}
fn resolve_identifier_expression_type(
&self,
name: &str,
span: Span,
) -> Result<Type, SemanticError> {
if name == "self" {
if let Some(self_type) = &self.current_self_type {
return Ok(self_type.clone());
}
return Ok(Type::Named("Unknown".to_string(), vec![]));
}
let symbol = self
.symbol_table
.get_cloned(name)
.or_else(|| self.symbol_table.lookup(name));
if let Some(symbol) = symbol {
let type_ = symbol.type_.clone().ok_or_else(|| {
SemanticError::new(format!("Symbol '{}' has no type information", name), span)
})?;
let type_ = match &type_ {
Type::Generic(n) if n == name => Type::Variable(name.to_string()),
_ => type_,
};
return Ok(type_);
}
if let Some(sig) = self.get_builtin_sig(name) {
return Ok(Type::Function {
params: sig.params.clone(),
returns: Box::new(sig.return_type.clone()),
default_count: 0,
});
}
let stdlib_names: std::collections::HashSet<String> = std_module_registry()
.keys()
.filter_map(|s| s.strip_prefix("std.").map(|name| name.to_string()))
.collect();
for (module_ns, module_symbols) in &self.imported_symbols {
if !stdlib_names.contains(module_ns) {
continue;
}
if let Some(sym) = module_symbols.get(name)
&& matches!(sym.kind, SymbolKind::Class)
{
return Ok(Type::Named(name.to_string(), Vec::new()));
}
}
Err(self.undefined_symbol_error("variable", name, span))
}
fn resolve_binary_expression_type(
&mut self,
left: &ExpressionNode,
right: &ExpressionNode,
op: &crate::ast::BinaryOp,
op_span: &Span,
expr_span: Span,
) -> Result<Type, SemanticError> {
let left_type = self.get_expression_type(left)?;
let right_type = self.get_expression_type(right)?;
if *op == crate::ast::BinaryOp::Assign {
self.resolve_empty_collection_types(&left_type, right)?;
let right_type = self.get_expression_type(right)?;
self.validate_assignment_target(left, &right_type, expr_span)?;
return Ok(right_type);
}
if matches!(
op,
crate::ast::BinaryOp::AddAssign
| crate::ast::BinaryOp::SubtractAssign
| crate::ast::BinaryOp::MultiplyAssign
| crate::ast::BinaryOp::DivideAssign
| crate::ast::BinaryOp::ModuloAssign
) {
self.validate_compound_assignment_target(
left,
&left_type,
&right_type,
op,
expr_span,
op_span,
)?;
let base_op = Self::compound_base_op(op);
if let Some(result_type) =
self.resolve_binary_operator(&left_type, &right_type, &base_op)
{
return Ok(result_type);
}
return Err(SemanticError::with_help(
format!(
"Operator '{}' is not supported between types {} and {}",
format_binary_op(&base_op),
format_type(&left_type),
format_type(&right_type)
),
*op_span,
Self::binary_op_help(&left_type, &right_type, &base_op),
));
}
if let Some(result_type) = self.resolve_binary_operator(&left_type, &right_type, op) {
return Ok(result_type);
}
Err(SemanticError::with_help(
format!(
"Operator '{}' is not supported between types {} and {}",
format_binary_op(op),
format_type(&left_type),
format_type(&right_type)
),
*op_span,
Self::binary_op_help(&left_type, &right_type, op),
))
}
fn validate_assignment_target(
&mut self,
left: &ExpressionNode,
right_type: &Type,
expr_span: Span,
) -> Result<(), SemanticError> {
match &left.kind {
crate::ast::ExpressionKind::Identifier(name) => {
self.validate_identifier_assignment_target(name, left.span, right_type, expr_span)
}
crate::ast::ExpressionKind::FieldAccess {
expr: obj_expr,
field,
} => self.validate_field_assignment_target(
obj_expr, field, left.span, right_type, expr_span,
),
crate::ast::ExpressionKind::Unary {
op: crate::ast::UnaryOp::Deref,
..
} => Ok(()),
crate::ast::ExpressionKind::ListAccess {
expr: target_expr, ..
} => self.validate_index_assignment_target(target_expr, right_type, expr_span),
_ => Err(SemanticError::with_help(
"Cannot assign to this expression",
expr_span,
"Only variables, fields, dereferences, and indexed expressions can be assigned to",
)),
}
}
fn resolve_lvalue_class_symbol(
&mut self,
obj_expr: &ExpressionNode,
) -> Result<Option<(String, crate::semantics::Symbol)>, SemanticError> {
let obj_type = self.get_expression_type(obj_expr)?;
if let Type::Named(class_name, _) = &obj_type
&& let Some(symbol) = self.symbol_table.lookup(class_name)
{
return Ok(Some((class_name.clone(), symbol)));
}
Ok(None)
}
fn check_const_field_assignment(
&self,
field: &str,
fields: &std::collections::HashMap<String, (Type, bool)>,
expr_span: Span,
) -> Result<(), SemanticError> {
if let Some((_field_type, is_const)) = fields.get(field)
&& *is_const
{
return Err(SemanticError::with_help(
format!("Cannot assign to const field '{}'", field),
expr_span,
"Const fields cannot be modified after initialization. Remove the 'const' modifier from the field declaration if mutation is needed.",
));
}
Ok(())
}
fn validate_identifier_assignment_target(
&mut self,
name: &str,
left_span: Span,
right_type: &Type,
expr_span: Span,
) -> Result<(), SemanticError> {
let symbol = self
.symbol_table
.lookup(name)
.ok_or_else(|| self.undefined_symbol_error("variable", name, left_span))?;
if symbol.kind == SymbolKind::Constant {
return Err(SemanticError::with_help(
format!("Cannot assign to constant '{}'", name),
expr_span,
"Constants cannot be modified after initialization",
));
}
let var_type = symbol.type_.as_ref().ok_or_else(|| {
SemanticError::new(
format!("Variable '{}' has no type information", name),
left_span,
)
})?;
if let Type::Reference(inner) = var_type {
self.check_type_compatibility(inner, right_type, expr_span)
} else {
self.check_type_compatibility(var_type, right_type, expr_span)
}
}
fn validate_field_assignment_target(
&mut self,
obj_expr: &ExpressionNode,
field: &str,
left_span: Span,
right_type: &Type,
expr_span: Span,
) -> Result<(), SemanticError> {
let Some((class_name, symbol)) = self.resolve_lvalue_class_symbol(obj_expr)? else {
return Ok(());
};
self.check_const_field_assignment(field, &symbol.fields, expr_span)?;
if let Some((field_type, _)) = symbol.fields.get(field) {
self.check_type_compatibility(field_type, right_type, expr_span)?;
return Ok(());
}
Err(self.field_not_found_error(field, &class_name, left_span))
}
fn validate_index_assignment_target(
&mut self,
target_expr: &ExpressionNode,
right_type: &Type,
expr_span: Span,
) -> Result<(), SemanticError> {
let target_type = self.get_expression_type(target_expr)?;
match target_type {
Type::List(ref elem_type) => {
self.check_type_compatibility(elem_type, right_type, expr_span)
}
Type::Map(_, ref value_type) => {
self.check_type_compatibility(value_type, right_type, expr_span)
}
_ => Err(SemanticError::with_help(
format!(
"Cannot assign to index on type {}",
format_type(&target_type)
),
expr_span,
"Only lists and maps support index assignment. Example: my_list[0] = value, my_map[\"key\"] = value",
)),
}
}
fn validate_compound_assignment_target(
&mut self,
left: &ExpressionNode,
left_type: &Type,
right_type: &Type,
op: &crate::ast::BinaryOp,
expr_span: Span,
op_span: &Span,
) -> Result<(), SemanticError> {
match &left.kind {
crate::ast::ExpressionKind::Identifier(name) => {
self.validate_identifier_compound_target(name, left.span, expr_span)?;
}
crate::ast::ExpressionKind::FieldAccess { .. } => {
self.validate_field_compound_target(
left, left_type, right_type, op, expr_span, op_span,
)?;
}
crate::ast::ExpressionKind::Unary {
op: crate::ast::UnaryOp::Deref,
..
} => {}
_ => {}
}
Ok(())
}
fn validate_identifier_compound_target(
&self,
name: &str,
left_span: Span,
expr_span: Span,
) -> Result<(), SemanticError> {
let symbol = self
.symbol_table
.lookup(name)
.ok_or_else(|| self.undefined_symbol_error("variable", name, left_span))?;
if symbol.kind == SymbolKind::Constant {
return Err(SemanticError::with_help(
format!("Cannot modify constant '{}'", name),
expr_span,
"Constants cannot be modified after initialization. Declare the variable with 'auto' instead of 'const' if you need to change its value.",
));
}
Ok(())
}
fn validate_field_compound_target(
&mut self,
left: &ExpressionNode,
left_type: &Type,
right_type: &Type,
op: &crate::ast::BinaryOp,
expr_span: Span,
op_span: &Span,
) -> Result<(), SemanticError> {
let crate::ast::ExpressionKind::FieldAccess {
expr: obj_expr,
field,
} = &left.kind
else {
return Ok(());
};
let Some((class_name, symbol)) = self.resolve_lvalue_class_symbol(obj_expr)? else {
return Ok(());
};
if let Some((_field_type, is_const)) = symbol.fields.get(field) {
if *is_const {
return Err(SemanticError::with_help(
format!("Cannot modify const field '{}'", field),
expr_span,
"Const fields cannot be modified after initialization. Remove the 'const' modifier from the field declaration if mutation is needed.",
));
}
let base_op = Self::compound_base_op(op);
self.resolve_binary_operator(left_type, right_type, &base_op)
.ok_or_else(|| {
SemanticError::with_help(
format!(
"Operator '{}' is not supported between types {} and {}",
format_binary_op(&base_op),
format_type(left_type),
format_type(right_type)
),
*op_span,
format!(
"The '{}' operator cannot be applied to {} and {}. Ensure both operands have compatible types.",
format_binary_op(&base_op),
format_type(left_type),
format_type(right_type)
),
)
})?;
} else {
return Err(self.field_not_found_error(field, &class_name, left.span));
}
Ok(())
}
fn compound_base_op(op: &crate::ast::BinaryOp) -> crate::ast::BinaryOp {
match op {
crate::ast::BinaryOp::AddAssign => crate::ast::BinaryOp::Add,
crate::ast::BinaryOp::SubtractAssign => crate::ast::BinaryOp::Subtract,
crate::ast::BinaryOp::MultiplyAssign => crate::ast::BinaryOp::Multiply,
crate::ast::BinaryOp::DivideAssign => crate::ast::BinaryOp::Divide,
crate::ast::BinaryOp::ModuloAssign => crate::ast::BinaryOp::Modulo,
_ => unreachable!(),
}
}
fn resolve_module_field(
&self,
module_name: &str,
field: &str,
span: Span,
) -> Result<Type, SemanticError> {
let module_symbols = self.imported_symbols.get(module_name).ok_or_else(|| {
SemanticError::with_help(
format!("Module '{}' not found in imports", module_name),
span,
format!(
"Make sure you have imported '{}' at the top of your file, e.g. import {}",
module_name, module_name
),
)
})?;
let symbol = module_symbols.get(field).ok_or_else(|| {
let available: Vec<&String> = module_symbols.keys().collect();
if available.is_empty() {
SemanticError::new(
format!(
"Module '{}' has no exported symbol '{}'",
module_name, field
),
span,
)
} else {
SemanticError::with_help(
format!(
"Module '{}' has no exported symbol '{}'",
module_name, field
),
span,
format!(
"Available exports: {}",
available
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
),
)
}
})?;
symbol.type_.clone().ok_or_else(|| {
SemanticError::new(
format!(
"Symbol '{}' in module '{}' has no type information",
field, module_name
),
span,
)
})
}
fn resolve_reference_field(
&mut self,
inner_type: &Type,
field: &str,
span: Span,
) -> Result<Type, SemanticError> {
if let Type::Named(name, args) = inner_type {
if let Some(symbol) = self.symbol_table.lookup(name) {
if let Some((field_type, _)) = symbol.fields.get(field) {
return Ok(self.substitute_type_params(field_type, &symbol.type_params, args));
}
if let Some(method_sig) = self.get_method_sig(inner_type, field) {
return Ok(Type::Function {
params: method_sig.params,
returns: Box::new(method_sig.return_type),
default_count: 0,
});
}
return Err(self.field_not_found_error(field, name, span));
}
return Err(self.undefined_symbol_error("type", name, span));
}
if let Some(method_sig) = self.get_method_sig(inner_type, field) {
return Ok(Type::Function {
params: method_sig.params,
returns: Box::new(method_sig.return_type),
default_count: 0,
});
}
Err(SemanticError::with_help(
format!(
"Cannot access field '{}' on type {}",
field,
format_type(inner_type)
),
span,
format!(
"The type {} does not have a field or method named '{}'",
format_type(inner_type),
field
),
))
}
fn resolve_named_field(
&mut self,
expr_type: &Type,
name: &str,
args: &[Type],
field: &str,
span: Span,
) -> Result<Type, SemanticError> {
if let Some(symbol) = self.symbol_table.lookup(name) {
return self
.resolve_field_from_local_symbol(&symbol, args, expr_type, field, name, span);
}
self.resolve_field_from_imported_module(name, field, expr_type, args, span)
}
fn resolve_field_from_local_symbol(
&mut self,
symbol: &Symbol,
args: &[Type],
expr_type: &Type,
field: &str,
name: &str,
span: Span,
) -> Result<Type, SemanticError> {
if let Some((field_type, _)) = symbol.fields.get(field) {
return Ok(self.substitute_type_params(field_type, &symbol.type_params, args));
}
if let Some(method_sig) = self.get_method_sig(expr_type, field) {
return Ok(self.wrap_method_signature(&method_sig));
}
Err(self.field_not_found_error(field, name, span))
}
fn resolve_field_from_imported_module(
&mut self,
name: &str,
field: &str,
expr_type: &Type,
args: &[Type],
span: Span,
) -> Result<Type, SemanticError> {
for module_symbols in self.imported_symbols.values() {
if let Some(class_symbol) = module_symbols.get(name)
&& let Some(method_sig) = class_symbol.methods.get(field)
{
let resolved_sig =
self.resolve_method_sig_for_field(method_sig, &class_symbol.type_params, args);
return Ok(self.wrap_method_signature(&resolved_sig));
}
}
Err(self.method_not_found_error(field, &format_type(expr_type), span))
}
fn resolve_method_sig_for_field(
&self,
method_sig: &MethodSig,
type_params: &[(String, Vec<String>)],
args: &[Type],
) -> MethodSig {
if args.is_empty() {
method_sig.clone()
} else {
self.substitute_method_sig(method_sig, type_params, args)
}
}
fn wrap_method_signature(&self, method_sig: &MethodSig) -> Type {
Type::Function {
params: method_sig.params.clone(),
returns: Box::new(method_sig.return_type.clone()),
default_count: 0,
}
}
fn check_type_compatibility(
&self,
expected: &Type,
actual: &Type,
span: Span,
) -> Result<(), SemanticError> {
let mut temp_unifier = Unifier::new();
temp_unifier.unify(expected, actual, span).map_err(|_| {
SemanticError::new(
format!(
"Type mismatch: expected {}, got {}",
format_type(expected),
format_type(actual)
),
span,
)
})
}
#[allow(clippy::only_used_in_recursion)]
fn substitute_type_param(&self, type_: &Type, param: &str, replacement: &Type) -> Type {
match type_ {
Type::Variable(var) if var == param => replacement.clone(),
Type::Generic(var) if var == param => replacement.clone(),
Type::Named(name, args) if name == param && args.is_empty() => replacement.clone(),
Type::Named(name, args) => Type::Named(
name.clone(),
args.iter()
.map(|a| self.substitute_type_param(a, param, replacement))
.collect(),
),
Type::List(inner) => Type::List(Box::new(self.substitute_type_param(
inner,
param,
replacement,
))),
Type::Set(inner) => Type::Set(Box::new(self.substitute_type_param(
inner,
param,
replacement,
))),
Type::Map(key, value) => Type::Map(
Box::new(self.substitute_type_param(key, param, replacement)),
Box::new(self.substitute_type_param(value, param, replacement)),
),
Type::Optional(inner) => Type::Optional(Box::new(self.substitute_type_param(
inner,
param,
replacement,
))),
Type::Function {
params,
returns,
default_count,
} => Type::Function {
params: params
.iter()
.map(|p| self.substitute_type_param(p, param, replacement))
.collect(),
returns: Box::new(self.substitute_type_param(returns, param, replacement)),
default_count: *default_count,
},
Type::Reference(inner) => Type::Reference(Box::new(self.substitute_type_param(
inner,
param,
replacement,
))),
_ => type_.clone(),
}
}
fn substitute_type_params(
&self,
type_: &Type,
params: &[(String, Vec<String>)],
replacements: &[Type],
) -> Type {
let mut result = type_.clone();
for ((param_name, _), replacement) in params.iter().zip(replacements) {
result = self.substitute_type_param(&result, param_name, replacement);
}
result
}
fn check_method_compatibility(
&self,
interface_sig: &MethodSig,
class_sig: &MethodSig,
span: Span,
) -> Result<(), SemanticError> {
let mut unifier = Unifier::new();
unifier
.unify(&interface_sig.return_type, &class_sig.return_type, span)
.map_err(|e| {
SemanticError::with_help(
format!(
"Return type mismatch in interface implementation: {}",
e.message
),
span,
"The class method's return type must match the interface method's return type",
)
})?;
if interface_sig.params.len() != class_sig.params.len() {
return Err(SemanticError::with_help(
format!(
"Parameter count mismatch: interface expects {} parameter{}, class provides {}",
interface_sig.params.len(),
if interface_sig.params.len() == 1 {
""
} else {
"s"
},
class_sig.params.len()
),
span,
"The class method must have the same number of parameters as the interface method",
));
}
for (i, (int_param, class_param)) in interface_sig
.params
.iter()
.zip(&class_sig.params)
.enumerate()
{
unifier.unify(int_param, class_param, span).map_err(|e| {
SemanticError::with_help(
format!(
"Parameter {} type mismatch in interface implementation: {}",
i, e.message
),
span,
format!(
"Parameter {} must have type {} to match the interface",
i,
format_type(int_param)
),
)
})?;
}
Ok(())
}
fn get_instantiated_constructor_type(
&mut self,
name: &str,
type_args: &[TypeNode],
span: Span,
) -> Result<Type, SemanticError> {
let symbol = self
.symbol_table
.get_cloned(name)
.ok_or_else(|| self.undefined_symbol_error("type", name, span))?;
if symbol.kind != SymbolKind::Class {
return Err(SemanticError::with_help(
format!("'{}' is not a class", name),
span,
format!(
"'{}' is a {}not a class. Only classes can be instantiated with .new()",
name,
match symbol.kind {
SymbolKind::Function => "a function, ",
SymbolKind::Variable => "a variable, ",
SymbolKind::Interface => "an interface, ",
SymbolKind::Enum => "an enum, ",
SymbolKind::Constant => "a constant, ",
SymbolKind::Import => "an import, ",
SymbolKind::Type => "a type parameter, ",
_ => "",
}
),
));
}
let resolved_args = type_args
.iter()
.map(|arg| self.resolve_type(arg))
.collect::<Result<Vec<_>, _>>()?;
if resolved_args.len() != symbol.type_params.len() {
return Err(SemanticError::with_help(
format!(
"Expected {} type argument(s) for '{}', got {}",
symbol.type_params.len(),
name,
resolved_args.len()
),
span,
format!(
"Class '{}' requires {} type parameter(s). Example: {}<{}>",
name,
symbol.type_params.len(),
name,
symbol
.type_params
.iter()
.map(|(p, _)| p.as_str())
.collect::<Vec<_>>()
.join(", ")
),
));
}
let new_sig = symbol.methods.get("new").ok_or_else(|| SemanticError::with_help(
format!("Class '{}' has no constructor", name),
span,
format!(
"Class '{}' does not have a .new() method. Ensure the class has fields or a constructor defined.",
name
),
))?;
let substituted_params = new_sig
.params
.iter()
.map(|p| self.substitute_type_params(p, &symbol.type_params, &resolved_args))
.collect();
let substituted_returns =
self.substitute_type_params(&new_sig.return_type, &symbol.type_params, &resolved_args);
Ok(Type::Function {
params: substituted_params,
returns: Box::new(substituted_returns),
default_count: 0,
})
}
fn type_implements_interface(&self, type_: &Type, interface_name: &str) -> bool {
self.type_implements_interface_with_args(type_, interface_name, &[])
}
fn type_implements_interface_with_args(
&self,
type_: &Type,
interface_name: &str,
interface_args: &[Type],
) -> bool {
match type_ {
Type::Named(name, _) => {
self.type_implements_interface_with_named(name, interface_name, interface_args)
}
Type::Primitive(prim) => {
self.type_implements_interface_with_primitive(prim, interface_name)
}
Type::Variable(var) | Type::Generic(var) => {
self.type_implements_interface_with_variable(var, interface_name, interface_args)
}
_ => false,
}
}
fn type_implements_interface_with_named(
&self,
name: &str,
interface_name: &str,
interface_args: &[Type],
) -> bool {
if let Some(symbol) = self.symbol_table.lookup(name) {
if let Some((stored_args, _)) = symbol.interfaces.get(interface_name) {
if interface_args.is_empty() {
return true;
}
if let Some(interface_symbol) = self.symbol_table.lookup(interface_name)
&& interface_symbol.type_params.len() != interface_args.len()
{
return false;
}
stored_args == interface_args
} else {
false
}
} else {
false
}
}
fn type_implements_interface_with_primitive(
&self,
prim: &PrimitiveType,
interface_name: &str,
) -> bool {
match prim {
PrimitiveType::Str => {
interface_name == "Stringable"
|| interface_name == "Equatable"
|| interface_name == "Comparable"
|| interface_name == "Hashable"
|| interface_name == "Error"
}
PrimitiveType::Int => {
matches!(
interface_name,
"Stringable" | "Equatable" | "Comparable" | "Hashable"
)
}
PrimitiveType::Float => {
matches!(
interface_name,
"Stringable" | "Equatable" | "Comparable" | "Hashable"
)
}
PrimitiveType::Bool => {
matches!(interface_name, "Stringable" | "Equatable" | "Hashable")
}
PrimitiveType::Char => {
matches!(
interface_name,
"Stringable" | "Equatable" | "Comparable" | "Hashable"
)
}
_ => false,
}
}
fn type_implements_interface_with_variable(
&self,
var: &str,
interface_name: &str,
interface_args: &[Type],
) -> bool {
if let Some(bounds) = self.current_bounds.get(var) {
bounds.iter().any(|(bound_name, bound_args)| {
bound_name == interface_name
&& (interface_args.is_empty() || bound_args == interface_args)
})
} else {
false
}
}
fn get_builtin_interface_method(
&self,
interface_name: &str,
method_name: &str,
) -> Option<MethodSig> {
match interface_name {
"Stringable" => match method_name {
"to_string" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Str),
is_static: false,
}),
_ => None,
},
"Equatable" => match method_name {
"eq" => Some(MethodSig {
params: vec![Type::Generic("Self".to_string())],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}),
_ => None,
},
"Comparable" => match method_name {
"cmp" => Some(MethodSig {
params: vec![Type::Generic("Self".to_string())],
return_type: Type::Primitive(PrimitiveType::Int),
is_static: false,
}),
_ => None,
},
"Hashable" => match method_name {
"hash" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Int),
is_static: false,
}),
_ => None,
},
"Error" => match method_name {
"message" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Str),
is_static: false,
}),
_ => None,
},
_ => None,
}
}
fn get_named_method_sig(
&self,
name: &str,
args: &[Type],
method_name: &str,
) -> Option<MethodSig> {
let symbol = self.symbol_table.lookup(name)?;
if let Some(sig) = symbol.methods.get(method_name) {
return Some(if args.is_empty() {
sig.clone()
} else {
self.substitute_method_sig(sig, &symbol.type_params, args)
});
}
self.find_interface_method(&symbol, method_name)
}
fn get_variable_generic_method_sig(&self, var: &str, method_name: &str) -> Option<MethodSig> {
let bounds = self.current_bounds.get(var)?;
for (bound_name, bound_args) in bounds {
if let Some(sig) = self.get_builtin_interface_method(bound_name, method_name) {
return Some(sig);
}
if let Some(interface_symbol) = self.symbol_table.lookup(bound_name)
&& let Some(sig) = self.find_interface_method(&interface_symbol, method_name)
{
if bound_args.is_empty() {
return Some(sig);
}
return Some(self.substitute_method_sig(
&sig,
&interface_symbol.type_params,
bound_args,
));
}
}
None
}
fn get_primitive_method_sig(
&self,
prim: &PrimitiveType,
method_name: &str,
) -> Option<MethodSig> {
use PrimitiveType::{Bool, Char, Float, Int, Str};
let resolver = match prim {
Int => Some(Self::get_int_method_sig as fn(&Self, &str) -> Option<MethodSig>),
Float => Some(Self::get_float_method_sig as fn(&Self, &str) -> Option<MethodSig>),
Str => Some(Self::get_string_method_sig as fn(&Self, &str) -> Option<MethodSig>),
Bool => Some(Self::get_bool_method_sig as fn(&Self, &str) -> Option<MethodSig>),
Char => Some(Self::get_char_method_sig as fn(&Self, &str) -> Option<MethodSig>),
PrimitiveType::Void | PrimitiveType::Auto => None,
};
resolver.and_then(|resolve| resolve(self, method_name))
}
fn make_instance_method_sig(params: Vec<Type>, return_type: Type) -> MethodSig {
MethodSig {
params,
return_type,
is_static: false,
}
}
fn make_eq_method_sig(param_type: PrimitiveType) -> MethodSig {
Self::make_instance_method_sig(
vec![Type::Primitive(param_type)],
Type::Primitive(PrimitiveType::Bool),
)
}
fn make_cmp_method_sig(param_type: PrimitiveType) -> MethodSig {
Self::make_instance_method_sig(
vec![Type::Primitive(param_type)],
Type::Primitive(PrimitiveType::Int),
)
}
fn make_hash_method_sig() -> MethodSig {
Self::make_instance_method_sig(vec![], Type::Primitive(PrimitiveType::Int))
}
fn make_to_string_method_sig() -> MethodSig {
Self::make_instance_method_sig(vec![], Type::Primitive(PrimitiveType::Str))
}
fn make_str_parse_result_method_sig(value_type: PrimitiveType) -> MethodSig {
Self::make_instance_method_sig(
vec![],
Type::Result(
Box::new(Type::Primitive(value_type)),
Box::new(Type::Primitive(PrimitiveType::Str)),
),
)
}
fn get_int_method_sig(&self, method_name: &str) -> Option<MethodSig> {
match method_name {
"to_string" => Some(Self::make_to_string_method_sig()),
"to_float" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Float),
)),
"to_int" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Int),
)),
"to_char" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Char),
)),
"eq" => Some(Self::make_eq_method_sig(PrimitiveType::Int)),
"cmp" => Some(Self::make_cmp_method_sig(PrimitiveType::Int)),
"hash" => Some(Self::make_hash_method_sig()),
_ => None,
}
}
fn get_float_method_sig(&self, method_name: &str) -> Option<MethodSig> {
match method_name {
"to_string" => Some(Self::make_to_string_method_sig()),
"to_int" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Int),
)),
"to_float" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Float),
)),
"eq" => Some(Self::make_eq_method_sig(PrimitiveType::Float)),
"cmp" => Some(Self::make_cmp_method_sig(PrimitiveType::Float)),
"hash" => Some(Self::make_hash_method_sig()),
_ => None,
}
}
fn get_string_method_sig(&self, method_name: &str) -> Option<MethodSig> {
match method_name {
"to_string" | "message" => Some(Self::make_to_string_method_sig()),
"length" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Int),
)),
"to_int" => Some(Self::make_str_parse_result_method_sig(PrimitiveType::Int)),
"to_float" => Some(Self::make_str_parse_result_method_sig(PrimitiveType::Float)),
"to_char" => Some(Self::make_str_parse_result_method_sig(PrimitiveType::Char)),
"eq" => Some(Self::make_eq_method_sig(PrimitiveType::Str)),
"cmp" => Some(Self::make_cmp_method_sig(PrimitiveType::Str)),
"hash" => Some(Self::make_hash_method_sig()),
_ => None,
}
}
fn get_bool_method_sig(&self, method_name: &str) -> Option<MethodSig> {
match method_name {
"to_string" => Some(Self::make_to_string_method_sig()),
"to_int" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Int),
)),
"to_float" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Float),
)),
"eq" => Some(Self::make_eq_method_sig(PrimitiveType::Bool)),
"hash" => Some(Self::make_hash_method_sig()),
_ => None,
}
}
fn get_char_method_sig(&self, method_name: &str) -> Option<MethodSig> {
match method_name {
"to_string" => Some(Self::make_to_string_method_sig()),
"to_int" => Some(Self::make_str_parse_result_method_sig(PrimitiveType::Int)),
"to_char" => Some(Self::make_instance_method_sig(
vec![],
Type::Primitive(PrimitiveType::Char),
)),
"eq" => Some(Self::make_eq_method_sig(PrimitiveType::Char)),
"cmp" => Some(Self::make_cmp_method_sig(PrimitiveType::Char)),
"hash" => Some(Self::make_hash_method_sig()),
_ => None,
}
}
fn get_list_method_sig(&self, elem_type: &Type, method_name: &str) -> Option<MethodSig> {
match method_name {
"push_back" => Some(MethodSig {
params: vec![elem_type.clone()],
return_type: Type::Void,
is_static: false,
}),
"pop_back" => Some(MethodSig {
params: vec![],
return_type: Type::Optional(Box::new(elem_type.clone())),
is_static: false,
}),
"push" => Some(MethodSig {
params: vec![elem_type.clone()],
return_type: Type::Void,
is_static: false,
}),
"pop" => Some(MethodSig {
params: vec![],
return_type: Type::Optional(Box::new(elem_type.clone())),
is_static: false,
}),
"get" => Some(MethodSig {
params: vec![Type::Primitive(PrimitiveType::Int)],
return_type: Type::Optional(Box::new(elem_type.clone())),
is_static: false,
}),
"is_empty" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}),
"size" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Int),
is_static: false,
}),
"to_string" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Str),
is_static: false,
}),
_ => None,
}
}
fn get_map_method_sig(
&self,
key_type: &Type,
value_type: &Type,
method_name: &str,
) -> Option<MethodSig> {
match method_name {
"put" => Some(MethodSig {
params: vec![key_type.clone(), value_type.clone()],
return_type: Type::Void,
is_static: false,
}),
"get" => Some(MethodSig {
params: vec![key_type.clone()],
return_type: Type::Optional(Box::new(value_type.clone())),
is_static: false,
}),
"get_keys" => Some(MethodSig {
params: vec![],
return_type: Type::List(Box::new(key_type.clone())),
is_static: false,
}),
"get_values" => Some(MethodSig {
params: vec![],
return_type: Type::List(Box::new(value_type.clone())),
is_static: false,
}),
"get_pairs" => Some(MethodSig {
params: vec![],
return_type: Type::List(Box::new(Type::Tuple(
Box::new(key_type.clone()),
Box::new(value_type.clone()),
))),
is_static: false,
}),
"contains" => Some(MethodSig {
params: vec![key_type.clone()],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}),
"remove" => Some(MethodSig {
params: vec![key_type.clone()],
return_type: Type::Optional(Box::new(value_type.clone())),
is_static: false,
}),
"size" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Int),
is_static: false,
}),
"is_empty" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}),
"to_string" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Str),
is_static: false,
}),
_ => None,
}
}
fn get_set_method_sig(&self, elem_type: &Type, method_name: &str) -> Option<MethodSig> {
match method_name {
"add" => Some(MethodSig {
params: vec![elem_type.clone()],
return_type: Type::Void,
is_static: false,
}),
"remove" => Some(MethodSig {
params: vec![elem_type.clone()],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}),
"contains" => Some(MethodSig {
params: vec![elem_type.clone()],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}),
"size" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Int),
is_static: false,
}),
"is_empty" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}),
"to_string" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Str),
is_static: false,
}),
"to_list" => Some(MethodSig {
params: vec![],
return_type: Type::List(Box::new(elem_type.clone())),
is_static: false,
}),
_ => None,
}
}
fn get_optional_method_sig(&self, method_name: &str) -> Option<MethodSig> {
fn bool_method_sig() -> MethodSig {
MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}
}
match method_name {
"to_string" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Str),
is_static: false,
}),
"is_some" | "is_none" => Some(bool_method_sig()),
_ => None,
}
}
fn get_result_method_sig(&self, method_name: &str) -> Option<MethodSig> {
fn bool_method_sig() -> MethodSig {
MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Bool),
is_static: false,
}
}
match method_name {
"to_string" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Str),
is_static: false,
}),
"is_ok" | "is_err" => Some(bool_method_sig()),
_ => None,
}
}
fn get_tuple_method_sig(&self, method_name: &str) -> Option<MethodSig> {
match method_name {
"to_string" => Some(MethodSig {
params: vec![],
return_type: Type::Primitive(PrimitiveType::Str),
is_static: false,
}),
"new" => Some(MethodSig {
params: vec![],
return_type: Type::Tuple(
Box::new(Type::Primitive(PrimitiveType::Int)),
Box::new(Type::Primitive(PrimitiveType::Str)),
),
is_static: true,
}),
_ => None,
}
}
pub(crate) fn get_method_sig(&self, type_: &Type, method_name: &str) -> Option<MethodSig> {
match type_ {
Type::Named(name, args) => self.get_named_method_sig(name, args, method_name),
Type::Variable(var) | Type::Generic(var) => {
self.get_variable_generic_method_sig(var, method_name)
}
Type::Primitive(prim) => self.get_primitive_method_sig(prim, method_name),
Type::List(elem_type) => self.get_list_method_sig(elem_type, method_name),
Type::Map(key_type, value_type) => {
self.get_map_method_sig(key_type, value_type, method_name)
}
Type::Set(elem_type) => self.get_set_method_sig(elem_type, method_name),
Type::Optional(_) => self.get_optional_method_sig(method_name),
Type::Result(_, _) => self.get_result_method_sig(method_name),
Type::Tuple(_, _) => self.get_tuple_method_sig(method_name),
Type::Reference(inner) => self.get_method_sig(inner, method_name),
_ => None,
}
}
fn find_interface_method(
&self,
symbol: &crate::semantics::Symbol,
method_name: &str,
) -> Option<MethodSig> {
for (_, interface_methods) in symbol.interfaces.values() {
if let Some(sig) = interface_methods.get(method_name) {
return Some(sig.clone());
}
}
None
}
fn substitute_method_sig(
&self,
sig: &MethodSig,
type_params: &[(String, Vec<String>)],
args: &[Type],
) -> MethodSig {
let substituted_params = sig
.params
.iter()
.map(|p| self.substitute_type_params(p, type_params, args))
.collect();
let substituted_return = self.substitute_type_params(&sig.return_type, type_params, args);
MethodSig {
params: substituted_params,
return_type: substituted_return,
is_static: false,
}
}
fn resolve_binary_operator(
&self,
left_type: &Type,
right_type: &Type,
op: &BinaryOp,
) -> Option<Type> {
if matches!(op, BinaryOp::In) {
return self.resolve_in_binary_operator(left_type, right_type);
}
if left_type != right_type {
return None;
}
self.resolve_equal_type_binary_operator(left_type, right_type, op)
}
fn resolve_in_binary_operator(&self, left_type: &Type, right_type: &Type) -> Option<Type> {
match right_type {
Type::List(_) | Type::Set(_) => Some(Type::Primitive(crate::ast::PrimitiveType::Bool)),
Type::Map(key_type, _) => {
if left_type == key_type.as_ref() {
Some(Type::Primitive(crate::ast::PrimitiveType::Bool))
} else {
None
}
}
Type::Primitive(PrimitiveType::Str) => {
if matches!(
left_type,
Type::Primitive(PrimitiveType::Char | PrimitiveType::Str)
) {
Some(Type::Primitive(crate::ast::PrimitiveType::Bool))
} else {
None
}
}
_ => None,
}
}
fn resolve_equal_type_binary_operator(
&self,
left_type: &Type,
right_type: &Type,
op: &BinaryOp,
) -> Option<Type> {
match op {
BinaryOp::Add => self.resolve_add_binary_operator(left_type),
BinaryOp::Subtract
| BinaryOp::Multiply
| BinaryOp::Divide
| BinaryOp::Modulo
| BinaryOp::Exponent => self.resolve_numeric_binary_operator(left_type),
BinaryOp::Equal | BinaryOp::NotEqual => {
Some(Type::Primitive(crate::ast::PrimitiveType::Bool))
}
BinaryOp::Less | BinaryOp::LessEqual | BinaryOp::Greater | BinaryOp::GreaterEqual => {
self.resolve_comparison_binary_operator(left_type)
}
BinaryOp::LogicalAnd | BinaryOp::LogicalOr => {
self.resolve_logical_binary_operator(left_type, right_type)
}
_ => None,
}
}
fn resolve_add_binary_operator(&self, left_type: &Type) -> Option<Type> {
if matches!(
left_type,
Type::Primitive(PrimitiveType::Str | PrimitiveType::Int | PrimitiveType::Float)
| Type::List(_)
| Type::Map(_, _)
| Type::Set(_)
) {
Some(left_type.clone())
} else {
None
}
}
fn resolve_numeric_binary_operator(&self, left_type: &Type) -> Option<Type> {
if matches!(
left_type,
Type::Primitive(PrimitiveType::Int | PrimitiveType::Float)
) {
Some(left_type.clone())
} else {
None
}
}
fn resolve_comparison_binary_operator(&self, left_type: &Type) -> Option<Type> {
if matches!(
left_type,
Type::Primitive(PrimitiveType::Int | PrimitiveType::Float | PrimitiveType::Str)
) || self.type_implements_interface(left_type, "Comparable")
{
Some(Type::Primitive(crate::ast::PrimitiveType::Bool))
} else {
None
}
}
fn resolve_logical_binary_operator(&self, left_type: &Type, right_type: &Type) -> Option<Type> {
if matches!(left_type, Type::Primitive(PrimitiveType::Bool))
&& matches!(right_type, Type::Primitive(PrimitiveType::Bool))
{
Some(Type::Primitive(crate::ast::PrimitiveType::Bool))
} else {
None
}
}
}
pub(crate) fn infer_missing_type_params_from_bounds(
type_params: &[(String, Vec<crate::ast::TraitBound>)],
substitutions: &mut std::collections::HashMap<String, Type>,
) {
for (missing_param_name, _) in type_params {
if substitutions.contains_key(missing_param_name) {
continue;
}
if let Some(inferred_type) =
infer_missing_param_from_bounds(missing_param_name, type_params, substitutions)
{
substitutions.insert(missing_param_name.clone(), inferred_type);
}
}
}
fn infer_missing_param_from_bounds(
missing_param_name: &str,
type_params: &[(String, Vec<crate::ast::TraitBound>)],
substitutions: &std::collections::HashMap<String, Type>,
) -> Option<Type> {
type_params
.iter()
.find_map(|(owner_param_name, owner_bounds)| {
substitutions
.get(owner_param_name)
.and_then(owner_concrete_type_args)
.filter(|owner_type_args| !owner_type_args.is_empty())
.and_then(|owner_type_args| {
infer_bound_type_arg(missing_param_name, owner_bounds, owner_type_args)
})
})
}
fn owner_concrete_type_args(owner_concrete_type: &Type) -> Option<&[Type]> {
match owner_concrete_type {
Type::Named(_, args) => Some(args.as_slice()),
Type::Reference(inner) => match inner.as_ref() {
Type::Named(_, args) => Some(args.as_slice()),
_ => None,
},
_ => None,
}
}
fn infer_bound_type_arg(
missing_param_name: &str,
owner_bounds: &[crate::ast::TraitBound],
owner_type_args: &[Type],
) -> Option<Type> {
for bound in owner_bounds {
for (idx, bound_type_arg) in bound.type_params.iter().enumerate() {
if let TypeKind::Named(bound_name, _) = &bound_type_arg.kind
&& bound_name == missing_param_name
&& let Some(concrete_arg) = owner_type_args.get(idx)
{
return Some(concrete_arg.clone());
}
}
}
None
}
use crate::semantics::symbol_table::{
calculate_similarity_threshold, edit_distance as levenshtein_distance,
};
fn collection_new_hint(name: &str) -> Option<&'static str> {
match name {
"list" => Some(
"List has no '.new()' constructor. Use '[]' for an empty list or '[1, 2, 3]' for elements. For example: list<int> my_list = []",
),
"map" => Some(
"Map has no '.new()' constructor. Use '{}' for an empty map or '{\"key\": \"value\"}' for entries. For example: map<string, int> my_map = {}",
),
"set" => Some(
"Set has no '.new()' constructor. Use '{}' for an empty set or '{1, 2, 3}' for elements. For example: set<int> my_set = {}",
),
"optional" => Some(
"Optional has no '.new()' constructor. Use 'none' for an empty value or 'some(value)' for a value. For example: optional<int> my_opt = none",
),
"result" => Some(
"Result has no '.new()' constructor. Use 'ok(value)' or 'err(message)' to construct one. For example: result<int, string> my_res = ok(42)",
),
_ => None,
}
}