diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 5ad9ce26c3..8c82a0590d 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1000,7 +1000,15 @@ fn roc_dev_native( SIGUSR2 => { // this is the signal we use for a dbg - println!("I need to dbg something"); + roc_repl_expect::run::render_dbgs_in_memory( + &mut writer, + arena, + &mut expectations, + &interns, + &layout_interner, + &memory, + ) + .unwrap(); } _ => println!("received signal {}", sig), } diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs index b9dba0bf08..94736efd89 100644 --- a/crates/compiler/can/src/copy.rs +++ b/crates/compiler/can/src/copy.rs @@ -615,10 +615,12 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr loc_condition, loc_continuation, variable, + symbol, } => Dbg { loc_condition: Box::new(loc_condition.map(|e| go_help!(e))), loc_continuation: Box::new(loc_continuation.map(|e| go_help!(e))), variable: sub!(*variable), + symbol: *symbol, }, TypedHole(v) => TypedHole(sub!(*v)), diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index bb3d782334..c3d235a1b9 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -244,6 +244,7 @@ pub enum Expr { loc_condition: Box>, loc_continuation: Box>, variable: Variable, + symbol: Symbol, }, /// Rendered as empty box in editor @@ -260,6 +261,14 @@ pub struct ExpectLookup { pub ability_info: Option, } +#[derive(Clone, Copy, Debug)] +pub struct DbgLookup { + pub symbol: Symbol, + pub var: Variable, + pub region: Region, + pub ability_info: Option, +} + impl Expr { pub fn category(&self) -> Category { match self { @@ -1039,6 +1048,33 @@ pub fn canonicalize_expr<'a>( output, ) } + ast::Expr::Dbg(condition, continuation) => { + let mut output = Output::default(); + + let (loc_condition, output1) = + canonicalize_expr(env, var_store, scope, condition.region, &condition.value); + + let (loc_continuation, output2) = canonicalize_expr( + env, + var_store, + scope, + continuation.region, + &continuation.value, + ); + + output.union(output1); + output.union(output2); + + ( + Dbg { + loc_condition: Box::new(loc_condition), + loc_continuation: Box::new(loc_continuation), + variable: var_store.fresh(), + symbol: scope.gen_unique_symbol(), + }, + output, + ) + } ast::Expr::If(if_thens, final_else_branch) => { let mut branches = Vec::with_capacity(if_thens.len()); let mut output = Output::default(); @@ -1838,6 +1874,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr { loc_condition, loc_continuation, variable, + symbol, } => { let loc_condition = Loc { region: loc_condition.region, @@ -1853,6 +1890,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr { loc_condition: Box::new(loc_condition), loc_continuation: Box::new(loc_continuation), variable, + symbol, } } @@ -2582,9 +2620,10 @@ impl Declarations { }) } - pub fn expects(&self) -> VecMap> { + pub fn expects(&self) -> ExpectCollector { let mut collector = ExpectCollector { expects: VecMap::default(), + dbgs: VecMap::default(), }; let var = Variable::EMPTY_RECORD; @@ -2617,7 +2656,7 @@ impl Declarations { } } - collector.expects + collector } } @@ -2897,8 +2936,9 @@ fn toplevel_expect_to_inline_expect_help(mut loc_expr: Loc, has_effects: b loc_expr } -struct ExpectCollector { - expects: VecMap>, +pub struct ExpectCollector { + pub expects: VecMap>, + pub dbgs: VecMap, } impl crate::traverse::Visitor for ExpectCollector { @@ -2917,6 +2957,21 @@ impl crate::traverse::Visitor for ExpectCollector { self.expects .insert(loc_condition.region, lookups_in_cond.to_vec()); } + Expr::Dbg { + loc_condition, + variable, + symbol, + .. + } => { + let lookup = DbgLookup { + symbol: *symbol, + var: *variable, + region: loc_condition.region, + ability_info: None, + }; + + self.dbgs.insert(*symbol, lookup); + } _ => (), } diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index b6cc2d6624..bac5a4d661 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -3,7 +3,9 @@ use crate::annotation::{canonicalize_annotation, AnnotationFor}; use crate::def::{canonicalize_defs, Def}; use crate::effect_module::HostedGeneratedFunctions; use crate::env::Env; -use crate::expr::{ClosureData, Declarations, ExpectLookup, Expr, Output, PendingDerives}; +use crate::expr::{ + ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives, +}; use crate::pattern::{BindingsFromPattern, Pattern}; use crate::scope::Scope; use bumpalo::Bump; @@ -131,6 +133,7 @@ pub struct Module { pub rigid_variables: RigidVariables, pub abilities_store: PendingAbilitiesStore, pub loc_expects: VecMap>, + pub loc_dbgs: VecMap, } #[derive(Debug, Default)] @@ -153,6 +156,7 @@ pub struct ModuleOutput { pub pending_derives: PendingDerives, pub scope: Scope, pub loc_expects: VecMap>, + pub loc_dbgs: VecMap, } fn validate_generate_with<'a>( @@ -776,7 +780,7 @@ pub fn canonicalize_module_defs<'a>( } } - let loc_expects = declarations.expects(); + let collected = declarations.expects(); ModuleOutput { scope, @@ -789,7 +793,8 @@ pub fn canonicalize_module_defs<'a>( problems: env.problems, symbols_from_requires, pending_derives, - loc_expects, + loc_expects: collected.expects, + loc_dbgs: collected.dbgs, } } diff --git a/crates/compiler/can/src/operator.rs b/crates/compiler/can/src/operator.rs index c81e18fb4e..90a50be2d3 100644 --- a/crates/compiler/can/src/operator.rs +++ b/crates/compiler/can/src/operator.rs @@ -358,6 +358,14 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc region: loc_expr.region, }) } + Dbg(condition, continuation) => { + let desugared_condition = &*arena.alloc(desugar_expr(arena, condition)); + let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation)); + arena.alloc(Loc { + value: Dbg(desugared_condition, desugared_continuation), + region: loc_expr.region, + }) + } } } diff --git a/crates/compiler/can/src/traverse.rs b/crates/compiler/can/src/traverse.rs index 5f3b5994cf..1569fb2fc2 100644 --- a/crates/compiler/can/src/traverse.rs +++ b/crates/compiler/can/src/traverse.rs @@ -291,6 +291,7 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) { variable, loc_condition, loc_continuation, + symbol: _, } => { visitor.visit_expr(&loc_condition.value, loc_condition.region, *variable); visitor.visit_expr( diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index be9d000823..65c35fb50d 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -660,6 +660,7 @@ pub fn constrain_expr( loc_condition, loc_continuation, variable, + symbol: _, } => { let dbg_type = constraints.push_variable(*variable); let expected_dbg = constraints.push_expected_type(Expected::NoExpectation(dbg_type)); diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs index 75c8201f8e..528788238e 100644 --- a/crates/compiler/fmt/src/expr.rs +++ b/crates/compiler/fmt/src/expr.rs @@ -71,6 +71,7 @@ impl<'a> Formattable for Expr<'a> { Expect(condition, continuation) => { condition.is_multiline() || continuation.is_multiline() } + Dbg(condition, continuation) => condition.is_multiline() || continuation.is_multiline(), If(branches, final_else) => { final_else.is_multiline() @@ -379,6 +380,9 @@ impl<'a> Formattable for Expr<'a> { Expect(condition, continuation) => { fmt_expect(buf, condition, continuation, self.is_multiline(), indent); } + Dbg(condition, continuation) => { + fmt_dbg(buf, condition, continuation, self.is_multiline(), indent); + } If(branches, final_else) => { fmt_if(buf, branches, final_else, self.is_multiline(), indent); } @@ -843,6 +847,33 @@ fn fmt_when<'a, 'buf>( } } +fn fmt_dbg<'a, 'buf>( + buf: &mut Buf<'buf>, + condition: &'a Loc>, + continuation: &'a Loc>, + is_multiline: bool, + indent: u16, +) { + buf.ensure_ends_with_newline(); + buf.indent(indent); + buf.push_str("dbg"); + + let return_indent = if is_multiline { + buf.newline(); + indent + INDENT + } else { + buf.spaces(1); + indent + }; + + condition.format(buf, return_indent); + + // Always put a blank line after the `dbg` line(s) + buf.ensure_ends_with_blank_line(); + + continuation.format(buf, indent); +} + fn fmt_expect<'a, 'buf>( buf: &mut Buf<'buf>, condition: &'a Loc>, diff --git a/crates/compiler/fmt/src/spaces.rs b/crates/compiler/fmt/src/spaces.rs index 0d8ee50ec7..5c10b2e806 100644 --- a/crates/compiler/fmt/src/spaces.rs +++ b/crates/compiler/fmt/src/spaces.rs @@ -692,6 +692,10 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> { arena.alloc(a.remove_spaces(arena)), arena.alloc(b.remove_spaces(arena)), ), + Expr::Dbg(a, b) => Expr::Dbg( + arena.alloc(a.remove_spaces(arena)), + arena.alloc(b.remove_spaces(arena)), + ), Expr::Apply(a, b, c) => Expr::Apply( arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena), diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs index 55521761bc..84762f6bf4 100644 --- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs +++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs @@ -1123,7 +1123,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( // now what arguments!(condition); - let region = roc_region::all::Region::zero(); // todo + let region = unsafe { std::mem::transmute::<_, roc_region::all::Region>(args[0]) }; crate::llvm::expect::clone_to_shared_memory( env, @@ -1131,7 +1131,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( layout_ids, args[0], region, - &[], + &[args[0]], ); crate::llvm::expect::send_dbg(env); diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index b6ad939c11..223054ebf4 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -7,7 +7,7 @@ use parking_lot::Mutex; use roc_builtins::roc::module_source; use roc_can::abilities::{AbilitiesStore, PendingAbilitiesStore, ResolvedImpl}; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints, TypeOrVar}; -use roc_can::expr::PendingDerives; +use roc_can::expr::{DbgLookup, PendingDerives}; use roc_can::expr::{Declarations, ExpectLookup}; use roc_can::module::{ canonicalize_module_defs, ExposedByModule, ExposedForModule, ExposedModuleTypes, Module, @@ -731,6 +731,7 @@ pub struct Expectations { pub subs: roc_types::subs::Subs, pub path: PathBuf, pub expectations: VecMap>, + pub dbgs: VecMap, pub ident_ids: IdentIds, } @@ -775,6 +776,7 @@ struct ParsedModule<'a> { } type LocExpects = VecMap>; +type LocDbgs = VecMap; /// A message sent out _from_ a worker thread, /// representing a result of work done, or a request for further work @@ -794,6 +796,7 @@ enum Msg<'a> { module_timing: ModuleTiming, abilities_store: AbilitiesStore, loc_expects: LocExpects, + loc_dbgs: LocDbgs, }, FinishedAllTypeChecking { solved_subs: Solved, @@ -2403,6 +2406,7 @@ fn update<'a>( mut module_timing, abilities_store, loc_expects, + loc_dbgs, } => { log!("solved types for {:?}", module_id); module_timing.end_time = Instant::now(); @@ -2424,6 +2428,7 @@ fn update<'a>( let expectations = Expectations { expectations: loc_expects, + dbgs: loc_dbgs, subs: solved_subs.clone().into_inner(), path: path.to_owned(), ident_ids: ident_ids.clone(), @@ -4552,6 +4557,7 @@ fn run_solve<'a>( let mut module = module; let loc_expects = std::mem::take(&mut module.loc_expects); + let loc_dbgs = std::mem::take(&mut module.loc_dbgs); let module = module; let (solved_subs, solved_implementations, exposed_vars_by_symbol, problems, abilities_store) = { @@ -4626,6 +4632,7 @@ fn run_solve<'a>( module_timing, abilities_store, loc_expects, + loc_dbgs, } } @@ -4832,6 +4839,7 @@ fn canonicalize_and_constrain<'a>( rigid_variables: module_output.rigid_variables, abilities_store: module_output.scope.abilities_store, loc_expects: module_output.loc_expects, + loc_dbgs: module_output.loc_dbgs, }; let constrained_module = ConstrainedModule { diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 96d4f330ec..566dfa85e5 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -6551,9 +6551,9 @@ pub fn from_can<'a>( loc_condition, loc_continuation, variable, + symbol: dbg_symbol, } => { let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache); - let dbg_symbol = env.unique_symbol(); let call = crate::ir::Call { call_type: CallType::LowLevel { diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index 94308036c4..468aaff520 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -208,6 +208,7 @@ pub enum Expr<'a> { Defs(&'a Defs<'a>, &'a Loc>), Backpassing(&'a [Loc>], &'a Loc>, &'a Loc>), Expect(&'a Loc>, &'a Loc>), + Dbg(&'a Loc>, &'a Loc>), // Application /// To apply by name, do Apply(Var(...), ...) diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 88a4bdf8fc..71d72619db 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -327,6 +327,7 @@ fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc>, E loc!(specialize(EExpr::If, if_expr_help(options))), loc!(specialize(EExpr::When, when::expr_help(options))), loc!(specialize(EExpr::Expect, expect_help(options))), + loc!(specialize(EExpr::Dbg, dbg_help(options))), loc!(specialize(EExpr::Closure, closure_help(options))), loc!(expr_operator_chain(options)), fail_expr_start_e() @@ -1912,6 +1913,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpe } } +fn dbg_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpect<'a>> { + move |arena: &'a Bump, state: State<'a>, min_indent| { + let start_column = state.column(); + + let (_, _, state) = + parser::keyword_e(keyword::DBG, EExpect::Dbg).parse(arena, state, min_indent)?; + + let (_, condition, state) = space0_before_e( + specialize_ref( + EExpect::Condition, + set_min_indent(start_column + 1, expr_start(options)), + ), + EExpect::IndentCondition, + ) + .parse(arena, state, start_column + 1) + .map_err(|(_, f)| (MadeProgress, f))?; + + let parse_cont = specialize_ref( + EExpect::Continuation, + space0_before_e(loc_expr(), EExpr::IndentEnd), + ); + + let (_, loc_cont, state) = parse_cont.parse(arena, state, min_indent)?; + + let expr = Expr::Dbg(arena.alloc(condition), arena.alloc(loc_cont)); + + Ok((MadeProgress, expr, state)) + } +} + fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<'a>> { move |arena: &'a Bump, state, min_indent| { let (_, _, state) = diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index d613f0fd49..5e640e369a 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -354,6 +354,7 @@ pub enum EExpr<'a> { If(EIf<'a>, Position), Expect(EExpect<'a>, Position), + Dbg(EExpect<'a>, Position), Closure(EClosure<'a>, Position), Underscore(Position), diff --git a/crates/repl_expect/src/run.rs b/crates/repl_expect/src/run.rs index 2ec241aca6..966ef88311 100644 --- a/crates/repl_expect/src/run.rs +++ b/crates/repl_expect/src/run.rs @@ -416,6 +416,44 @@ pub fn render_expects_in_memory<'a>( ) } +pub fn render_dbgs_in_memory<'a>( + writer: &mut impl std::io::Write, + arena: &'a Bump, + expectations: &mut VecMap, + interns: &'a Interns, + layout_interner: &Arc>>, + memory: &ExpectMemory, +) -> std::io::Result { + let shared_ptr = memory.ptr; + + let frame = ExpectFrame::at_offset(shared_ptr, ExpectSequence::START_OFFSET); + let module_id = frame.module_id; + + let data = expectations.get_mut(&module_id).unwrap(); + let filename = data.path.to_owned(); + let source = std::fs::read_to_string(&data.path).unwrap(); + + let renderer = Renderer::new( + arena, + interns, + RenderTarget::ColorTerminal, + module_id, + filename, + &source, + ); + + render_dbg_failure( + writer, + &renderer, + arena, + expectations, + interns, + layout_interner, + shared_ptr, + ExpectSequence::START_OFFSET, + ) +} + fn split_expect_lookups(subs: &Subs, lookups: &[ExpectLookup]) -> (Vec, Vec) { lookups .iter() @@ -437,6 +475,69 @@ fn split_expect_lookups(subs: &Subs, lookups: &[ExpectLookup]) -> (Vec, .unzip() } +#[allow(clippy::too_many_arguments)] +fn render_dbg_failure<'a>( + writer: &mut impl std::io::Write, + renderer: &Renderer, + arena: &'a Bump, + expectations: &mut VecMap, + interns: &'a Interns, + layout_interner: &Arc>>, + start: *const u8, + offset: usize, +) -> std::io::Result { + // we always run programs as the host + let target_info = (&target_lexicon::Triple::host()).into(); + + let frame = ExpectFrame::at_offset(start, offset); + let module_id = frame.module_id; + + let failure_region = frame.region; + let dbg_symbol = unsafe { std::mem::transmute::<_, Symbol>(failure_region) }; + let expect_region = Some(Region::zero()); + + let data = expectations.get_mut(&module_id).unwrap(); + + let current = match data.dbgs.get(&dbg_symbol) { + None => panic!("region {failure_region:?} not in list of expects"), + Some(current) => current, + }; + let failure_region = current.region; + + let subs = arena.alloc(&mut data.subs); + + let current = ExpectLookup { + symbol: current.symbol, + var: current.var, + ability_info: current.ability_info, + }; + + let (symbols, variables) = split_expect_lookups(subs, &[current]); + + let (offset, expressions) = crate::get_values( + target_info, + arena, + subs, + interns, + layout_interner, + start, + frame.start_offset, + &variables, + ); + + renderer.render_dbg( + writer, + subs, + &symbols, + &variables, + &expressions, + expect_region, + failure_region, + )?; + + Ok(offset) +} + #[allow(clippy::too_many_arguments)] fn render_expect_failure<'a>( writer: &mut impl std::io::Write, diff --git a/crates/reporting/src/error/expect.rs b/crates/reporting/src/error/expect.rs index 5bde85c412..5c58051c6d 100644 --- a/crates/reporting/src/error/expect.rs +++ b/crates/reporting/src/error/expect.rs @@ -164,6 +164,44 @@ impl<'a> Renderer<'a> { write!(writer, "{}", buf) } + #[allow(clippy::too_many_arguments)] + pub fn render_dbg( + &self, + writer: &mut W, + subs: &mut Subs, + symbols: &[Symbol], + variables: &[Variable], + expressions: &[Expr<'_>], + expect_region: Option, + failure_region: Region, + ) -> std::io::Result<()> + where + W: std::io::Write, + { + use crate::report::Report; + + let line_col_region = self.to_line_col_region(expect_region, failure_region); + let doc = self.render_lookups(subs, line_col_region, symbols, variables, expressions); + + let report = Report { + title: "DBG".into(), + doc, + filename: self.filename.clone(), + severity: crate::report::Severity::RuntimeError, + }; + + let mut buf = String::new(); + + report.render( + self.render_target, + &mut buf, + &self.alloc, + &crate::report::DEFAULT_PALETTE, + ); + + write!(writer, "{}", buf) + } + pub fn render_panic( &self, writer: &mut W,