diff --git a/crates/compiler/solve/tests/uitest.rs b/crates/compiler/solve/tests/uitest.rs index fcdcae9d68..aa27636c8f 100644 --- a/crates/compiler/solve/tests/uitest.rs +++ b/crates/compiler/solve/tests/uitest.rs @@ -9,7 +9,8 @@ use std::{ use lazy_static::lazy_static; use libtest_mimic::{run, Arguments, Failed, Trial}; use regex::Regex; -use test_solve_helpers::InferOptions; +use roc_region::all::LineColumn; +use test_solve_helpers::{infer_queries, InferOptions, InferredQuery}; fn main() -> Result<(), Box> { let args = Arguments::from_args(); @@ -77,7 +78,18 @@ fn run_test(path: PathBuf) -> Result<(), Failed> { source, } = TestCase::parse(data)?; - fs::write(&path, &source)?; + let inferred_program = infer_queries(&source, infer_options)?; + let inferred_queries = inferred_program.into_sorted_queries(); + + { + let mut fd = fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(&path)?; + + assemble_query_output(&mut fd, &source, inferred_queries)?; + } + check_for_changes(&path)?; Ok(()) @@ -150,3 +162,63 @@ fn check_for_changes(path: &Path) -> Result<(), Failed> { Ok(()) } + +/// Assemble the output for a test, with queries elaborated in-line. +fn assemble_query_output( + writer: &mut impl io::Write, + source: &str, + sorted_queries: Vec, +) -> io::Result<()> { + // Reverse the queries so that we can pop them off the end as we pass through the lines. + let mut sorted_queries = sorted_queries; + sorted_queries.reverse(); + + for (i, line) in source.lines().enumerate() { + let mut is_query_line = false; + + while matches!( + sorted_queries.last(), + Some(InferredQuery { + source_line_column, + .. + }) if source_line_column.line == i as _ + ) { + let InferredQuery { + output, + comment_column, + source_line_column, + source, + } = sorted_queries.pop().unwrap(); + + reconstruct_comment_line(writer, &source, source_line_column, comment_column, output)?; + + writeln!(writer)?; + + is_query_line = true; + } + + if !is_query_line { + write!(writer, "{line}\n")?; + } + } + + Ok(()) +} + +fn reconstruct_comment_line( + writer: &mut impl io::Write, + source: &str, + source_line_column: LineColumn, + comment_column: u32, + output: String, +) -> io::Result<()> { + for _ in 0..comment_column { + write!(writer, " ")?; + } + write!(writer, "#")?; + for _ in 0..(source_line_column.column - comment_column - 1) { + write!(writer, " ")?; + } + write!(writer, "{source} {output}")?; + Ok(()) +} diff --git a/crates/compiler/solve/tests/uitest/specialize/derive_hash_for_bool.txt b/crates/compiler/solve/tests/uitest/specialize/derive_hash_for_bool.txt index 62ac622df2..f7d711e0c8 100644 --- a/crates/compiler/solve/tests/uitest/specialize/derive_hash_for_bool.txt +++ b/crates/compiler/solve/tests/uitest/specialize/derive_hash_for_bool.txt @@ -1,4 +1,4 @@ app "test" provides [main] to "./platform" main = Bool.isEq Bool.true Bool.false -# ^^^^^^^^^ +# ^^^^^^^^^ Eq#Bool.isEq(9) : Bool, Bool -[[Bool.structuralEq(11)]]-> Bool diff --git a/crates/compiler/test_solve_helpers/src/lib.rs b/crates/compiler/test_solve_helpers/src/lib.rs index 133cff1510..4338cb01ac 100644 --- a/crates/compiler/test_solve_helpers/src/lib.rs +++ b/crates/compiler/test_solve_helpers/src/lib.rs @@ -134,8 +134,13 @@ lazy_static! { Regex::new(r#"(?P\^+)(?:\{-(?P\d+)\})?"#).unwrap(); } -#[derive(Debug, Clone, Copy)] -pub struct TypeQuery(Region); +#[derive(Debug, Clone)] +pub struct TypeQuery { + query_region: Region, + source: String, + comment_column: u32, + source_line_column: LineColumn, +} /// Parse inference queries in a Roc program. /// See [RE_TYPE_QUERY]. @@ -144,6 +149,16 @@ fn parse_queries(src: &str) -> Vec { let mut queries = vec![]; let mut consecutive_query_lines = 0; for (i, line) in src.lines().enumerate() { + // If this is a query line, it should start with a comment somewhere before the query + // lines. + let comment_column = match line.find("#") { + Some(i) => i as _, + None => { + consecutive_query_lines = 0; + continue; + } + }; + let mut queries_on_line = RE_TYPE_QUERY.captures_iter(line).into_iter().peekable(); if queries_on_line.peek().is_none() { @@ -154,28 +169,46 @@ fn parse_queries(src: &str) -> Vec { } for capture in queries_on_line { + let source = capture + .get(0) + .expect("full capture must always exist") + .as_str() + .to_string(); + let wher = capture.name("where").unwrap(); let subtract_col = capture .name("sub") .and_then(|m| str::parse(m.as_str()).ok()) .unwrap_or(0); - let (start, end) = (wher.start() as u32, wher.end() as u32); - let (start, end) = (start - subtract_col, end - subtract_col); + let (source_start, source_end) = (wher.start() as u32, wher.end() as u32); + let (query_start, query_end) = (source_start - subtract_col, source_end - subtract_col); - let last_line = i as u32 - consecutive_query_lines; - let start_lc = LineColumn { - line: last_line, - column: start, + let source_line_column = LineColumn { + line: i as u32, + column: source_start, }; - let end_lc = LineColumn { - line: last_line, - column: end, - }; - let lc_region = LineColumnRegion::new(start_lc, end_lc); - let region = line_info.convert_line_column_region(lc_region); - queries.push(TypeQuery(region)); + let query_region = { + let last_line = i as u32 - consecutive_query_lines; + let query_start_lc = LineColumn { + line: last_line, + column: query_start, + }; + let query_end_lc = LineColumn { + line: last_line, + column: query_end, + }; + let query_lc_region = LineColumnRegion::new(query_start_lc, query_end_lc); + line_info.convert_line_column_region(query_lc_region) + }; + + queries.push(TypeQuery { + query_region, + source, + comment_column, + source_line_column, + }); } } queries @@ -189,8 +222,13 @@ pub struct InferOptions { } pub struct InferredQuery { - pub region: Region, pub output: String, + /// Where the comment before the query string was written in the source. + pub comment_column: u32, + /// Where the query string "^^^" itself was written in the source. + pub source_line_column: LineColumn, + /// The content of the query string. + pub source: String, } pub struct InferredProgram { @@ -200,6 +238,15 @@ pub struct InferredProgram { inferred_queries: Vec, } +impl InferredProgram { + /// Returns all inferred queries, sorted by their source location. + pub fn into_sorted_queries(self) -> Vec { + let mut inferred = self.inferred_queries; + inferred.sort_by_key(|iq| iq.source_line_column); + inferred + } +} + pub fn infer_queries(src: &str, options: InferOptions) -> Result> { let ( LoadedModule { @@ -239,12 +286,18 @@ pub fn infer_queries(src: &str, options: InferOptions) -> Result Result { - format!( - "{}#{}({}) : {}", - spec_type.as_str(&interns), - text, - spec_symbol.ident_id().index(), - actual_str - ) - } - None => { - format!("{} : {}", text, actual_str) - } - }; + let elaborated = match find_ability_member_and_owning_type_at( + query_region, + &declarations, + &abilities_store, + ) { + Some((spec_type, spec_symbol)) => { + format!( + "{}#{}({}) : {}", + spec_type.as_str(&interns), + text, + spec_symbol.ident_id().index(), + actual_str + ) + } + None => { + format!("{} : {}", text, actual_str) + } + }; inferred_queries.push(InferredQuery { - region, output: elaborated, + comment_column, + source_line_column, + source, }); }