use crate::eval::PrimitiveType; use crate::syntax::{Expression, Location, Program, StructureDefinition, TopLevel}; use crate::util::scoped_map::ScopedMap; use crate::util::warning_result::WarningResult; use codespan_reporting::diagnostic::Diagnostic; use std::collections::HashMap; use std::str::FromStr; use super::{FunctionDefinition, Name, Type}; /// An error we found while validating the input program. /// /// These errors indicate that we should stop trying to compile /// the program, because it's just fundamentally broken in a way /// that we're not going to be able to work through. As with most /// of these errors, we recommend converting this to a [`Diagnostic`] /// and using [`codespan_reporting`] to present them to the user. #[derive(Debug)] pub enum Error { UnboundVariable(Location, String), UnknownType(Location, String), } impl From for Diagnostic { fn from(x: Error) -> Self { match &x { Error::UnboundVariable(location, name) => location .labelled_error("unbound here") .with_message(format!("Unbound variable '{}'", name)), Error::UnknownType(location, name) => location .labelled_error("type referenced here") .with_message(format!("Unknown type '{}'", name)), } } } /// A problem we found validating the input that isn't critical. /// /// These are things that the user might want to do something about, /// but we can keep going without it being a problem. As with most of /// these things, if you want to present this information to the user, /// the best way to do so is via [`From`] and [`Diagnostic`], and then /// interactions via [`codespan_reporting`]. #[derive(Debug, PartialEq, Eq)] pub enum Warning { ShadowedVariable(Location, Location, String), } impl From for Diagnostic { fn from(x: Warning) -> Self { match &x { Warning::ShadowedVariable(original, new, name) => Diagnostic::warning() .with_labels(vec![ new.primary_label().with_message("variable rebound here"), original .secondary_label() .with_message("original binding site"), ]) .with_message(format!("Variable '{}' is rebound", name)), } } } impl Program { /// Validate that the program makes semantic sense, not just syntactic sense. /// /// This checks for things like references to variables that don't exist, for /// example, and generates warnings for things that are inadvisable but not /// actually a problem. pub fn validate(raw_syntax: Vec) -> WarningResult { let mut bound_variables = ScopedMap::new(); Self::validate_with_bindings(raw_syntax, &mut bound_variables) } /// Validate that the program makes semantic sense, not just syntactic sense. /// /// This checks for things like references to variables that don't exist, for /// example, and generates warnings for things that are inadvisable but not /// actually a problem. pub fn validate_with_bindings( raw_syntax: Vec, bound_variables: &mut ScopedMap, ) -> WarningResult { let mut functions = HashMap::new(); let mut structures = HashMap::new(); let mut result = WarningResult::ok(vec![]); let location = Location::infer_from(&raw_syntax); for stmt in raw_syntax.into_iter() { match stmt { TopLevel::Expression(expr) => { let expr_result = expr.validate(bound_variables, &mut structures, &mut functions); result = result.merge_with(expr_result, |mut previous, current| { previous.push(current); Ok(previous) }); } TopLevel::Structure(loc, name, fields) => { let definition = StructureDefinition::new( loc, name.clone(), fields.into_iter().map(|(n, t)| (n, Some(t))).collect(), ); structures.insert(name, definition); } } } result.map(move |exprs| Program { functions, structures, body: Expression::Block(location, exprs), }) } } impl Expression { fn validate( self, variable_map: &mut ScopedMap, structure_map: &mut HashMap, function_map: &mut HashMap, ) -> WarningResult { match self { Expression::Value(_, _) => WarningResult::ok(self), Expression::Constructor(location, name, fields) => { let mut result = WarningResult::ok(vec![]); for (name, expr) in fields.into_iter() { let expr_result = expr.validate(variable_map, structure_map, function_map); result = result.merge_with(expr_result, move |mut fields, new_expr| { fields.push((name, new_expr)); Ok(fields) }); } result.map(move |fields| Expression::Constructor(location, name, fields)) } Expression::Reference(ref var) if variable_map.contains_key(&var.original_name().to_string()) => { WarningResult::ok(self) } Expression::Reference(var) => WarningResult::err(Error::UnboundVariable( var.location().clone(), var.original_name().to_string(), )), Expression::FieldRef(location, exp, field) => exp .validate(variable_map, structure_map, function_map) .map(|x| Expression::FieldRef(location, Box::new(x), field)), Expression::Cast(location, t, expr) => { let mut expr_result = expr.validate(variable_map, structure_map, function_map); if PrimitiveType::from_str(&t).is_err() { expr_result.add_error(Error::UnknownType(location.clone(), t.clone())); } expr_result.map(|e| Expression::Cast(location, t, Box::new(e))) } // FIXME: Check for valid primitives here!! Expression::Primitive(_, _) => WarningResult::ok(self), Expression::Call(loc, func, args) => { let mut result = func .validate(variable_map, structure_map, function_map) .map(|x| (x, vec![])); for arg in args.into_iter() { let expr_result = arg.validate(variable_map, structure_map, function_map); result = result.merge_with(expr_result, |(func, mut previous_args), new_arg| { previous_args.push(new_arg); Ok((func, previous_args)) }); } result.map(|(func, args)| Expression::Call(loc, Box::new(func), args)) } Expression::Block(loc, stmts) => { let mut result = WarningResult::ok(vec![]); for stmt in stmts.into_iter() { let stmt_result = stmt.validate(variable_map, structure_map, function_map); result = result.merge_with(stmt_result, |mut stmts, stmt| { stmts.push(stmt); Ok(stmts) }); } result.map(|stmts| Expression::Block(loc, stmts)) } Expression::Binding(loc, var, val) => { // we're going to make the decision that a variable is not bound in the right // hand side of its binding, which makes a lot of things easier. So we'll just // immediately check the expression, and go from there. let mut result = val.validate(variable_map, structure_map, function_map); if let Some(original_binding_site) = variable_map.get(&var.original_name().to_string()) { result.add_warning(Warning::ShadowedVariable( original_binding_site.clone(), loc.clone(), var.to_string(), )); } else { variable_map.insert(var.to_string(), loc.clone()); } result.map(|val| Expression::Binding(loc, var, Box::new(val))) } Expression::Function(loc, name, mut arguments, return_type, body) => { let mut result = WarningResult::ok(()); // first we should check for shadowing for new_name in name.iter().chain(arguments.iter().map(|x| &x.0)) { if let Some(original_site) = variable_map.get(new_name.original_name()) { result.add_warning(Warning::ShadowedVariable( original_site.clone(), loc.clone(), new_name.original_name().to_string(), )); } } // the function name is now available in our current scope, if the function was given one if let Some(name) = &name { variable_map.insert(name.original_name().to_string(), name.location().clone()); } // the arguments are available in a new scope, which we will use to validate the function // body variable_map.new_scope(); for (arg, _) in arguments.iter() { variable_map.insert(arg.original_name().to_string(), arg.location().clone()); } let body_result = body.validate(variable_map, structure_map, function_map); variable_map.release_scope(); body_result.merge_with(result, move |mut body, _| { // figure out what, if anything, needs to be in the closure for this function. let mut free_variables = body.free_variables(); for (n, _) in arguments.iter() { free_variables.remove(n); } // generate a new name for the closure type we're about to create let closure_type_name = Name::located_gensym( loc.clone(), name.as_ref().map(Name::original_name).unwrap_or("closure_"), ); // ... and then create a structure type that has all of the free variables // in it let closure_type = StructureDefinition::new( loc.clone(), closure_type_name.clone(), free_variables.iter().map(|x| (x.clone(), None)).collect(), ); // this will become the first argument of the function, so name it and add // it to the argument list. let closure_arg = Name::gensym("__closure_arg"); arguments.insert( 0, ( closure_arg.clone(), Some(Type::Named(closure_type_name.clone())), ), ); // Now make a map from the old free variable names to references into // our closure argument let rebinds = free_variables .into_iter() .map(|n| { ( n.clone(), Expression::FieldRef( n.location().clone(), Box::new(Expression::Reference(closure_arg.clone())), n, ), ) }) .collect::>(); let mut rebind_map = rebinds.iter().cloned().collect(); // and replace all the references in the function with this map body.replace_references(&mut rebind_map); // OK! This function definitely needs a name; if the user didn't give // it one, we'll do so. let function_name = name.unwrap_or_else(|| Name::located_gensym(loc.clone(), "function")); // And finally, we can make the function definition and insert it into our global // list along with the new closure type. let function = FunctionDefinition::new( function_name.clone(), arguments.clone(), return_type.clone(), body, ); structure_map.insert(closure_type_name.clone(), closure_type); function_map.insert(function_name.clone(), function); // And the result of this function is a call to a primitive that generates // the closure value in some sort of reasonable way. Ok(Expression::Call( Location::manufactured(), Box::new(Expression::Primitive( Location::manufactured(), Name::new("", Location::manufactured()), )), vec![ Expression::Reference(function_name), Expression::Constructor( Location::manufactured(), closure_type_name, rebinds, ), ], )) }) } } } } #[test] fn cast_checks_are_reasonable() { let mut variable_map = ScopedMap::new(); let mut structure_map = HashMap::new(); let mut function_map = HashMap::new(); let good_stmt = Expression::parse(0, "x = 4u8;").expect("valid test case"); let result_good = good_stmt.validate(&mut variable_map, &mut structure_map, &mut function_map); assert!(result_good.is_ok()); assert!(result_good.warnings().is_empty()); let bad_stmt = Expression::parse(0, "x = 4u8;").expect("valid test case"); let result_err = bad_stmt.validate(&mut variable_map, &mut structure_map, &mut function_map); assert!(result_err.is_err()); assert!(result_err.warnings().is_empty()); }