feat: Move running kind inside a session to kind-driver

This commit is contained in:
Nicolas Abril 2023-05-09 12:56:51 +02:00
parent 2d0cda4faa
commit 0fdcff3a11
6 changed files with 171 additions and 160 deletions

View File

@ -1,14 +1,12 @@
#![feature(panic_info_message)]
use std::path::PathBuf;
use std::time::Instant;
use std::{fmt, io};
use clap::{Parser, Subcommand};
use driver::resolution::ResolutionError;
use kind_driver::session::Session;
use kind_report::data::{Diagnostic, FileCache, Log, Severity};
use kind_report::data::{FileCache, Log};
use kind_report::RenderConfig;
use std::panic;
@ -143,7 +141,7 @@ where
pub fn render_to_stderr<T, E>(render_config: &RenderConfig, session: &T, err: &E)
where
T: FileCache,
E: Report,
E: Report + ?Sized,
{
Report::render(
err,
@ -154,77 +152,16 @@ where
.unwrap();
}
pub fn compile_in_session<T>(
pub fn run_in_session<T>(
render_config: &RenderConfig,
root: PathBuf,
file: String,
compiled: bool,
fun: &mut dyn FnMut(&mut Session) -> anyhow::Result<T>,
action: &mut dyn FnMut(&mut Session) -> anyhow::Result<T>,
) -> anyhow::Result<T> {
let (rx, tx) = std::sync::mpsc::channel();
let mut session = Session::new(root, rx);
eprintln!();
render_to_stderr(
render_config,
&session,
&Log::Checking(format!("The file '{}'", file)),
);
let start = Instant::now();
let res = fun(&mut session);
let diagnostics = tx.try_iter().collect::<Vec<Box<dyn Diagnostic>>>();
let mut contains_error = false;
let mut hidden = 0;
let total = diagnostics.len() as u64;
for diagnostic in diagnostics {
if diagnostic.get_severity() == Severity::Error {
contains_error = true;
}
let is_root = diagnostic
.get_syntax_ctx()
.map(|x| x.is_root())
.unwrap_or_default();
if render_config.only_main && !is_root {
hidden += 1;
continue;
}
render_to_stderr(render_config, &session, &diagnostic)
}
if !contains_error {
render_to_stderr(
render_config,
&session,
&if compiled {
Log::Compiled(start.elapsed())
} else {
Log::Checked(start.elapsed())
},
);
eprintln!();
res
} else {
render_to_stderr(render_config, &session, &Log::Failed(start.elapsed(), total, hidden));
eprintln!();
match res {
Ok(_) => Err(ResolutionError.into()),
Err(res) => Err(res),
}
}
let log =
|session: &Session, report: &dyn Report| render_to_stderr(render_config, session, report);
driver::run_in_session(render_config, root, file, compiled, action, &log)
}
pub fn run_cli(config: Cli) -> anyhow::Result<()> {
@ -254,7 +191,7 @@ pub fn run_cli(config: Cli) -> anyhow::Result<()> {
match config.command {
Command::Check { file, coverage } => {
compile_in_session(&render_config, root, file.clone(), false, &mut |session| {
run_in_session(&render_config, root, file.clone(), false, &mut |session| {
let (_, rewrites) = driver::type_check_book(
session,
&PathBuf::from(file.clone()),
@ -270,7 +207,7 @@ pub fn run_cli(config: Cli) -> anyhow::Result<()> {
}
Command::ToHVM { file } => {
let result =
compile_in_session(&render_config, root, file.clone(), true, &mut |session| {
run_in_session(&render_config, root, file.clone(), true, &mut |session| {
let book = driver::erase_book(
session,
&PathBuf::from(file.clone()),
@ -282,22 +219,21 @@ pub fn run_cli(config: Cli) -> anyhow::Result<()> {
println!("{}", result);
}
Command::Run { file } => {
let res =
compile_in_session(&render_config, root, file.clone(), true, &mut |session| {
let path = PathBuf::from(file.clone());
let book = driver::erase_book(session, &path, entrypoints.clone())?;
driver::check_main_entry(session, &book)?;
let book = driver::compile_book_to_hvm(book, config.trace);
let (result, rewrites) = driver::execute_file(&book.to_string(), config.tids)?;
let res = run_in_session(&render_config, root, file.clone(), true, &mut |session| {
let path = PathBuf::from(file.clone());
let book = driver::erase_book(session, &path, entrypoints.clone())?;
driver::check_main_entry(session, &book)?;
let book = driver::compile_book_to_hvm(book, config.trace);
let (result, rewrites) = driver::execute_file(&book.to_string(), config.tids)?;
render_to_stderr(&render_config, session, &Log::Rewrites(rewrites));
render_to_stderr(&render_config, session, &Log::Rewrites(rewrites));
Ok(result)
})?;
Ok(result)
})?;
println!("{}", res);
}
Command::Show { file } => {
compile_in_session(&render_config, root, file.clone(), true, &mut |session| {
run_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::to_book(session, &PathBuf::from(file.clone()))
})
.map(|res| {
@ -306,49 +242,44 @@ pub fn run_cli(config: Cli) -> anyhow::Result<()> {
})?;
}
Command::ToKindCore { file } => {
let res =
compile_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::desugar_book(session, &PathBuf::from(file.clone()))
})?;
let res = run_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::desugar_book(session, &PathBuf::from(file.clone()))
})?;
print!("{}", res);
}
Command::Erase { file } => {
let res =
compile_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::erase_book(session, &PathBuf::from(file.clone()), entrypoints.clone())
})?;
let res = run_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::erase_book(session, &PathBuf::from(file.clone()), entrypoints.clone())
})?;
print!("{}", res);
}
Command::GenChecker { file, coverage } => {
let res =
compile_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::check_erasure_book(session, &PathBuf::from(file.clone()))
})?;
let res = run_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::check_erasure_book(session, &PathBuf::from(file.clone()))
})?;
print!("{}", driver::generate_checker(&res, coverage));
}
Command::Eval { file } => {
let res =
compile_in_session(&render_config, root, file.clone(), true, &mut |session| {
let book = driver::desugar_book(session, &PathBuf::from(file.clone()))?;
driver::check_main_desugared_entry(session, &book)?;
let (res, rewrites) = driver::eval_in_checker(&book);
let res = run_in_session(&render_config, root, file.clone(), true, &mut |session| {
let book = driver::desugar_book(session, &PathBuf::from(file.clone()))?;
driver::check_main_desugared_entry(session, &book)?;
let (res, rewrites) = driver::eval_in_checker(&book);
render_to_stderr(&render_config, session, &Log::Rewrites(rewrites));
render_to_stderr(&render_config, session, &Log::Rewrites(rewrites));
Ok(res)
})?;
Ok(res)
})?;
println!("{}", res);
}
Command::ToKDL { file, namespace } => {
let res =
compile_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::compile_book_to_kdl(
&PathBuf::from(file.clone()),
session,
&namespace.clone().unwrap_or("".to_string()),
entrypoints.clone(),
)
})?;
let res = run_in_session(&render_config, root, file.clone(), true, &mut |session| {
driver::compile_book_to_kdl(
&PathBuf::from(file.clone()),
session,
&namespace.clone().unwrap_or("".to_string()),
entrypoints.clone(),
)
})?;
println!("{}", res);
}
}
@ -357,9 +288,12 @@ pub fn run_cli(config: Cli) -> anyhow::Result<()> {
}
pub fn main() {
panic::set_hook(Box::new(|e| {
println!("\n[Error]: internal compiler error '{:?}' at {}", e.message().unwrap(), e.location().unwrap());
println!(
"\n[Error]: internal compiler error '{:?}' at {}",
e.message().unwrap(),
e.location().unwrap()
);
println!("Please submit a full report about this error at: https://github.com/HigherOrderCO/Kind/issues/new");
println!("It would help us a lot :)\n");
}));

View File

@ -1,14 +1,18 @@
use checker::eval;
use diagnostic::{DriverDiagnostic, GenericDriverError};
use kind_pass::{desugar, erasure, inline::inline_book};
use kind_report::data::FileCache;
use kind_report::{
data::{FileCache, Log, Severity},
report::Report,
RenderConfig,
};
use kind_span::SyntaxCtxIndex;
use hvm::language::{syntax as backend};
use hvm::language::syntax as backend;
use kind_tree::{concrete, desugared, untyped};
use resolution::ResolutionError;
use session::Session;
use std::{path::PathBuf};
use std::{path::PathBuf, time::Instant};
use kind_checker as checker;
@ -147,9 +151,7 @@ pub fn check_main_desugared_entry(
pub fn execute_file(file: &str, tids: Option<usize>) -> anyhow::Result<(String, u64)> {
match eval(file, "Main", false, tids) {
Ok((res, rewrites)) => {
Ok((res.to_string(), rewrites))
},
Ok((res, rewrites)) => Ok((res.to_string(), rewrites)),
Err(_) => anyhow::Result::Err(GenericDriverError.into()),
}
}
@ -161,3 +163,69 @@ pub fn eval_in_checker(book: &desugared::Book) -> (String, u64) {
pub fn generate_checker(book: &desugared::Book, check_coverage: bool) -> String {
checker::gen_checker(book, check_coverage, book.entrs.keys().cloned().collect())
}
pub fn run_in_session<T>(
render_config: &RenderConfig,
root: PathBuf,
file: String,
compiled: bool,
action: &mut dyn FnMut(&mut Session) -> anyhow::Result<T>,
log: &dyn Fn(&Session, &dyn Report),
) -> anyhow::Result<T> {
let (rx, tx) = std::sync::mpsc::channel();
let mut session = Session::new(root, rx);
log(&session, &Log::Empty);
log(&session, &Log::Checking(format!("The file '{}'", file)));
let start = Instant::now();
let res = action(&mut session);
let diagnostics = tx.try_iter().collect::<Vec<_>>();
let mut contains_error = false;
let mut hidden = 0;
let total = diagnostics.len() as u64;
for diagnostic in diagnostics {
if diagnostic.get_severity() == Severity::Error {
contains_error = true;
}
let is_root = diagnostic
.get_syntax_ctx()
.map(|x| x.is_root())
.unwrap_or_default();
if render_config.only_main && !is_root {
hidden += 1;
continue;
}
log(&session, &diagnostic);
}
if !contains_error {
log(
&session,
&if compiled {
Log::Compiled(start.elapsed())
} else {
Log::Checked(start.elapsed())
},
);
log(&session, &Log::Empty);
res
} else {
log(&session, &Log::Failed(start.elapsed(), total, hidden));
log(&session, &Log::Empty);
match res {
Ok(_) => Err(ResolutionError.into()),
Err(res) => Err(res),
}
}
}

View File

@ -73,6 +73,7 @@ pub enum Log {
Compiled(Duration),
Rewrites(u64),
Failed(Duration, u64, u64),
Empty,
}
pub trait Diagnostic {

View File

@ -11,11 +11,11 @@ use std::fmt::Write;
use std::path::PathBuf;
use yansi::Paint;
fn colorize_code<T: Write + Sized>(
fn colorize_code(
markers: &mut [&(Point, Point, &Marker)],
code_line: &str,
modify: &dyn Fn(&str) -> String,
fmt: &mut T,
fmt: &mut dyn Write,
) -> std::fmt::Result {
markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
let mut start = 0;
@ -47,12 +47,12 @@ fn colorize_code<T: Write + Sized>(
Ok(())
}
fn mark_inlined<T: Write + Sized>(
fn mark_inlined(
prefix: &str,
code: &str,
config: &RenderConfig,
inline_markers: &mut [&(Point, Point, &Marker)],
fmt: &mut T,
fmt: &mut dyn Write,
) -> std::fmt::Result {
inline_markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
let mut start = 0;
@ -148,7 +148,7 @@ impl Color {
}
impl Renderable<Classic> for Severity {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
use Severity::*;
let painted = match self {
@ -162,7 +162,7 @@ impl Renderable<Classic> for Severity {
}
impl<'a> Renderable<Classic> for Header<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
Renderable::<Classic>::render(self.severity, fmt, cache, config)?;
fmt.write_str(&Paint::new(&self.title).bold().to_string())?;
fmt.write_char('\n')
@ -170,7 +170,7 @@ impl<'a> Renderable<Classic> for Header<'a> {
}
impl Renderable<Classic> for Subtitle {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
match self {
Subtitle::Normal(color, phr) | Subtitle::Field(color, phr) => {
let bullet = color.colorize(config.chars.bullet);
@ -194,7 +194,7 @@ impl Renderable<Classic> for Subtitle {
}
impl<'a> Renderable<Classic> for Word {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
match self {
Word::Normal(str) => write!(fmt, "{} ", Paint::new(str)),
Word::Dimmed(str) => write!(fmt, "{} ", Paint::new(str).dimmed()),
@ -205,7 +205,7 @@ impl<'a> Renderable<Classic> for Word {
}
impl<'a> Renderable<Classic> for Subtitles<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
if !self.0.is_empty() {
writeln!(fmt)?;
}
@ -215,7 +215,7 @@ impl<'a> Renderable<Classic> for Subtitles<'a> {
}
impl<'a> Renderable<Classic> for CodeBlock<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, config: &RenderConfig) -> Res {
let guide = LineGuide::get(self.code);
let point = guide.find(self.markers.0[0].position.start);
@ -342,7 +342,7 @@ impl<'a> Renderable<Classic> for CodeBlock<'a> {
}
impl Renderable<Classic> for Log {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
match self {
Log::Checking(file) => {
writeln!(
@ -390,12 +390,13 @@ impl Renderable<Classic> for Log {
u64
)
}
Log::Empty => writeln!(fmt, ""),
}
}
}
impl<'a> Renderable<Classic> for Markers<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
let groups = group_markers(&self.0);
let is_empty = groups.is_empty();
let current = PathBuf::from(".").canonicalize().unwrap();
@ -424,7 +425,7 @@ impl<'a> Renderable<Classic> for Markers<'a> {
}
impl<'a> Renderable<Classic> for Hints<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
for hint in self.0 {
writeln!(
fmt,
@ -440,7 +441,7 @@ impl<'a> Renderable<Classic> for Hints<'a> {
}
impl Renderable<Classic> for DiagnosticFrame {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
write!(fmt, " ")?;
Renderable::<Classic>::render(&self.header(), fmt, cache, config)?;

View File

@ -7,10 +7,10 @@ use crate::{report::*, RenderConfig};
use super::{CodeBlock, Compact, Renderable, Res};
fn mark_code<T: Write + Sized>(
fn mark_code(
markers: &mut [&(Point, Point, &Marker)],
code_line: &str,
fmt: &mut T,
fmt: &mut dyn Write,
) -> std::fmt::Result {
markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
let mut start = 0;
@ -42,7 +42,7 @@ fn mark_code<T: Write + Sized>(
}
impl<'a> Renderable<Compact> for Word {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
match self {
Word::Normal(str) => write!(fmt, "{} ", str),
Word::Dimmed(str) => write!(fmt, "{} ", str),
@ -53,7 +53,7 @@ impl<'a> Renderable<Compact> for Word {
}
impl Renderable<Compact> for Subtitle {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
match self {
Subtitle::Field(_, phr) => writeln!(fmt, "{}", phr.to_lowercase()),
Subtitle::Normal(_, phr) => writeln!(fmt, "- {}", phr),
@ -69,7 +69,7 @@ impl Renderable<Compact> for Subtitle {
}
impl<'a> Renderable<Compact> for Subtitles<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
if !self.0.is_empty() {
writeln!(fmt)?;
}
@ -79,7 +79,7 @@ impl<'a> Renderable<Compact> for Subtitles<'a> {
}
impl Renderable<Compact> for Severity {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
use Severity::*;
let painted = match self {
@ -93,13 +93,13 @@ impl Renderable<Compact> for Severity {
}
impl<'a> Renderable<Compact> for Header<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
writeln!(fmt, "{}", &self.title.to_lowercase())
}
}
impl<'a> Renderable<Compact> for CodeBlock<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _8778: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
writeln!(fmt, "location")?;
let guide = LineGuide::get(self.code);
@ -133,7 +133,7 @@ impl<'a> Renderable<Compact> for CodeBlock<'a> {
}
impl<'a> Renderable<Compact> for Markers<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
let groups = group_markers(&self.0);
let current = PathBuf::from(".").canonicalize().unwrap();
@ -155,12 +155,7 @@ impl<'a> Renderable<Compact> for Markers<'a> {
}
impl Renderable<Compact> for DiagnosticFrame {
fn render<U: std::fmt::Write, C: crate::data::FileCache>(
&self,
fmt: &mut U,
cache: &C,
config: &crate::RenderConfig,
) -> super::Res {
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
Renderable::<Compact>::render(&self.header(), fmt, cache, config)?;
Renderable::<Compact>::render(&self.subtitles(), fmt, cache, config)?;
Renderable::<Compact>::render(&self.markers(), fmt, cache, config)?;
@ -170,7 +165,7 @@ impl Renderable<Compact> for DiagnosticFrame {
}
impl Renderable<Compact> for Log {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
match self {
Log::Compiled(_) => writeln!(fmt, "compiled"),
Log::Checked(_) => writeln!(fmt, "checked"),

View File

@ -1,7 +1,10 @@
use std::{fmt::Write, path::Path};
use crate::{data::{FileCache, Diagnostic, DiagnosticFrame, Log}, RenderConfig};
use super::code::FileMarkers;
use crate::{
data::{Diagnostic, DiagnosticFrame, FileCache, Log},
RenderConfig,
};
pub mod classic;
pub mod compact;
@ -35,17 +38,20 @@ pub enum Mode {
pub(crate) struct CodeBlock<'a> {
pub code: &'a str,
pub path: &'a Path,
pub markers: &'a FileMarkers
pub markers: &'a FileMarkers,
}
/// A type class for renderable error reports and messages. It's useful
/// to change easily things without problems.
pub trait Renderable<T> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res;
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res;
}
impl<'a, T, E> Renderable<T> for Vec<E> where E : Renderable<T> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
impl<'a, T, E> Renderable<T> for Vec<E>
where
E: Renderable<T>,
{
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
for elem in self {
elem.render(fmt, cache, config)?;
}
@ -53,14 +59,20 @@ impl<'a, T, E> Renderable<T> for Vec<E> where E : Renderable<T> {
}
}
impl<T> Renderable<T> for Box<dyn Diagnostic> where DiagnosticFrame: Renderable<T> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
impl<T> Renderable<T> for Box<dyn Diagnostic>
where
DiagnosticFrame: Renderable<T>,
{
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
Renderable::<T>::render(&self.to_diagnostic_frame(config), fmt, cache, config)
}
}
pub trait Report where Self : Renderable<Classic> + Renderable<Compact> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
pub trait Report
where
Self: Renderable<Classic> + Renderable<Compact>,
{
fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
match config.mode {
Mode::Classic => Renderable::<Classic>::render(self, fmt, cache, config),
Mode::Compact => Renderable::<Compact>::render(self, fmt, cache, config),
@ -69,4 +81,4 @@ pub trait Report where Self : Renderable<Classic> + Renderable<Compact> {
}
impl Report for Box<dyn Diagnostic> {}
impl Report for Log {}
impl Report for Log {}