diff --git a/Cargo.lock b/Cargo.lock index 31643c9761..83ea321b54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,6 +443,16 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "component-fuzz-util" version = "0.0.0" @@ -544,7 +554,6 @@ dependencies = [ "gimli", "hashbrown", "log", - "miette", "regalloc2", "serde", "sha2 0.10.2", @@ -645,8 +654,8 @@ dependencies = [ name = "cranelift-isle" version = "0.91.0" dependencies = [ + "codespan-reporting", "log", - "miette", "tempfile", ] @@ -1534,12 +1543,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "is_ci" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" - [[package]] name = "isle-fuzz" version = "0.0.0" @@ -1557,7 +1560,6 @@ dependencies = [ "clap 3.2.8", "cranelift-isle", "env_logger 0.9.0", - "miette", ] [[package]] @@ -1763,37 +1765,6 @@ dependencies = [ "autocfg 1.1.0", ] -[[package]] -name = "miette" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ec753a43fd71bb5f28751c9ec17fbe89d6d26ca8282d1e1f82f5ac3dbd5581e" -dependencies = [ - "atty", - "backtrace", - "miette-derive", - "once_cell", - "owo-colors", - "supports-color", - "supports-hyperlinks", - "supports-unicode", - "terminal_size", - "textwrap 0.15.0", - "thiserror", - "unicode-width", -] - -[[package]] -name = "miette-derive" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdfc33ea15c5446600f91d319299dd40301614afff7143cdfa9bf4c09da3ca64" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "miniz_oxide" version = "0.5.1" @@ -2008,12 +1979,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -[[package]] -name = "owo-colors" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" - [[package]] name = "p256" version = "0.9.0" @@ -2681,12 +2646,6 @@ dependencies = [ "serde", ] -[[package]] -name = "smawk" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" - [[package]] name = "socket2" version = "0.4.4" @@ -2751,34 +2710,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "supports-color" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" -dependencies = [ - "atty", - "is_ci", -] - -[[package]] -name = "supports-hyperlinks" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" -dependencies = [ - "atty", -] - -[[package]] -name = "supports-unicode" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" -dependencies = [ - "atty", -] - [[package]] name = "symbolic_expressions" version = "5.0.3" @@ -2896,11 +2827,6 @@ name = "textwrap" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" -dependencies = [ - "smawk", - "unicode-linebreak", - "unicode-width", -] [[package]] name = "thiserror" @@ -3051,15 +2977,6 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" -[[package]] -name = "unicode-linebreak" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" -dependencies = [ - "regex", -] - [[package]] name = "unicode-normalization" version = "0.1.21" diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml index 2c989c87ab..bb881cd937 100644 --- a/cranelift/codegen/Cargo.toml +++ b/cranelift/codegen/Cargo.toml @@ -40,7 +40,6 @@ criterion = "0.3" [build-dependencies] cranelift-codegen-meta = { path = "meta", version = "0.91.0" } cranelift-isle = { path = "../isle/isle", version = "=0.91.0" } -miette = { version = "5.1.0", features = ["fancy"], optional = true } [features] default = ["std", "unwind"] @@ -101,7 +100,7 @@ incremental-cache = [ souper-harvest = ["souper-ir", "souper-ir/stringify"] # Provide fancy Miette-produced errors for ISLE. -isle-errors = ["miette", "cranelift-isle/miette-errors"] +isle-errors = ["cranelift-isle/fancy-errors"] # Put ISLE generated files in isle_generated_code/, for easier # inspection, rather than inside of target/. diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs index 085b5f0151..e98bc3df52 100644 --- a/cranelift/codegen/build.rs +++ b/cranelift/codegen/build.rs @@ -15,6 +15,7 @@ // current directory is used to find the sources. use cranelift_codegen_meta as meta; +use cranelift_isle::error::Errors; use std::env; use std::io::Read; @@ -288,13 +289,16 @@ fn build_isle( } if let Err(e) = run_compilation(compilation) { - eprintln!("Error building ISLE files: {:?}", e); - let mut source = e.source(); - while let Some(e) = source { - eprintln!("{:?}", e); - source = e.source(); - } had_error = true; + eprintln!("Error building ISLE files:"); + eprintln!("{:?}", e); + #[cfg(not(feature = "isle-errors"))] + { + eprintln!("To see a more detailed error report, run: "); + eprintln!(); + eprintln!(" $ cargo check -p cranelift-codegen --features isle-errors"); + eprintln!(); + } } } @@ -311,21 +315,16 @@ fn build_isle( /// /// NB: This must happen *after* the `cranelift-codegen-meta` functions, since /// it consumes files generated by them. -fn run_compilation( - compilation: &IsleCompilation, -) -> Result<(), Box> { +fn run_compilation(compilation: &IsleCompilation) -> Result<(), Errors> { use cranelift_isle as isle; eprintln!("Rebuilding {}", compilation.output.display()); - let code = (|| { - let lexer = isle::lexer::Lexer::from_files( - compilation - .inputs - .iter() - .chain(compilation.untracked_inputs.iter()), - )?; - let defs = isle::parser::parse(lexer)?; + let code = { + let file_paths = compilation + .inputs + .iter() + .chain(compilation.untracked_inputs.iter()); let mut options = isle::codegen::CodegenOptions::default(); // Because we include!() the generated ISLE source, we cannot @@ -335,62 +334,8 @@ fn run_compilation( // https://github.com/rust-lang/rust/issues/47995.) options.exclude_global_allow_pragmas = true; - isle::compile::compile(&defs, &options) - })() - .map_err(|e| { - // Make sure to include the source snippets location info along with - // the error messages. - - #[cfg(feature = "isle-errors")] - { - let report = miette::Report::new(e); - return DebugReport(report); - - struct DebugReport(miette::Report); - - impl std::fmt::Display for DebugReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - self.0.handler().debug(&*self.0, f) - } - } - - impl std::fmt::Debug for DebugReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) - } - } - - impl std::error::Error for DebugReport {} - } - #[cfg(not(feature = "isle-errors"))] - { - return DebugReport(format!("{}", e)); - - struct DebugReport(String); - - impl std::fmt::Display for DebugReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - writeln!(f, "ISLE errors:\n\n{}\n", self.0)?; - writeln!(f, "To see a more detailed error report, run: ")?; - writeln!(f, "")?; - writeln!( - f, - " $ cargo check -p cranelift-codegen --features isle-errors" - )?; - writeln!(f, "")?; - Ok(()) - } - } - - impl std::fmt::Debug for DebugReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) - } - } - - impl std::error::Error for DebugReport {} - } - })?; + isle::compile::from_files(file_paths, &options)? + }; let code = rustfmt(&code).unwrap_or_else(|e| { println!( @@ -404,7 +349,8 @@ fn run_compilation( "Writing ISLE-generated Rust code to {}", compilation.output.display() ); - std::fs::write(&compilation.output, code)?; + std::fs::write(&compilation.output, code) + .map_err(|e| Errors::from_io(e, "failed writing output"))?; Ok(()) } diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index 185178d6f3..961036c755 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -9,8 +9,8 @@ repository = "https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/i version = "0.91.0" [dependencies] +codespan-reporting = { version = "0.11.1", optional = true } log = { workspace = true, optional = true } -miette = { version = "5.1.0", optional = true } [dev-dependencies] tempfile = "3" @@ -19,4 +19,4 @@ tempfile = "3" default = [] logging = ["log"] -miette-errors = ["miette"] +fancy-errors = ["codespan-reporting"] diff --git a/cranelift/isle/isle/src/compile.rs b/cranelift/isle/isle/src/compile.rs index 23c11df1bb..3b6df764fe 100644 --- a/cranelift/isle/isle/src/compile.rs +++ b/cranelift/isle/isle/src/compile.rs @@ -1,13 +1,25 @@ //! Compilation process, from AST to Sema to Sequences of Insts. -use crate::error::Result; +use std::path::Path; + +use crate::error::Errors; use crate::{ast, codegen, sema, trie}; /// Compile the given AST definitions into Rust source code. -pub fn compile(defs: &ast::Defs, options: &codegen::CodegenOptions) -> Result { +pub fn compile(defs: &ast::Defs, options: &codegen::CodegenOptions) -> Result { let mut typeenv = sema::TypeEnv::from_ast(defs)?; let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?; crate::overlap::check(&mut typeenv, &termenv)?; let tries = trie::build_tries(&termenv); Ok(codegen::codegen(&typeenv, &termenv, &tries, options)) } + +/// Compile the given files into Rust source code. +pub fn from_files>( + inputs: impl IntoIterator, + options: &codegen::CodegenOptions, +) -> Result { + let lexer = crate::lexer::Lexer::from_files(inputs)?; + let defs = crate::parser::parse(lexer)?; + compile(&defs, options) +} diff --git a/cranelift/isle/isle/src/error.rs b/cranelift/isle/isle/src/error.rs index 9115be6367..d8c543c5bc 100644 --- a/cranelift/isle/isle/src/error.rs +++ b/cranelift/isle/isle/src/error.rs @@ -4,16 +4,75 @@ use std::sync::Arc; use crate::lexer::Pos; -/// Either `Ok(T)` or `Err(isle::Error)`. -pub type Result = std::result::Result; +/// A collection of errors from attempting to compile some ISLE source files. +pub struct Errors { + /// The individual errors. + pub errors: Vec, + pub(crate) filenames: Vec>, + pub(crate) file_texts: Vec>, +} + +impl std::fmt::Debug for Errors { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.errors.is_empty() { + return Ok(()); + } + let diagnostics = Vec::from_iter(self.errors.iter().map(|e| { + let message = match e { + Error::IoError { context, .. } => format!("{}", context), + Error::ParseError { msg, .. } => format!("parse error: {}", msg), + Error::TypeError { msg, .. } => format!("type error: {}", msg), + Error::UnreachableError { msg, .. } => format!("unreachable rule: {}", msg), + Error::OverlapError { msg, .. } => format!("overlap error: {}", msg), + }; + + let labels = match e { + Error::IoError { .. } => vec![], + + Error::ParseError { span, .. } + | Error::TypeError { span, .. } + | Error::UnreachableError { span, .. } => { + vec![Label::primary(span.from.file, span)] + } + + Error::OverlapError { rules, .. } => { + let mut labels = vec![Label::primary(rules[0].from.file, &rules[0])]; + labels.extend( + rules[1..] + .iter() + .map(|span| Label::secondary(span.from.file, span)), + ); + labels + } + }; + + let mut sources = Vec::new(); + let mut source = e.source(); + while let Some(e) = source { + sources.push(format!("{:?}", e)); + source = std::error::Error::source(e); + } + + Diagnostic::error() + .with_message(message) + .with_labels(labels) + .with_notes(sources) + })); + self.emit(f, diagnostics)?; + if self.errors.len() > 1 { + writeln!(f, "found {} errors", self.errors.len())?; + } + Ok(()) + } +} /// Errors produced by ISLE. -#[derive(Clone, Debug)] +#[derive(Debug)] pub enum Error { /// An I/O error. IoError { /// The underlying I/O error. - error: Arc, + error: std::io::Error, /// The context explaining what caused the I/O error. context: String, }, @@ -23,9 +82,6 @@ pub enum Error { /// The error message. msg: String, - /// The input ISLE source. - src: Source, - /// The location of the parse error. span: Span, }, @@ -35,9 +91,6 @@ pub enum Error { /// The error message. msg: String, - /// The input ISLE source. - src: Source, - /// The location of the type error. span: Span, }, @@ -47,9 +100,6 @@ pub enum Error { /// The error message. msg: String, - /// The input ISLE source. - src: Source, - /// The location of the unreachable rule. span: Span, }, @@ -62,153 +112,98 @@ pub enum Error { /// The locations of all the rules that overlap. When there are more than two rules /// present, the first rule is the one with the most overlaps (likely a fall-through /// wildcard case). - rules: Vec<(Source, Span)>, + rules: Vec, }, - - /// Multiple errors. - Errors(Vec), } -impl Error { - /// Create a `isle::Error` from the given I/O error and context. +impl Errors { + /// Create `isle::Errors` from the given I/O error and context. pub fn from_io(error: std::io::Error, context: impl Into) -> Self { - Error::IoError { - error: Arc::new(error), - context: context.into(), + Errors { + errors: vec![Error::IoError { + error, + context: context.into(), + }], + filenames: Vec::new(), + file_texts: Vec::new(), } } -} -impl From> for Error { - fn from(es: Vec) -> Self { - Error::Errors(es) + #[cfg(feature = "fancy-errors")] + fn emit( + &self, + f: &mut std::fmt::Formatter, + diagnostics: Vec>, + ) -> std::fmt::Result { + use codespan_reporting::term::termcolor; + let w = termcolor::BufferWriter::stderr(termcolor::ColorChoice::Auto); + let mut b = w.buffer(); + let mut files = codespan_reporting::files::SimpleFiles::new(); + for (name, source) in self.filenames.iter().zip(self.file_texts.iter()) { + files.add(name, source); + } + for diagnostic in diagnostics { + codespan_reporting::term::emit(&mut b, &Default::default(), &files, &diagnostic) + .map_err(|_| std::fmt::Error)?; + } + let b = b.into_inner(); + let b = std::str::from_utf8(&b).map_err(|_| std::fmt::Error)?; + f.write_str(b) + } + + #[cfg(not(feature = "fancy-errors"))] + fn emit( + &self, + f: &mut std::fmt::Formatter, + diagnostics: Vec>, + ) -> std::fmt::Result { + let line_starts: Vec> = self + .file_texts + .iter() + .map(|text| { + let mut end = 0; + text.split_inclusive('\n') + .map(|line| { + let start = end; + end += line.len(); + start + }) + .collect() + }) + .collect(); + let pos = |file_id: usize, offset| { + let starts = &line_starts[file_id]; + let line = starts.partition_point(|&start| start <= offset); + let text = &self.file_texts[file_id]; + let line_range = starts[line - 1]..starts.get(line).copied().unwrap_or(text.len()); + let col = offset - line_range.start + 1; + format!( + "{}:{}:{}: {}", + self.filenames[file_id], line, col, &text[line_range] + ) + }; + for diagnostic in diagnostics { + writeln!(f, "{}", diagnostic.message)?; + for label in diagnostic.labels { + f.write_str(&pos(label.file_id, label.range.start))?; + } + for note in diagnostic.notes { + writeln!(f, "{}", note)?; + } + } + Ok(()) } } impl Error { - fn unwrap_errors(&self) -> &[Error] { - match self { - Error::Errors(e) => e, - _ => panic!("`isle::Error::unwrap_errors` on non-`isle::Error::Errors`"), - } - } -} - -impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Error::IoError { error, .. } => Some(&*error as &dyn std::error::Error), + Error::IoError { error, .. } => Some(error), _ => None, } } } -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Error::IoError { context, .. } => write!(f, "{}", context), - - // Include locations directly in the `Display` output when - // we're not wrapping errors with miette (which provides - // its own way of showing locations and context). - #[cfg(not(feature = "miette-errors"))] - Error::ParseError { src, span, msg, .. } => write!( - f, - "{}: parse error: {}", - span.from.pretty_print_with_filename(&*src.name), - msg - ), - #[cfg(not(feature = "miette-errors"))] - Error::TypeError { src, span, msg, .. } => write!( - f, - "{}: type error: {}", - span.from.pretty_print_with_filename(&*src.name), - msg - ), - - #[cfg(feature = "miette-errors")] - Error::ParseError { msg, .. } => write!(f, "parse error: {}", msg), - #[cfg(feature = "miette-errors")] - Error::TypeError { msg, .. } => write!(f, "type error: {}", msg), - - Error::UnreachableError { src, span, msg } => { - write!( - f, - "{}: unreachable rule: {}", - span.from.pretty_print_with_filename(&*src.name), - msg - ) - } - - Error::OverlapError { msg, rules, .. } => { - writeln!(f, "overlap error: {}\n{}", msg, OverlappingRules(&rules)) - } - - Error::Errors(_) => write!( - f, - "{}found {} errors", - DisplayErrors(self.unwrap_errors()), - self.unwrap_errors().len(), - ), - } - } -} - -struct DisplayErrors<'a>(&'a [Error]); -impl std::fmt::Display for DisplayErrors<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for e in self.0 { - writeln!(f, "{}", e)?; - } - Ok(()) - } -} - -struct OverlappingRules<'a>(&'a [(Source, Span)]); -impl std::fmt::Display for OverlappingRules<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for (src, span) in self.0 { - writeln!(f, " {}", span.from.pretty_print_with_filename(&*src.name))?; - } - Ok(()) - } -} - -/// A source file and its contents. -#[derive(Clone)] -pub struct Source { - /// The name of this source file. - pub name: Arc, - /// The text of this source file. - #[allow(unused)] // Used only when miette is enabled. - pub text: Arc, -} - -impl std::fmt::Debug for Source { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Source") - .field("name", &self.name) - .field("source", &""); - Ok(()) - } -} - -impl Source { - pub(crate) fn new(name: Arc, text: Arc) -> Self { - Self { name, text } - } - - /// Get this source's file name. - pub fn name(&self) -> &Arc { - &self.name - } - - /// Get this source's text contents. - pub fn text(&self) -> &Arc { - &self.name - } -} - /// A span in a given source. #[derive(Clone, Debug)] pub struct Span { @@ -237,3 +232,69 @@ impl Span { } } } + +impl From<&Span> for std::ops::Range { + fn from(span: &Span) -> Self { + span.from.offset..span.to.offset + } +} + +use diagnostic::{Diagnostic, Label}; + +#[cfg(feature = "fancy-errors")] +use codespan_reporting::diagnostic; + +#[cfg(not(feature = "fancy-errors"))] +/// Minimal versions of types from codespan-reporting. +mod diagnostic { + use std::ops::Range; + + pub struct Diagnostic { + pub message: String, + pub labels: Vec>, + pub notes: Vec, + } + + impl Diagnostic { + pub fn error() -> Self { + Self { + message: String::new(), + labels: Vec::new(), + notes: Vec::new(), + } + } + + pub fn with_message(mut self, message: impl Into) -> Self { + self.message = message.into(); + self + } + + pub fn with_labels(mut self, labels: Vec>) -> Self { + self.labels = labels; + self + } + + pub fn with_notes(mut self, notes: Vec) -> Self { + self.notes = notes; + self + } + } + + pub struct Label { + pub file_id: FileId, + pub range: Range, + } + + impl Label { + pub fn primary(file_id: FileId, range: impl Into>) -> Self { + Self { + file_id, + range: range.into(), + } + } + + pub fn secondary(file_id: FileId, range: impl Into>) -> Self { + Self::primary(file_id, range) + } + } +} diff --git a/cranelift/isle/isle/src/error_miette.rs b/cranelift/isle/isle/src/error_miette.rs deleted file mode 100644 index 555b4e4aca..0000000000 --- a/cranelift/isle/isle/src/error_miette.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! miette-specific trait implementations. This is kept separate so -//! that we can have a very lightweight build of the ISLE compiler as -//! part of the Cranelift build process without pulling in any -//! dependencies. - -use crate::error::{Error, Source, Span}; -use miette::{SourceCode, SourceSpan}; - -impl From for SourceSpan { - fn from(span: Span) -> Self { - SourceSpan::new(span.from.offset.into(), span.to.offset.into()) - } -} - -impl SourceCode for Source { - fn read_span<'a>( - &'a self, - span: &SourceSpan, - context_lines_before: usize, - context_lines_after: usize, - ) -> std::result::Result + 'a>, miette::MietteError> { - let contents = self - .text - .read_span(span, context_lines_before, context_lines_after)?; - Ok(Box::new(miette::MietteSpanContents::new_named( - self.name.to_string(), - contents.data(), - contents.span().clone(), - contents.line(), - contents.column(), - contents.line_count(), - ))) - } -} - -impl miette::Diagnostic for Error { - fn labels(&self) -> Option + '_>> { - match self { - Self::ParseError { msg, span, .. } | Self::TypeError { msg, span, .. } => { - Some(Box::new( - vec![miette::LabeledSpan::new_with_span( - Some(msg.clone()), - span.clone(), - )] - .into_iter(), - )) - } - _ => None, - } - } - fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> { - match self { - Self::ParseError { src, .. } | Self::TypeError { src, .. } => Some(src), - _ => None, - } - } - fn related(&self) -> Option + '_>> { - match self { - Self::Errors(errors) => Some(Box::new( - errors.iter().map(|x| x as &dyn miette::Diagnostic), - )), - _ => None, - } - } -} diff --git a/cranelift/isle/isle/src/lexer.rs b/cranelift/isle/isle/src/lexer.rs index 058ddf7765..d76b1e16a5 100644 --- a/cranelift/isle/isle/src/lexer.rs +++ b/cranelift/isle/isle/src/lexer.rs @@ -1,10 +1,12 @@ //! Lexer for the ISLE language. -use crate::error::{Error, Result, Source, Span}; +use crate::error::{Error, Errors, Span}; use std::borrow::Cow; use std::path::Path; use std::sync::Arc; +type Result = std::result::Result; + /// The lexer. /// /// Breaks source text up into a sequence of tokens (with source positions). @@ -43,19 +45,10 @@ pub struct Pos { } impl Pos { - /// Print this source position as `file.isle:12:34`. - pub fn pretty_print(&self, filenames: &[Arc]) -> String { - self.pretty_print_with_filename(&filenames[self.file]) - } /// Print this source position as `file.isle line 12`. pub fn pretty_print_line(&self, filenames: &[Arc]) -> String { format!("{} line {}", filenames[self.file], self.line) } - /// As above for `pretty_print`, but with the specific filename - /// already provided. - pub fn pretty_print_with_filename(&self, filename: &str) -> String { - format!("{}:{}:{}", filename, self.line, self.col) - } } /// A token of ISLE source. @@ -107,7 +100,7 @@ impl<'a> Lexer<'a> { filenames.push(f.display().to_string().into()); let s = std::fs::read_to_string(f) - .map_err(|e| Error::from_io(e, format!("failed to read file: {}", f.display())))?; + .map_err(|e| Errors::from_io(e, format!("failed to read file: {}", f.display())))?; file_texts.push(s.into()); } @@ -165,14 +158,14 @@ impl<'a> Lexer<'a> { } } - fn error(&self, pos: Pos, msg: impl Into) -> Error { - Error::ParseError { - msg: msg.into(), - src: Source::new( - self.filenames[pos.file].clone(), - self.file_texts[pos.file].clone(), - ), - span: Span::new_single(self.pos()), + fn error(&self, pos: Pos, msg: impl Into) -> Errors { + Errors { + errors: vec![Error::ParseError { + msg: msg.into(), + span: Span::new_single(pos), + }], + filenames: self.filenames.clone(), + file_texts: self.file_texts.clone(), } } diff --git a/cranelift/isle/isle/src/lib.rs b/cranelift/isle/isle/src/lib.rs index 570aeb4e4c..1248bcbf48 100644 --- a/cranelift/isle/isle/src/lib.rs +++ b/cranelift/isle/isle/src/lib.rs @@ -211,6 +211,3 @@ pub mod parser; pub mod sema; pub mod trie; pub mod trie_again; - -#[cfg(feature = "miette-errors")] -mod error_miette; diff --git a/cranelift/isle/isle/src/overlap.rs b/cranelift/isle/isle/src/overlap.rs index 2e0f34a553..4411de8c88 100644 --- a/cranelift/isle/isle/src/overlap.rs +++ b/cranelift/isle/isle/src/overlap.rs @@ -3,20 +3,24 @@ use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; -use crate::error::{Error, Result, Source, Span}; +use crate::error::{self, Error, Span}; use crate::lexer::Pos; use crate::sema::{TermEnv, TermId, TermKind, TypeEnv}; use crate::trie_again; /// Check for overlap. -pub fn check(tyenv: &TypeEnv, termenv: &TermEnv) -> Result<()> { - let (terms, mut errors) = trie_again::build(termenv, tyenv); - errors.append(&mut check_overlaps(terms, termenv).report(tyenv)); +pub fn check(tyenv: &TypeEnv, termenv: &TermEnv) -> Result<(), error::Errors> { + let (terms, mut errors) = trie_again::build(termenv); + errors.append(&mut check_overlaps(terms, termenv).report()); - match errors.len() { - 0 => Ok(()), - 1 => Err(errors.pop().unwrap()), - _ => Err(Error::Errors(errors)), + if errors.is_empty() { + Ok(()) + } else { + Err(error::Errors { + errors, + filenames: tyenv.filenames.clone(), + file_texts: tyenv.file_texts.clone(), + }) } } @@ -32,19 +36,9 @@ impl Errors { /// nodes from the graph with the highest degree, reporting errors for them and their direct /// connections. The goal with reporting errors this way is to prefer reporting rules that /// overlap with many others first, and then report other more targeted overlaps later. - fn report(mut self, tyenv: &TypeEnv) -> Vec { + fn report(mut self) -> Vec { let mut errors = Vec::new(); - let get_info = |pos: Pos| { - let file = pos.file; - let src = Source::new( - tyenv.filenames[file].clone(), - tyenv.file_texts[file].clone(), - ); - let span = Span::new_single(pos); - (src, span) - }; - while let Some((&pos, _)) = self .nodes .iter() @@ -62,9 +56,9 @@ impl Errors { } // build the real error - let mut rules = vec![get_info(pos)]; + let mut rules = vec![Span::new_single(pos)]; - rules.extend(node.into_iter().map(get_info)); + rules.extend(node.into_iter().map(Span::new_single)); errors.push(Error::OverlapError { msg: String::from("rules are overlapping"), @@ -73,7 +67,7 @@ impl Errors { } errors.sort_by_key(|err| match err { - Error::OverlapError { rules, .. } => rules.first().unwrap().1.from, + Error::OverlapError { rules, .. } => rules.first().unwrap().from, _ => Pos::default(), }); errors diff --git a/cranelift/isle/isle/src/parser.rs b/cranelift/isle/isle/src/parser.rs index 6975777bee..03b9d09033 100644 --- a/cranelift/isle/isle/src/parser.rs +++ b/cranelift/isle/isle/src/parser.rs @@ -1,9 +1,11 @@ //! Parser for ISLE language. use crate::ast::*; -use crate::error::*; +use crate::error::{Error, Errors, Span}; use crate::lexer::{Lexer, Pos, Token}; +type Result = std::result::Result; + /// Parse the top-level ISLE definitions and return their AST. pub fn parse(lexer: Lexer) -> Result { let parser = Parser::new(lexer); @@ -32,14 +34,14 @@ impl<'a> Parser<'a> { Parser { lexer } } - fn error(&self, pos: Pos, msg: String) -> Error { - Error::ParseError { - msg, - src: Source::new( - self.lexer.filenames[pos.file].clone(), - self.lexer.file_texts[pos.file].clone(), - ), - span: Span::new_single(pos), + fn error(&self, pos: Pos, msg: String) -> Errors { + Errors { + errors: vec![Error::ParseError { + msg, + span: Span::new_single(pos), + }], + filenames: self.lexer.filenames.clone(), + file_texts: self.lexer.file_texts.clone(), } } diff --git a/cranelift/isle/isle/src/sema.rs b/cranelift/isle/isle/src/sema.rs index 5051009a1f..4990ae99cf 100644 --- a/cranelift/isle/isle/src/sema.rs +++ b/cranelift/isle/isle/src/sema.rs @@ -56,7 +56,7 @@ declare_id!( /// The type environment. /// /// Keeps track of which symbols and rules have which types. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct TypeEnv { /// Arena of input ISLE source filenames. /// @@ -887,7 +887,7 @@ macro_rules! unwrap_or_continue { impl TypeEnv { /// Construct the type environment from the AST. - pub fn from_ast(defs: &ast::Defs) -> Result { + pub fn from_ast(defs: &ast::Defs) -> Result { let mut tyenv = TypeEnv { filenames: defs.filenames.clone(), file_texts: defs.file_texts.clone(), @@ -968,11 +968,15 @@ impl TypeEnv { Ok(tyenv) } - fn return_errors(&mut self) -> Result<()> { - match self.errors.len() { - 0 => Ok(()), - 1 => Err(self.errors.pop().unwrap()), - _ => Err(Error::Errors(std::mem::take(&mut self.errors))), + fn return_errors(&mut self) -> Result<(), Errors> { + if self.errors.is_empty() { + Ok(()) + } else { + Err(Errors { + errors: std::mem::take(&mut self.errors), + filenames: self.filenames.clone(), + file_texts: self.file_texts.clone(), + }) } } @@ -1062,16 +1066,10 @@ impl TypeEnv { } fn error(&self, pos: Pos, msg: impl Into) -> Error { - let e = Error::TypeError { + Error::TypeError { msg: msg.into(), - src: Source::new( - self.filenames[pos.file].clone(), - self.file_texts[pos.file].clone(), - ), span: Span::new_single(pos), - }; - log!("{}", e); - e + } } fn report_error(&mut self, pos: Pos, msg: impl Into) { @@ -1121,7 +1119,7 @@ impl Bindings { impl TermEnv { /// Construct the term environment from the AST and the type environment. - pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result { + pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result { let mut env = TermEnv { terms: vec![], term_map: StableMap::new(), diff --git a/cranelift/isle/isle/src/trie_again.rs b/cranelift/isle/isle/src/trie_again.rs index 7e00980e08..b0af04e926 100644 --- a/cranelift/isle/isle/src/trie_again.rs +++ b/cranelift/isle/isle/src/trie_again.rs @@ -1,6 +1,6 @@ //! A strongly-normalizing intermediate representation for ISLE rules. This representation is chosen //! to closely reflect the operations we can implement in Rust, to make code generation easy. -use crate::error::{Error, Source, Span}; +use crate::error::{Error, Span}; use crate::lexer::Pos; use crate::sema; use crate::DisjointSets; @@ -167,16 +167,13 @@ pub struct RuleSet { } /// Construct a [RuleSet] for each term in `termenv` that has rules. -pub fn build( - termenv: &sema::TermEnv, - tyenv: &sema::TypeEnv, -) -> (Vec<(sema::TermId, RuleSet)>, Vec) { +pub fn build(termenv: &sema::TermEnv) -> (Vec<(sema::TermId, RuleSet)>, Vec) { let mut errors = Vec::new(); let mut term = HashMap::new(); for rule in termenv.rules.iter() { term.entry(rule.root_term) .or_insert_with(RuleSetBuilder::default) - .add_rule(rule, termenv, tyenv, &mut errors); + .add_rule(rule, termenv, &mut errors); } // The `term` hash map may return terms in any order. Sort them to ensure that we produce the @@ -282,13 +279,7 @@ struct RuleSetBuilder { } impl RuleSetBuilder { - fn add_rule( - &mut self, - rule: &sema::Rule, - termenv: &sema::TermEnv, - tyenv: &sema::TypeEnv, - errors: &mut Vec, - ) { + fn add_rule(&mut self, rule: &sema::Rule, termenv: &sema::TermEnv, errors: &mut Vec) { self.current_rule.pos = rule.pos; self.current_rule.prio = rule.prio; self.current_rule.result = rule.visit(self, termenv); @@ -299,20 +290,17 @@ impl RuleSetBuilder { self.rules.rules.push(rule); } else { // If this rule can never match, drop it so it doesn't affect overlap checking. - errors.extend(self.unreachable.drain(..).map(|err| { - let src = Source::new( - tyenv.filenames[err.pos.file].clone(), - tyenv.file_texts[err.pos.file].clone(), - ); - Error::UnreachableError { - msg: format!( - "rule requires binding to match both {:?} and {:?}", - err.constraint_a, err.constraint_b - ), - src, - span: Span::new_single(err.pos), - } - })) + errors.extend( + self.unreachable + .drain(..) + .map(|err| Error::UnreachableError { + msg: format!( + "rule requires binding to match both {:?} and {:?}", + err.constraint_a, err.constraint_b + ), + span: Span::new_single(err.pos), + }), + ) } } diff --git a/cranelift/isle/isle/tests/run_tests.rs b/cranelift/isle/isle/tests/run_tests.rs index b22990c5d2..9adf51c32d 100644 --- a/cranelift/isle/isle/tests/run_tests.rs +++ b/cranelift/isle/isle/tests/run_tests.rs @@ -1,18 +1,16 @@ //! Helper for autogenerated unit tests. -use cranelift_isle::error::Result; -use cranelift_isle::{compile, lexer, parser}; +use cranelift_isle::compile; +use cranelift_isle::error::Errors; use std::default::Default; -fn build(filename: &str) -> Result { - let lexer = lexer::Lexer::from_files(vec![filename])?; - let defs = parser::parse(lexer)?; - compile::compile(&defs, &Default::default()) +fn build(filename: &str) -> Result { + compile::from_files(&[filename], &Default::default()) } pub fn run_pass(filename: &str) { if let Err(err) = build(filename) { - panic!("pass test failed:\n{}", err); + panic!("pass test failed:\n{:?}", err); } } diff --git a/cranelift/isle/islec/Cargo.toml b/cranelift/isle/islec/Cargo.toml index 4204550fc9..46e43b9303 100644 --- a/cranelift/isle/islec/Cargo.toml +++ b/cranelift/isle/islec/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0 WITH LLVM-exception" publish = false [dependencies] -cranelift-isle = { version = "*", path = "../isle/", features = ["miette-errors", "logging"] } +cranelift-isle = { version = "*", path = "../isle/", features = ["fancy-errors", "logging"] } env_logger = { workspace = true } -miette = { version = "5.1.0", features = ["fancy"] } clap = { workspace = true } diff --git a/cranelift/isle/islec/src/main.rs b/cranelift/isle/islec/src/main.rs index 20c235d929..f934e71ae6 100644 --- a/cranelift/isle/islec/src/main.rs +++ b/cranelift/isle/islec/src/main.rs @@ -1,6 +1,6 @@ use clap::Parser; -use cranelift_isle::{compile, lexer, parser}; -use miette::{Context, IntoDiagnostic, Result}; +use cranelift_isle::compile; +use cranelift_isle::error::Errors; use std::{ default::Default, fs, @@ -20,33 +20,19 @@ struct Opts { inputs: Vec, } -fn main() -> Result<()> { +fn main() -> Result<(), Errors> { let _ = env_logger::try_init(); - let _ = miette::set_hook(Box::new(|_| { - Box::new( - miette::MietteHandlerOpts::new() - // `miette` mistakenly uses braille-optimized output for emacs's - // `M-x shell`. - .force_graphical(true) - .build(), - ) - })); - let opts = Opts::parse(); - - let lexer = lexer::Lexer::from_files(opts.inputs)?; - let defs = parser::parse(lexer)?; - let code = compile::compile(&defs, &Default::default())?; + let code = compile::from_files(opts.inputs, &Default::default())?; let stdout = io::stdout(); let (mut output, output_name): (Box, _) = match &opts.output { Some(f) => { - let output = Box::new( - fs::File::create(f) - .into_diagnostic() - .with_context(|| format!("failed to create '{}'", f.display()))?, - ); + let output = + Box::new(fs::File::create(f).map_err(|e| { + Errors::from_io(e, format!("failed to create '{}'", f.display())) + })?); (output, f.display().to_string()) } None => { @@ -57,8 +43,7 @@ fn main() -> Result<()> { output .write_all(code.as_bytes()) - .into_diagnostic() - .with_context(|| format!("failed to write to '{}'", output_name))?; + .map_err(|e| Errors::from_io(e, format!("failed to write to '{}'", output_name)))?; Ok(()) } diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index fc5c0f7caa..2dd941ad38 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -125,6 +125,12 @@ criteria = "safe-to-deploy" version = "1.0.0" notes = "I am the author of this crate." +[[audits.codespan-reporting]] +who = "Jamey Sharp " +criteria = "safe-to-deploy" +version = "0.11.1" +notes = "This library uses `forbid(unsafe_code)` and has no filesystem or network I/O." + [[audits.criterion]] who = "Alex Crichton " criteria = "safe-to-run" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 4411710d32..6da7ec522f 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -28,7 +28,7 @@ criteria = "safe-to-deploy" [[exemptions.adler]] version = "1.0.2" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.aead]] version = "0.4.3" @@ -394,10 +394,6 @@ criteria = "safe-to-deploy" version = "2.5.0" criteria = "safe-to-deploy" -[[exemptions.is_ci]] -version = "1.1.1" -criteria = "safe-to-deploy" - [[exemptions.itertools]] version = "0.10.3" criteria = "safe-to-deploy" @@ -482,17 +478,9 @@ criteria = "safe-to-deploy" version = "0.6.5" criteria = "safe-to-deploy" -[[exemptions.miette]] -version = "5.1.0" -criteria = "safe-to-deploy" - -[[exemptions.miette-derive]] -version = "5.1.0" -criteria = "safe-to-deploy" - [[exemptions.miniz_oxide]] version = "0.5.1" -criteria = "safe-to-deploy" +criteria = "safe-to-run" [[exemptions.mio]] version = "0.8.2" @@ -562,10 +550,6 @@ criteria = "safe-to-run" version = "6.0.0" criteria = "safe-to-deploy" -[[exemptions.owo-colors]] -version = "3.4.0" -criteria = "safe-to-deploy" - [[exemptions.p256]] version = "0.9.0" criteria = "safe-to-deploy" @@ -818,10 +802,6 @@ criteria = "safe-to-deploy" version = "1.8.0" criteria = "safe-to-deploy" -[[exemptions.smawk]] -version = "0.3.1" -criteria = "safe-to-deploy" - [[exemptions.socket2]] version = "0.4.4" criteria = "safe-to-deploy" @@ -854,18 +834,6 @@ criteria = "safe-to-deploy" version = "2.4.1" criteria = "safe-to-deploy" -[[exemptions.supports-color]] -version = "1.3.0" -criteria = "safe-to-deploy" - -[[exemptions.supports-hyperlinks]] -version = "1.2.0" -criteria = "safe-to-deploy" - -[[exemptions.supports-unicode]] -version = "1.0.2" -criteria = "safe-to-deploy" - [[exemptions.symbolic_expressions]] version = "5.0.3" criteria = "safe-to-run" @@ -946,10 +914,6 @@ criteria = "safe-to-run" version = "1.15.0" criteria = "safe-to-deploy" -[[exemptions.unicode-linebreak]] -version = "0.1.2" -criteria = "safe-to-deploy" - [[exemptions.unicode-width]] version = "0.1.9" criteria = "safe-to-deploy"