use super::const_fold::{self, ConstValue};
use super::{SemanticAnalyzer, SemanticError, SymbolKind, Type, format_type};
use crate::ast::{
AstNode, EnumVariant, ExpressionKind, ExpressionNode, Field, FunctionNode, PrimitiveType,
TraitBound, TraitRef, WhereClause,
};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct ClassInvariant {
pub predicate: ExpressionNode,
pub referenced_fields: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct InheritedPrecondition {
pub interface_param_names: Vec<String>,
pub predicates: Vec<ExpressionNode>,
}
impl SemanticAnalyzer {
pub(super) fn analyze_where_clause(&mut self, clause: &WhereClause) {
for predicate in &clause.predicates {
if let Err(e) = self.analyze_expression(predicate) {
self.errors.push(e);
continue;
}
match self.get_expression_type(predicate) {
Ok(Type::Primitive(PrimitiveType::Bool)) => {
if const_fold::fold(predicate) == Some(ConstValue::Bool(false)) {
self.errors.push(SemanticError::with_help(
"where predicate is always false",
predicate.span,
"This predicate can never hold, so every check of it would panic. Fix the condition or remove it.",
));
}
}
Ok(other) => self.errors.push(SemanticError::with_help(
format!(
"where predicate must be a bool, got {}",
format_type(&other)
),
predicate.span,
"Each predicate in a where block must evaluate to a bool. Example: where { value > 0 }",
)),
Err(e) => self.errors.push(e),
}
}
}
pub(super) fn collect_interface_preconditions(&mut self, ast: &[AstNode]) {
let mut map = HashMap::new();
for nodes in self.all_module_asts.values() {
Self::extract_interface_preconditions(nodes, &mut map);
}
Self::extract_interface_preconditions(ast, &mut map);
self.interface_preconditions = map;
}
fn extract_interface_preconditions(
nodes: &[AstNode],
map: &mut HashMap<String, HashMap<String, InheritedPrecondition>>,
) {
for node in nodes {
let AstNode::Interface { name, methods, .. } = node else {
continue;
};
for method in methods {
let Some(clause) = &method.where_clause else {
continue;
};
map.entry(name.clone()).or_default().insert(
method.name.clone(),
InheritedPrecondition {
interface_param_names: method
.params
.iter()
.map(|p| p.name.clone())
.collect(),
predicates: clause.predicates.clone(),
},
);
}
}
}
pub(super) fn analyze_interface_where_clauses(
&mut self,
type_params: &[(String, Vec<TraitBound>)],
methods: &[FunctionNode],
) -> Result<(), SemanticError> {
for method in methods {
let Some(clause) = &method.where_clause else {
continue;
};
self.symbol_table.push_scope()?;
for (param_name, _) in type_params.iter().chain(method.type_params.iter()) {
self.symbol_table.add_symbol(
param_name,
Self::make_symbol(
SymbolKind::Type,
method.span,
Some(Type::Generic(param_name.clone())),
),
)?;
}
for param in &method.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_where_clause(clause);
self.symbol_table.pop_scope()?;
}
Ok(())
}
pub(super) fn analyze_enum_where_clauses(
&mut self,
variants: &[EnumVariant],
) -> Result<(), SemanticError> {
for variant in variants {
let Some(clause) = &variant.where_clause else {
continue;
};
self.symbol_table.push_scope()?;
for (field_name, field_type) in variant.data.iter().flatten() {
let Some(field_name) = field_name else {
continue;
};
let field_type = self.resolve_type(field_type)?;
self.symbol_table.add_symbol(
field_name,
Self::make_symbol(SymbolKind::Variable, clause.span, Some(field_type)),
)?;
}
self.analyze_where_clause(clause);
self.symbol_table.pop_scope()?;
}
Ok(())
}
pub(super) fn analyze_class_where_clauses(
&mut self,
name: &str,
fields: &[Field],
methods: &[FunctionNode],
traits: &[TraitRef],
class_where: Option<&WhereClause>,
) -> Result<(), SemanticError> {
let field_names: HashSet<String> = fields.iter().map(|f| f.name.clone()).collect();
self.symbol_table.push_scope()?;
for field in fields {
let field_type = self.resolve_type(&field.type_)?;
let _ = self.symbol_table.add_symbol(
&field.name,
Self::make_symbol(SymbolKind::Variable, field.type_.span, Some(field_type)),
);
}
let construction_env = self.fold_field_defaults(fields);
let mut invariants = Vec::new();
let clauses = fields
.iter()
.filter_map(|f| f.where_clause.as_ref())
.chain(class_where);
for clause in clauses {
self.analyze_where_clause(clause);
for predicate in &clause.predicates {
if const_fold::fold(predicate) != Some(ConstValue::Bool(false))
&& const_fold::fold_with_env(predicate, &construction_env)
== Some(ConstValue::Bool(false))
{
self.errors.push(SemanticError::with_help(
"field defaults violate this where constraint at construction",
predicate.span,
"Invariants are checked when .new() runs, after field defaults are applied, so every construction would panic. Give the constrained fields defaults that satisfy this predicate.",
));
}
invariants.push(ClassInvariant {
referenced_fields: referenced_field_names(predicate, &field_names),
predicate: predicate.clone(),
});
}
}
self.symbol_table.pop_scope()?;
if !invariants.is_empty() {
self.class_invariants.insert(name.to_string(), invariants);
}
self.record_inherited_preconditions(name, methods, traits);
Ok(())
}
fn fold_field_defaults(&self, fields: &[Field]) -> HashMap<String, ConstValue> {
let mut env = HashMap::new();
for field in fields {
if field.is_generic_param {
continue;
}
let value = if let Some(default) = &field.default_value {
const_fold::fold(default)
} else {
match self.resolve_type(&field.type_) {
Ok(Type::Primitive(PrimitiveType::Int)) => Some(ConstValue::Int(0)),
Ok(Type::Primitive(PrimitiveType::Float)) => Some(ConstValue::Float(0.0)),
Ok(Type::Primitive(PrimitiveType::Bool)) => Some(ConstValue::Bool(false)),
Ok(Type::Primitive(PrimitiveType::Str)) => Some(ConstValue::Str(String::new())),
_ => None,
}
};
if let Some(value) = value {
env.insert(field.name.clone(), value);
}
}
env
}
fn record_inherited_preconditions(
&mut self,
class_name: &str,
methods: &[FunctionNode],
traits: &[TraitRef],
) {
let mut inherited: HashMap<String, Vec<InheritedPrecondition>> = HashMap::new();
for trait_ref in traits {
let Some(interface_methods) = self.interface_preconditions.get(&trait_ref.name) else {
continue;
};
for (method_name, precondition) in interface_methods {
if methods.iter().any(|m| m.name == *method_name) {
inherited
.entry(method_name.clone())
.or_default()
.push(precondition.clone());
}
}
}
if !inherited.is_empty() {
self.inherited_preconditions
.insert(class_name.to_string(), inherited);
}
}
}
fn referenced_field_names(predicate: &ExpressionNode, fields: &HashSet<String>) -> Vec<String> {
let mut found = HashSet::new();
let mut contains_lambda = false;
collect_identifiers(predicate, fields, &mut found, &mut contains_lambda);
if contains_lambda {
return fields.iter().cloned().collect();
}
found.into_iter().collect()
}
fn collect_identifiers(
expr: &ExpressionNode,
fields: &HashSet<String>,
found: &mut HashSet<String>,
contains_lambda: &mut bool,
) {
match &expr.kind {
ExpressionKind::Identifier(name) => {
if fields.contains(name) {
found.insert(name.clone());
}
}
ExpressionKind::Binary { left, right, .. } => {
collect_identifiers(left, fields, found, contains_lambda);
collect_identifiers(right, fields, found, contains_lambda);
}
ExpressionKind::Unary { expr, .. } => {
collect_identifiers(expr, fields, found, contains_lambda);
}
ExpressionKind::Call { func, args } => {
collect_identifiers(func, fields, found, contains_lambda);
for arg in args {
collect_identifiers(arg, fields, found, contains_lambda);
}
}
ExpressionKind::FieldAccess { expr, .. } => {
collect_identifiers(expr, fields, found, contains_lambda);
}
ExpressionKind::ListAccess { expr, index } => {
collect_identifiers(expr, fields, found, contains_lambda);
collect_identifiers(index, fields, found, contains_lambda);
}
ExpressionKind::ListLiteral(elements)
| ExpressionKind::SetOrMapLiteral(elements)
| ExpressionKind::TupleLiteral(elements) => {
for element in elements {
collect_identifiers(element, fields, found, contains_lambda);
}
}
ExpressionKind::MapLiteral { entries, .. } => {
for (key, value) in entries {
collect_identifiers(key, fields, found, contains_lambda);
collect_identifiers(value, fields, found, contains_lambda);
}
}
ExpressionKind::If {
cond,
then_expr,
else_expr,
} => {
collect_identifiers(cond, fields, found, contains_lambda);
collect_identifiers(then_expr, fields, found, contains_lambda);
collect_identifiers(else_expr, fields, found, contains_lambda);
}
ExpressionKind::Lambda { .. } => {
*contains_lambda = true;
}
ExpressionKind::Literal(_) | ExpressionKind::None | ExpressionKind::GenericType(..) => {}
}
}