use crate::{ eval::PrimitiveType, syntax::{Expression, Location, Program, Statement, TopLevel}, util::scoped_map::ScopedMap, }; use codespan_reporting::diagnostic::Diagnostic; use std::str::FromStr; /// 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. 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(&self) -> (Vec, Vec) { let mut bound_variables = ScopedMap::new(); self.validate_with_bindings(&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( &self, bound_variables: &mut ScopedMap, ) -> (Vec, Vec) { let mut errors = vec![]; let mut warnings = vec![]; for stmt in self.items.iter() { let (mut new_errors, mut new_warnings) = stmt.validate_with_bindings(bound_variables); errors.append(&mut new_errors); warnings.append(&mut new_warnings); } (errors, warnings) } } impl TopLevel { /// Validate that the top level item 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 thins that are inadvisable but not /// actually a problem. pub fn validate(&self) -> (Vec, Vec) { let mut bound_variables = ScopedMap::new(); self.validate_with_bindings(&mut bound_variables) } /// Validate that the top level item 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 thins that are inadvisable but not /// actually a problem. pub fn validate_with_bindings( &self, bound_variables: &mut ScopedMap, ) -> (Vec, Vec) { match self { TopLevel::Function(name, arguments, body) => { bound_variables.new_scope(); if let Some(name) = name { bound_variables.insert(name.name.clone(), name.location.clone()); } for arg in arguments.iter() { bound_variables.insert(arg.name.clone(), arg.location.clone()); } let result = body.validate(&bound_variables); bound_variables.release_scope(); result } TopLevel::Statement(stmt) => stmt.validate(bound_variables), } } } impl Statement { /// Validate that the statement 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. Since statements appear in a broader context, you'll /// need to provide the set of variables that are bound where this statement /// occurs. We use a `HashMap` to map these bound locations to the locations /// where their bound, because these locations are handy when generating errors /// and warnings. fn validate( &self, bound_variables: &mut ScopedMap, ) -> (Vec, Vec) { let mut errors = vec![]; let mut warnings = vec![]; match self { Statement::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 exp_errors, mut exp_warnings) = val.validate(bound_variables); errors.append(&mut exp_errors); warnings.append(&mut exp_warnings); if let Some(original_binding_site) = bound_variables.get(&var.name) { warnings.push(Warning::ShadowedVariable( original_binding_site.clone(), loc.clone(), var.to_string(), )); } else { bound_variables.insert(var.to_string(), loc.clone()); } } Statement::Print(_, var) if bound_variables.contains_key(&var.name) => {} Statement::Print(loc, var) => { errors.push(Error::UnboundVariable(loc.clone(), var.to_string())) } } (errors, warnings) } } impl Expression { fn validate(&self, variable_map: &ScopedMap) -> (Vec, Vec) { match self { Expression::Value(_, _) => (vec![], vec![]), Expression::Reference(_, var) if variable_map.contains_key(var) => (vec![], vec![]), Expression::Reference(loc, var) => ( vec![Error::UnboundVariable(loc.clone(), var.clone())], vec![], ), Expression::Cast(location, t, expr) => { let (mut errs, warns) = expr.validate(variable_map); if PrimitiveType::from_str(t).is_err() { errs.push(Error::UnknownType(location.clone(), t.clone())) } (errs, warns) } Expression::Primitive(_, _, args) => { let mut errors = vec![]; let mut warnings = vec![]; for expr in args.iter() { let (mut err, mut warn) = expr.validate(variable_map); errors.append(&mut err); warnings.append(&mut warn); } (errors, warnings) } } } } #[test] fn cast_checks_are_reasonable() { let good_stmt = TopLevel::parse(0, "x = 4u8;").expect("valid test case"); let (good_errs, good_warns) = good_stmt.validate(); assert!(good_errs.is_empty()); assert!(good_warns.is_empty()); let bad_stmt = TopLevel::parse(0, "x = 4u8;").expect("valid test case"); let (bad_errs, bad_warns) = bad_stmt.validate(); assert!(bad_warns.is_empty()); assert_eq!(bad_errs.len(), 1); assert!(matches!(bad_errs[0], Error::UnknownType(_, ref x) if x == "apple")); }