mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-11 05:34:11 +03:00
Format problems reported by the IR checker
This commit is contained in:
parent
a84aebf2d3
commit
6e72307736
5
crates/compiler/mono/src/debug.rs
Normal file
5
crates/compiler/mono/src/debug.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod checker;
|
||||
mod report;
|
||||
|
||||
pub use checker::{check_procs, Problem, Problems};
|
||||
pub use report::format_problems;
|
@ -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 {
|
490
crates/compiler/mono/src/debug/report.rs
Normal file
490
crates/compiler/mono/src/debug/report.rs
Normal 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()))
|
||||
}
|
@ -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 {
|
||||
|
@ -22,4 +22,4 @@ pub mod tail_recursion;
|
||||
//#[allow(clippy::ptr_arg)]
|
||||
pub mod decision_tree;
|
||||
|
||||
pub mod checker;
|
||||
pub mod debug;
|
||||
|
Loading…
Reference in New Issue
Block a user