Format problems reported by the IR checker

This commit is contained in:
Ayaz Hafiz 2022-12-03 21:35:01 -06:00
parent a84aebf2d3
commit 6e72307736
No known key found for this signature in database
GPG Key ID: 0E2A37416A25EF58
5 changed files with 513 additions and 12 deletions

View File

@ -0,0 +1,5 @@
mod checker;
mod report;
pub use checker::{check_procs, Problem, Problems};
pub use report::format_problems;

View File

@ -63,6 +63,7 @@ pub enum ProblemKind<'a> {
num_given: usize,
},
CallingUndefinedProc {
symbol: Symbol,
proc_layout: ProcLayout<'a>,
similar: Vec<ProcLayout<'a>>,
},
@ -113,22 +114,24 @@ pub enum ProblemKind<'a> {
}
pub struct Problem<'a> {
pub proc_key: (Symbol, ProcLayout<'a>),
pub proc: &'a Proc<'a>,
pub proc_layout: ProcLayout<'a>,
pub line: usize,
pub kind: ProblemKind<'a>,
}
type Procs<'a> = MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>;
pub type Problems<'a> = Vec<Problem<'a>>;
pub struct Problems<'a>(pub(crate) Vec<Problem<'a>>);
pub fn check_procs<'a>(arena: &'a Bump, procs: &Procs<'a>) -> Problems<'a> {
pub fn check_procs<'a>(arena: &'a Bump, procs: &'a Procs<'a>) -> Problems<'a> {
let mut problems = Default::default();
let mut call_spec_ids = Default::default();
for (proc_key, proc) in procs.iter() {
for ((_, proc_layout), proc) in procs.iter() {
let mut ctx = Ctx {
arena,
proc_key: *proc_key,
proc,
proc_layout: *proc_layout,
ret_layout: proc.ret_layout,
problems: &mut problems,
call_spec_ids: &mut call_spec_ids,
@ -140,7 +143,7 @@ pub fn check_procs<'a>(arena: &'a Bump, procs: &Procs<'a>) -> Problems<'a> {
ctx.check_proc(proc);
}
problems
Problems(problems)
}
type VEnv<'a> = VecMap<Symbol, (usize, Layout<'a>)>;
@ -148,10 +151,11 @@ type JoinPoints<'a> = VecMap<JoinPointId, (usize, &'a [Param<'a>])>;
type CallSpecIds = VecMap<CallSpecId, usize>;
struct Ctx<'a, 'r> {
arena: &'a Bump,
problems: &'r mut Problems<'a>,
problems: &'r mut Vec<Problem<'a>>,
proc: &'a Proc<'a>,
proc_layout: ProcLayout<'a>,
procs: &'r Procs<'a>,
call_spec_ids: &'r mut CallSpecIds,
proc_key: (Symbol, ProcLayout<'a>),
ret_layout: Layout<'a>,
venv: VEnv<'a>,
joinpoints: JoinPoints<'a>,
@ -165,7 +169,8 @@ impl<'a, 'r> Ctx<'a, 'r> {
fn problem(&mut self, problem_kind: ProblemKind<'a>) {
self.problems.push(Problem {
proc_key: self.proc_key,
proc: self.proc,
proc_layout: self.proc_layout,
line: self.line,
kind: problem_kind,
})
@ -513,6 +518,7 @@ impl<'a, 'r> Ctx<'a, 'r> {
.map(|(_, lay)| *lay)
.collect();
self.problem(ProblemKind::CallingUndefinedProc {
symbol: name.name(),
proc_layout,
similar,
});
@ -584,7 +590,7 @@ enum TagPayloads<'a> {
Payloads(&'a [Layout<'a>]),
}
fn get_tag_id_payloads<'a>(union_layout: UnionLayout<'a>, tag_id: TagIdIntType) -> TagPayloads<'a> {
fn get_tag_id_payloads(union_layout: UnionLayout, tag_id: TagIdIntType) -> TagPayloads {
macro_rules! check_tag_id_oob {
($len:expr) => {
if tag_id as usize >= $len {

View File

@ -0,0 +1,490 @@
use std::fmt::Display;
use roc_intern::Interner;
use roc_module::symbol::{Interns, Symbol};
use ven_pretty::{Arena, DocAllocator, DocBuilder};
use crate::{
ir::{Parens, ProcLayout},
layout::{CapturesNiche, Layout},
};
use super::{
checker::{ProblemKind, UseKind},
Problem, Problems,
};
pub fn format_problems<'a, I>(
interns: &Interns,
interner: &I,
problems: Problems<'a>,
) -> impl Display
where
I: Interner<'a, Layout<'a>>,
{
let Problems(problems) = problems;
let f = Arena::new();
let problem_docs = problems
.into_iter()
.map(|p| format_problem(&f, interns, interner, p));
let all = f.intersperse(problem_docs, f.hardline());
all.1.pretty(80).to_string()
}
type Doc<'d> = DocBuilder<'d, Arena<'d>>;
const GUTTER_BAR: &str = "";
const HEADER_WIDTH: usize = 80;
fn format_problem<'a, 'd, I>(
f: &'d Arena<'d>,
interns: &'d Interns,
interner: &'d I,
problem: Problem<'a>,
) -> Doc<'d>
where
'a: 'd,
I: Interner<'a, Layout<'a>>,
{
let Problem {
proc,
proc_layout,
line,
kind,
} = problem;
let (title, mut docs, last_doc) = format_kind(f, interns, interner, kind);
docs.push((line, last_doc));
docs.sort_by_key(|(line, _)| *line);
let src = proc
.to_doc(f, interner, Parens::NotNeeded)
.1
.pretty(80)
.to_string();
let interpolated_docs = stack(
f,
docs.into_iter()
.map(|(line, doc)| format_sourced_doc(f, line, &src, doc)),
);
let header = format_header(f, title);
let proc_loc = format_proc_spec(f, interns, interner, proc.name.name(), proc_layout);
stack(f, [header, proc_loc, interpolated_docs])
}
fn format_sourced_doc<'d>(f: &'d Arena<'d>, line: usize, source: &str, doc: Doc<'d>) -> Doc<'d> {
let start_at = line.saturating_sub(1);
let source_lines = source.lines().skip(start_at).take(3);
let max_line_no_width = (start_at.to_string().len()).max((start_at + 3).to_string().len());
let pretty_lines = source_lines.enumerate().map(|(i, line_src)| {
let line_no = start_at + i;
let line_no_s = line_no.to_string();
let line_no_len = line_no_s.len();
f.text(line_no_s)
.append(f.text(" ".repeat(max_line_no_width - line_no_len)))
.append(f.text(GUTTER_BAR))
.append(f.text(if line_no == line { "> " } else { " " }))
.append(f.text(line_src.to_string()))
});
let pretty_lines = stack(f, pretty_lines);
stack(f, [pretty_lines, doc])
}
fn format_header<'d>(f: &'d Arena<'d>, title: &str) -> Doc<'d> {
let title_width = title.len() + 4;
let header = f.text(format!(
"── {} {}",
title,
"".repeat(HEADER_WIDTH - title_width)
));
header
}
fn format_kind<'a, 'd, I>(
f: &'d Arena<'d>,
interns: &'d Interns,
interner: &I,
kind: ProblemKind<'a>,
) -> (&'static str, Vec<(usize, Doc<'d>)>, Doc<'d>)
where
I: Interner<'a, Layout<'a>>,
{
let title;
let docs_before;
let doc = match kind {
ProblemKind::RedefinedSymbol { symbol, old_line } => {
title = "REDEFINED SYMBOL";
docs_before = vec![(
old_line,
f.concat([
f.as_string(symbol.as_str(interns)),
f.reflow(" first defined here"),
]),
)];
f.concat([
f.as_string(symbol.as_str(interns)),
f.reflow(" re-defined here"),
])
}
ProblemKind::NoSymbolInScope { symbol } => {
title = "SYMBOL NOT DEFINED";
docs_before = vec![];
f.concat([
f.as_string(symbol.as_str(interns)),
f.reflow(" not found in the present scope"),
])
}
ProblemKind::SymbolUseMismatch {
symbol,
def_layout,
def_line,
use_layout,
use_kind,
} => {
title = "SYMBOL LAYOUT DOESN'T MATCH ITS USE";
docs_before = vec![(
def_line,
f.concat([
f.as_string(symbol.as_str(interns)),
f.reflow(" defined here with layout "),
def_layout.to_doc(f, interner, Parens::NotNeeded),
]),
)];
f.concat([
f.as_string(symbol.as_str(interns)),
f.reflow(" used as a "),
f.reflow(format_use_kind(use_kind)),
f.reflow(" here with layout "),
use_layout.to_doc(f, interner, Parens::NotNeeded),
])
}
ProblemKind::SymbolDefMismatch {
symbol,
def_layout,
expr_layout,
} => {
title = "SYMBOL INITIALIZER HAS THE WRONG LAYOUT";
docs_before = vec![];
f.concat([
f.as_string(symbol.as_str(interns)),
f.reflow(" is defined as "),
def_layout.to_doc(f, interner, Parens::NotNeeded),
f.reflow(" but its initializer is "),
expr_layout.to_doc(f, interner, Parens::NotNeeded),
])
}
ProblemKind::BadSwitchConditionLayout { found_layout } => {
title = "BAD SWITCH CONDITION LAYOUT";
docs_before = vec![];
f.concat([
f.reflow("This switch condition is a "),
found_layout.to_doc(f, interner, Parens::NotNeeded),
])
}
ProblemKind::DuplicateSwitchBranch {} => {
title = "DUPLICATE SWITCH BRANCH";
docs_before = vec![];
f.reflow("The match of switch branch is reached earlier")
}
ProblemKind::RedefinedJoinPoint { id, old_line } => {
title = "DUPLICATE JOIN POINT";
docs_before = vec![(
old_line,
f.concat([
f.reflow("The join point "),
f.as_string(id.0),
f.reflow(" was previously defined here"),
]),
)];
f.reflow("and is redefined here")
}
ProblemKind::NoJoinPoint { id } => {
title = "JOIN POINT NOT DEFINED";
docs_before = vec![];
f.concat([
f.reflow("The join point "),
f.as_string(id.0),
f.reflow(" was not found in the present scope"),
])
}
ProblemKind::JumpArityMismatch {
def_line,
num_needed,
num_given,
} => {
title = "WRONG NUMBER OF ARGUMENTS IN JUMP";
docs_before = vec![(
def_line,
f.concat([
f.reflow("This join pont needs "),
f.as_string(num_needed),
f.reflow(" arguments"),
]),
)];
f.concat([
f.reflow("but this jump only gives it "),
f.as_string(num_given),
])
}
ProblemKind::CallingUndefinedProc {
symbol,
proc_layout,
similar,
} => {
title = "PROC SPECIALIZATION NOT DEFINED";
docs_before = vec![];
let no_spec_doc = stack(
f,
[
f.reflow("No specialization"),
format_proc_spec(f, interns, interner, symbol, proc_layout),
f.reflow("was found"),
],
);
let similar_doc = if similar.is_empty() {
f.nil()
} else {
let similars = similar
.into_iter()
.map(|other| format_proc_spec(f, interns, interner, symbol, other));
stack(
f,
[f.concat([
f.reflow("The following specializations of "),
f.as_string(symbol.as_str(interns)),
f.reflow(" were built:"),
stack(f, similars),
])],
)
};
stack(f, [no_spec_doc, similar_doc])
}
ProblemKind::DuplicateCallSpecId { old_call_line } => {
title = "DUPLICATE CALL SPEC ID";
docs_before = vec![(old_call_line, f.reflow("This call has a specialization ID"))];
f.reflow("...that is the same as the specialization ID of the call here")
}
ProblemKind::StructIndexOOB {
structure,
def_line,
index,
size,
} => {
title = "STRUCT INDEX IS OUT-OF-BOUNDS";
docs_before = vec![(
def_line,
f.concat([
f.reflow("The struct "),
f.as_string(structure.as_str(interns)),
f.reflow(" defined here has "),
f.as_string(size),
f.reflow(" fields"),
]),
)];
f.concat([
f.reflow("but is being indexed into field "),
f.as_string(index),
])
}
ProblemKind::NotAStruct {
structure,
def_line,
} => {
title = "SYMBOL IS NOT A STRUCT";
docs_before = vec![(
def_line,
f.concat([
f.reflow("The value "),
f.as_string(structure.as_str(interns)),
f.reflow(" defined here"),
]),
)];
f.reflow("cannot be used as a structure here")
}
ProblemKind::IndexingTagIdNotInUnion {
structure,
def_line,
tag_id,
union_layout,
} => {
title = "TAG ID NOT IN UNION";
docs_before = vec![(
def_line,
f.concat([
f.reflow("The union "),
f.as_string(structure.as_str(interns)),
f.reflow(" defined here has layout "),
Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded),
]),
)];
f.concat([f.reflow("which has no tag of id "), f.as_string(tag_id)])
}
ProblemKind::TagUnionStructIndexOOB {
structure,
def_line,
tag_id,
index,
size,
} => {
title = "UNION ID AND PAYLOAD INDEX IS OUT-OF-BOUNDS";
docs_before = vec![(
def_line,
f.concat([
f.reflow("The union "),
f.as_string(structure.as_str(interns)),
f.reflow(" defined here has "),
f.as_string(size),
f.reflow(" payloads at ID "),
f.as_string(tag_id),
]),
)];
f.concat([
f.reflow("but is being indexed into field "),
f.as_string(index),
f.reflow(" here"),
])
}
ProblemKind::IndexIntoNullableTag {
structure,
def_line,
tag_id,
union_layout,
} => {
title = "INDEX INTO NULLABLE TAG";
docs_before = vec![(
def_line,
f.concat([
f.reflow("The union "),
f.as_string(structure.as_str(interns)),
f.reflow(" defined here has layout "),
Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded),
]),
)];
f.concat([
f.reflow("but is being indexed into the nullable variant "),
f.as_string(tag_id),
f.reflow(" here"),
])
}
ProblemKind::UnboxNotABox { symbol, def_line } => {
title = "ATTEMPTING TO UNBOX A NON-BOX";
docs_before = vec![(
def_line,
f.concat([
f.as_string(symbol.as_str(interns)),
f.reflow(" is not a box"),
]),
)];
f.reflow("but is being unboxed here")
}
ProblemKind::CreatingTagIdNotInUnion {
tag_id,
union_layout,
} => {
title = "NO SUCH ID FOR TAG UNION";
docs_before = vec![];
f.concat([
f.reflow("The variant "),
f.as_string(tag_id),
f.reflow(" is outside the target union layout "),
Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded),
])
}
ProblemKind::CreateTagPayloadMismatch {
num_needed,
num_given,
} => {
title = "WRONG NUMBER OF ARGUMENTS IN TAG UNION";
docs_before = vec![];
f.concat([
f.reflow("This tag union payload needs "),
f.as_string(num_needed),
f.reflow(" values, but is only given "),
f.as_string(num_given),
])
}
};
(title, docs_before, doc)
}
fn format_use_kind(use_kind: UseKind) -> &'static str {
match use_kind {
UseKind::Ret => "return value",
UseKind::TagExpr => "tag constructor",
UseKind::TagReuse => "tag reuse",
UseKind::TagPayloadArg => "tag's payload",
UseKind::ListElemExpr => "list element",
UseKind::CallArg => "call argument",
UseKind::JumpArg => "jump argument",
UseKind::CrashArg => "crash message",
UseKind::SwitchCond => "switch condition",
UseKind::ExpectCond => "expect condition",
UseKind::ExpectLookup => "lookup for an expect",
}
}
fn format_proc_spec<'a, 'd, I>(
f: &'d Arena<'d>,
interns: &'d Interns,
interner: &I,
symbol: Symbol,
proc_layout: ProcLayout<'a>,
) -> Doc<'d>
where
I: Interner<'a, Layout<'a>>,
{
f.concat([
f.as_string(symbol.as_str(interns)),
f.reflow(" : "),
format_proc_layout(f, interner, proc_layout),
])
}
fn format_proc_layout<'a, 'd, I>(
f: &'d Arena<'d>,
interner: &I,
proc_layout: ProcLayout<'a>,
) -> Doc<'d>
where
I: Interner<'a, Layout<'a>>,
{
let ProcLayout {
arguments,
result,
captures_niche,
} = proc_layout;
let args = f.intersperse(
arguments
.iter()
.map(|a| a.to_doc(f, interner, Parens::InFunction)),
f.reflow(", "),
);
let fun = f.concat([
args,
f.reflow(" -> "),
result.to_doc(f, interner, Parens::NotNeeded),
]);
let niche = if captures_niche == CapturesNiche::no_niche() {
f.reflow("(no niche)")
} else {
f.concat([
f.reflow("(niche {"),
f.intersperse(
captures_niche
.0
.iter()
.map(|c| c.to_doc(f, interner, Parens::NotNeeded)),
f.reflow(", "),
),
f.reflow("})"),
])
};
f.concat([fun, f.space(), niche])
}
fn stack<'d>(f: &'d Arena<'d>, docs: impl IntoIterator<Item = Doc<'d>>) -> Doc<'d> {
f.intersperse(docs, f.line().append(f.line()))
}

View File

@ -1204,7 +1204,7 @@ impl std::fmt::Debug for LambdaSet<'_> {
///
/// See also https://github.com/roc-lang/roc/issues/3336.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct CapturesNiche<'a>(&'a [Layout<'a>]);
pub struct CapturesNiche<'a>(pub(crate) &'a [Layout<'a>]);
impl CapturesNiche<'_> {
pub fn no_niche() -> Self {

View File

@ -22,4 +22,4 @@ pub mod tail_recursion;
//#[allow(clippy::ptr_arg)]
pub mod decision_tree;
pub mod checker;
pub mod debug;