use fine::parser::SyntaxTree; use fine::semantics::{Semantics, Type}; use fine::tokens::Lines; use pretty_assertions::assert_eq; fn rebase_concrete(source_path: &str, dump: &str) { let contents = std::fs::read_to_string(source_path) .expect(&format!("unable to read input file {}", source_path)); let mut result = String::new(); let mut lines = contents.lines(); // Search for the "concrete:" section. let mut found_concrete_section = false; while let Some(line) = lines.next() { result.push_str(line); result.push_str("\n"); if line == "// @concrete:" { found_concrete_section = true; break; } } if !found_concrete_section { panic!( "unable to locate the concrete section in {}. Is there a line that starts with '// concrete:'?", source_path ); } // We've found the section we care about, replace all the lines we care // about with the actual lines. let mut replaced_output = false; while let Some(line) = lines.next() { if line.starts_with("// | ") { // Skip copying lines here, because we're skipping // the existing concrete syntax tree. } else { // OK we're out of concrete syntax tree; copy in the // new CST. (We do this inline so we don't lose // `line`.) for expected_line in dump.lines() { result.push_str("// | "); result.push_str(expected_line); result.push_str("\n"); } // (Make sure not to drop this line.) result.push_str(line); result.push_str("\n"); replaced_output = true; break; } } if !replaced_output { panic!( "didn't actually replace the output section in {}", source_path ); } // Now just copy the rest of the lines. while let Some(line) = lines.next() { result.push_str(line); result.push_str("\n"); } // ... and re-write the file. std::fs::write(source_path, result).expect("unable to write the new file!"); } fn assert_concrete(tree: &SyntaxTree, expected: &str, source_path: &str) { let dump = tree.dump(false); let rebase = std::env::var("FINE_TEST_REBASE") .unwrap_or(String::new()) .to_lowercase(); match rebase.as_str() { "1" | "true" | "yes" | "y" => { if dump != expected { rebase_concrete(source_path, &dump) } } _ => assert_eq!(expected, dump, "concrete syntax trees did not match (set FINE_TEST_REBASE=1 to auto-rebase if the diff is expected)"), } } fn report_semantic_error(semantics: &Semantics, message: &str) { let tree = semantics.tree(); println!("{message}! Parsed the tree as:"); println!("\n{}", tree.dump(true)); let errors = semantics.snapshot_errors(); if errors.len() == 0 { println!("There were no errors reported during checking.\n"); } else { println!( "{} error{} reported during checking:", errors.len(), if errors.len() == 1 { "" } else { "s" } ); for error in &errors { println!(" Error: {error}"); } println!(); } } macro_rules! semantic_panic { ($semantics:expr, $($t:tt)*) => {{ let message = format!($($t)*); report_semantic_error($semantics, &message); panic!("{message}"); }}; } macro_rules! semantic_assert { ($semantics:expr, $pred:expr, $($t:tt)*) => {{ if !$pred { let message = format!($($t)*); report_semantic_error($semantics, &message); panic!("{message}"); } }}; } macro_rules! semantic_assert_eq { ($semantics:expr, $left:expr, $right:expr, $($t:tt)*) => {{ let ll = $left; let rr = $right; if ll != rr { let message = format!($($t)*); report_semantic_error($semantics, &message); assert_eq!(ll, rr, "{}", message); } }}; } fn assert_type_at( tree: &SyntaxTree, lines: &Lines, pos: usize, expected: &str, _source_path: &str, ) { let semantics = Semantics::new(tree, lines); let tree_ref = match tree.find_tree_at(pos) { Some(t) => t, None => semantic_panic!(&semantics, "Unable to find the subtee at position {pos}"), }; let tree_type = semantics.type_of(tree_ref); let actual = format!("{}", tree_type.unwrap_or(Type::Error)); semantic_assert_eq!( &semantics, expected, actual, "The type of the tree at position {pos} was incorrect" ); } fn assert_type_error_at( tree: &SyntaxTree, lines: &Lines, pos: usize, expected: &str, _source_path: &str, ) { let semantics = Semantics::new(tree, lines); let tree_ref = match tree.find_tree_at(pos) { Some(t) => t, None => semantic_panic!(&semantics, "Unable to find the subtee at position {pos}"), }; let tree_type = semantics.type_of(tree_ref); semantic_assert!( &semantics, matches!(tree_type, Some(Type::Error)), "The type of the {:?} tree at position {pos} was '{tree_type:?}', not an error", tree[tree_ref].kind ); let errors = semantics.snapshot_errors(); semantic_assert!( &semantics, errors.iter().any(|e| e.message == expected), "Unable to find the expected error message '{expected}'" ); } include!(concat!(env!("OUT_DIR"), "/generated_tests.rs"));