cranelift-isle: Rewrite error reporting (#5318)
There were several issues with ISLE's existing error reporting implementation. - When using Miette for more readable error reports, it would panic if errors were reported from multiple files in the same run. - Miette is pretty heavy-weight for what we're doing, with a lot of dependencies. - The `Error::Errors` enum variant led to normalization steps in many places, to avoid using that variant to represent a single error. This commit: - replaces Miette with codespan-reporting - gets rid of a bunch of cargo-vet exemptions - replaces the `Error::Errors` variant with a new `Errors` type - removes source info from `Error` variants so they're easy to construct - adds source info only when formatting `Errors` - formats `Errors` with a custom `Debug` impl - shares common code between ISLE's callers, islec and cranelift-codegen - includes a source snippet even with fancy-errors disabled I tried to make this a series of smaller commits but I couldn't find any good split points; everything was too entangled with everything else.
This commit is contained in:
105
Cargo.lock
generated
105
Cargo.lock
generated
@@ -443,6 +443,16 @@ dependencies = [
|
|||||||
"os_str_bytes",
|
"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]]
|
[[package]]
|
||||||
name = "component-fuzz-util"
|
name = "component-fuzz-util"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
@@ -544,7 +554,6 @@ dependencies = [
|
|||||||
"gimli",
|
"gimli",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"log",
|
"log",
|
||||||
"miette",
|
|
||||||
"regalloc2",
|
"regalloc2",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2 0.10.2",
|
"sha2 0.10.2",
|
||||||
@@ -645,8 +654,8 @@ dependencies = [
|
|||||||
name = "cranelift-isle"
|
name = "cranelift-isle"
|
||||||
version = "0.91.0"
|
version = "0.91.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"codespan-reporting",
|
||||||
"log",
|
"log",
|
||||||
"miette",
|
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1534,12 +1543,6 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_ci"
|
|
||||||
version = "1.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "isle-fuzz"
|
name = "isle-fuzz"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
@@ -1557,7 +1560,6 @@ dependencies = [
|
|||||||
"clap 3.2.8",
|
"clap 3.2.8",
|
||||||
"cranelift-isle",
|
"cranelift-isle",
|
||||||
"env_logger 0.9.0",
|
"env_logger 0.9.0",
|
||||||
"miette",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1763,37 +1765,6 @@ dependencies = [
|
|||||||
"autocfg 1.1.0",
|
"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]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -2008,12 +1979,6 @@ version = "6.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "owo-colors"
|
|
||||||
version = "3.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "p256"
|
name = "p256"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -2681,12 +2646,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smawk"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@@ -2751,34 +2710,6 @@ version = "2.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
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]]
|
[[package]]
|
||||||
name = "symbolic_expressions"
|
name = "symbolic_expressions"
|
||||||
version = "5.0.3"
|
version = "5.0.3"
|
||||||
@@ -2896,11 +2827,6 @@ name = "textwrap"
|
|||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
dependencies = [
|
|
||||||
"smawk",
|
|
||||||
"unicode-linebreak",
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
@@ -3051,15 +2977,6 @@ version = "0.3.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
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]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
version = "0.1.21"
|
version = "0.1.21"
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ criterion = "0.3"
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cranelift-codegen-meta = { path = "meta", version = "0.91.0" }
|
cranelift-codegen-meta = { path = "meta", version = "0.91.0" }
|
||||||
cranelift-isle = { path = "../isle/isle", version = "=0.91.0" }
|
cranelift-isle = { path = "../isle/isle", version = "=0.91.0" }
|
||||||
miette = { version = "5.1.0", features = ["fancy"], optional = true }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std", "unwind"]
|
default = ["std", "unwind"]
|
||||||
@@ -101,7 +100,7 @@ incremental-cache = [
|
|||||||
souper-harvest = ["souper-ir", "souper-ir/stringify"]
|
souper-harvest = ["souper-ir", "souper-ir/stringify"]
|
||||||
|
|
||||||
# Provide fancy Miette-produced errors for ISLE.
|
# 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
|
# Put ISLE generated files in isle_generated_code/, for easier
|
||||||
# inspection, rather than inside of target/.
|
# inspection, rather than inside of target/.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
// current directory is used to find the sources.
|
// current directory is used to find the sources.
|
||||||
|
|
||||||
use cranelift_codegen_meta as meta;
|
use cranelift_codegen_meta as meta;
|
||||||
|
use cranelift_isle::error::Errors;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
@@ -288,13 +289,16 @@ fn build_isle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = run_compilation(compilation) {
|
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;
|
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
|
/// NB: This must happen *after* the `cranelift-codegen-meta` functions, since
|
||||||
/// it consumes files generated by them.
|
/// it consumes files generated by them.
|
||||||
fn run_compilation(
|
fn run_compilation(compilation: &IsleCompilation) -> Result<(), Errors> {
|
||||||
compilation: &IsleCompilation,
|
|
||||||
) -> Result<(), Box<dyn std::error::Error + 'static>> {
|
|
||||||
use cranelift_isle as isle;
|
use cranelift_isle as isle;
|
||||||
|
|
||||||
eprintln!("Rebuilding {}", compilation.output.display());
|
eprintln!("Rebuilding {}", compilation.output.display());
|
||||||
|
|
||||||
let code = (|| {
|
let code = {
|
||||||
let lexer = isle::lexer::Lexer::from_files(
|
let file_paths = compilation
|
||||||
compilation
|
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.chain(compilation.untracked_inputs.iter()),
|
.chain(compilation.untracked_inputs.iter());
|
||||||
)?;
|
|
||||||
let defs = isle::parser::parse(lexer)?;
|
|
||||||
|
|
||||||
let mut options = isle::codegen::CodegenOptions::default();
|
let mut options = isle::codegen::CodegenOptions::default();
|
||||||
// Because we include!() the generated ISLE source, we cannot
|
// Because we include!() the generated ISLE source, we cannot
|
||||||
@@ -335,62 +334,8 @@ fn run_compilation(
|
|||||||
// https://github.com/rust-lang/rust/issues/47995.)
|
// https://github.com/rust-lang/rust/issues/47995.)
|
||||||
options.exclude_global_allow_pragmas = true;
|
options.exclude_global_allow_pragmas = true;
|
||||||
|
|
||||||
isle::compile::compile(&defs, &options)
|
isle::compile::from_files(file_paths, &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 {}
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let code = rustfmt(&code).unwrap_or_else(|e| {
|
let code = rustfmt(&code).unwrap_or_else(|e| {
|
||||||
println!(
|
println!(
|
||||||
@@ -404,7 +349,8 @@ fn run_compilation(
|
|||||||
"Writing ISLE-generated Rust code to {}",
|
"Writing ISLE-generated Rust code to {}",
|
||||||
compilation.output.display()
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ repository = "https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/i
|
|||||||
version = "0.91.0"
|
version = "0.91.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
codespan-reporting = { version = "0.11.1", optional = true }
|
||||||
log = { workspace = true, optional = true }
|
log = { workspace = true, optional = true }
|
||||||
miette = { version = "5.1.0", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
@@ -19,4 +19,4 @@ tempfile = "3"
|
|||||||
default = []
|
default = []
|
||||||
|
|
||||||
logging = ["log"]
|
logging = ["log"]
|
||||||
miette-errors = ["miette"]
|
fancy-errors = ["codespan-reporting"]
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
//! Compilation process, from AST to Sema to Sequences of Insts.
|
//! 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};
|
use crate::{ast, codegen, sema, trie};
|
||||||
|
|
||||||
/// Compile the given AST definitions into Rust source code.
|
/// Compile the given AST definitions into Rust source code.
|
||||||
pub fn compile(defs: &ast::Defs, options: &codegen::CodegenOptions) -> Result<String> {
|
pub fn compile(defs: &ast::Defs, options: &codegen::CodegenOptions) -> Result<String, Errors> {
|
||||||
let mut typeenv = sema::TypeEnv::from_ast(defs)?;
|
let mut typeenv = sema::TypeEnv::from_ast(defs)?;
|
||||||
let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?;
|
let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?;
|
||||||
crate::overlap::check(&mut typeenv, &termenv)?;
|
crate::overlap::check(&mut typeenv, &termenv)?;
|
||||||
let tries = trie::build_tries(&termenv);
|
let tries = trie::build_tries(&termenv);
|
||||||
Ok(codegen::codegen(&typeenv, &termenv, &tries, options))
|
Ok(codegen::codegen(&typeenv, &termenv, &tries, options))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compile the given files into Rust source code.
|
||||||
|
pub fn from_files<P: AsRef<Path>>(
|
||||||
|
inputs: impl IntoIterator<Item = P>,
|
||||||
|
options: &codegen::CodegenOptions,
|
||||||
|
) -> Result<String, Errors> {
|
||||||
|
let lexer = crate::lexer::Lexer::from_files(inputs)?;
|
||||||
|
let defs = crate::parser::parse(lexer)?;
|
||||||
|
compile(&defs, options)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,16 +4,75 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::lexer::Pos;
|
use crate::lexer::Pos;
|
||||||
|
|
||||||
/// Either `Ok(T)` or `Err(isle::Error)`.
|
/// A collection of errors from attempting to compile some ISLE source files.
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub struct Errors {
|
||||||
|
/// The individual errors.
|
||||||
|
pub errors: Vec<Error>,
|
||||||
|
pub(crate) filenames: Vec<Arc<str>>,
|
||||||
|
pub(crate) file_texts: Vec<Arc<str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
/// Errors produced by ISLE.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// An I/O error.
|
/// An I/O error.
|
||||||
IoError {
|
IoError {
|
||||||
/// The underlying I/O error.
|
/// The underlying I/O error.
|
||||||
error: Arc<std::io::Error>,
|
error: std::io::Error,
|
||||||
/// The context explaining what caused the I/O error.
|
/// The context explaining what caused the I/O error.
|
||||||
context: String,
|
context: String,
|
||||||
},
|
},
|
||||||
@@ -23,9 +82,6 @@ pub enum Error {
|
|||||||
/// The error message.
|
/// The error message.
|
||||||
msg: String,
|
msg: String,
|
||||||
|
|
||||||
/// The input ISLE source.
|
|
||||||
src: Source,
|
|
||||||
|
|
||||||
/// The location of the parse error.
|
/// The location of the parse error.
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
@@ -35,9 +91,6 @@ pub enum Error {
|
|||||||
/// The error message.
|
/// The error message.
|
||||||
msg: String,
|
msg: String,
|
||||||
|
|
||||||
/// The input ISLE source.
|
|
||||||
src: Source,
|
|
||||||
|
|
||||||
/// The location of the type error.
|
/// The location of the type error.
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
@@ -47,9 +100,6 @@ pub enum Error {
|
|||||||
/// The error message.
|
/// The error message.
|
||||||
msg: String,
|
msg: String,
|
||||||
|
|
||||||
/// The input ISLE source.
|
|
||||||
src: Source,
|
|
||||||
|
|
||||||
/// The location of the unreachable rule.
|
/// The location of the unreachable rule.
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
@@ -62,153 +112,98 @@ pub enum Error {
|
|||||||
/// The locations of all the rules that overlap. When there are more than two rules
|
/// 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
|
/// present, the first rule is the one with the most overlaps (likely a fall-through
|
||||||
/// wildcard case).
|
/// wildcard case).
|
||||||
rules: Vec<(Source, Span)>,
|
rules: Vec<Span>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Multiple errors.
|
|
||||||
Errors(Vec<Error>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Errors {
|
||||||
/// Create a `isle::Error` from the given I/O error and context.
|
/// Create `isle::Errors` from the given I/O error and context.
|
||||||
pub fn from_io(error: std::io::Error, context: impl Into<String>) -> Self {
|
pub fn from_io(error: std::io::Error, context: impl Into<String>) -> Self {
|
||||||
Error::IoError {
|
Errors {
|
||||||
error: Arc::new(error),
|
errors: vec![Error::IoError {
|
||||||
|
error,
|
||||||
context: context.into(),
|
context: context.into(),
|
||||||
|
}],
|
||||||
|
filenames: Vec::new(),
|
||||||
|
file_texts: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<Error>> for Error {
|
#[cfg(feature = "fancy-errors")]
|
||||||
fn from(es: Vec<Error>) -> Self {
|
fn emit(
|
||||||
Error::Errors(es)
|
&self,
|
||||||
|
f: &mut std::fmt::Formatter,
|
||||||
|
diagnostics: Vec<Diagnostic<usize>>,
|
||||||
|
) -> 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<Diagnostic<usize>>,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let line_starts: Vec<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 {
|
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)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
Error::IoError { error, .. } => Some(&*error as &dyn std::error::Error),
|
Error::IoError { error, .. } => Some(error),
|
||||||
_ => None,
|
_ => 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<str>,
|
|
||||||
/// The text of this source file.
|
|
||||||
#[allow(unused)] // Used only when miette is enabled.
|
|
||||||
pub text: Arc<str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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", &"<redacted>");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Source {
|
|
||||||
pub(crate) fn new(name: Arc<str>, text: Arc<str>) -> Self {
|
|
||||||
Self { name, text }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get this source's file name.
|
|
||||||
pub fn name(&self) -> &Arc<str> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get this source's text contents.
|
|
||||||
pub fn text(&self) -> &Arc<str> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A span in a given source.
|
/// A span in a given source.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Span {
|
pub struct Span {
|
||||||
@@ -237,3 +232,69 @@ impl Span {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&Span> for std::ops::Range<usize> {
|
||||||
|
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<FileId> {
|
||||||
|
pub message: String,
|
||||||
|
pub labels: Vec<Label<FileId>>,
|
||||||
|
pub notes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<FileId> Diagnostic<FileId> {
|
||||||
|
pub fn error() -> Self {
|
||||||
|
Self {
|
||||||
|
message: String::new(),
|
||||||
|
labels: Vec::new(),
|
||||||
|
notes: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_message(mut self, message: impl Into<String>) -> Self {
|
||||||
|
self.message = message.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_labels(mut self, labels: Vec<Label<FileId>>) -> Self {
|
||||||
|
self.labels = labels;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_notes(mut self, notes: Vec<String>) -> Self {
|
||||||
|
self.notes = notes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Label<FileId> {
|
||||||
|
pub file_id: FileId,
|
||||||
|
pub range: Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<FileId> Label<FileId> {
|
||||||
|
pub fn primary(file_id: FileId, range: impl Into<Range<usize>>) -> Self {
|
||||||
|
Self {
|
||||||
|
file_id,
|
||||||
|
range: range.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn secondary(file_id: FileId, range: impl Into<Range<usize>>) -> Self {
|
||||||
|
Self::primary(file_id, range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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<Span> 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<Box<dyn miette::SpanContents<'a> + '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<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
|
|
||||||
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<Box<dyn Iterator<Item = &dyn miette::Diagnostic> + '_>> {
|
|
||||||
match self {
|
|
||||||
Self::Errors(errors) => Some(Box::new(
|
|
||||||
errors.iter().map(|x| x as &dyn miette::Diagnostic),
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
//! Lexer for the ISLE language.
|
//! Lexer for the ISLE language.
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source, Span};
|
use crate::error::{Error, Errors, Span};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Errors>;
|
||||||
|
|
||||||
/// The lexer.
|
/// The lexer.
|
||||||
///
|
///
|
||||||
/// Breaks source text up into a sequence of tokens (with source positions).
|
/// Breaks source text up into a sequence of tokens (with source positions).
|
||||||
@@ -43,19 +45,10 @@ pub struct Pos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Pos {
|
impl Pos {
|
||||||
/// Print this source position as `file.isle:12:34`.
|
|
||||||
pub fn pretty_print(&self, filenames: &[Arc<str>]) -> String {
|
|
||||||
self.pretty_print_with_filename(&filenames[self.file])
|
|
||||||
}
|
|
||||||
/// Print this source position as `file.isle line 12`.
|
/// Print this source position as `file.isle line 12`.
|
||||||
pub fn pretty_print_line(&self, filenames: &[Arc<str>]) -> String {
|
pub fn pretty_print_line(&self, filenames: &[Arc<str>]) -> String {
|
||||||
format!("{} line {}", filenames[self.file], self.line)
|
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.
|
/// A token of ISLE source.
|
||||||
@@ -107,7 +100,7 @@ impl<'a> Lexer<'a> {
|
|||||||
filenames.push(f.display().to_string().into());
|
filenames.push(f.display().to_string().into());
|
||||||
|
|
||||||
let s = std::fs::read_to_string(f)
|
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());
|
file_texts.push(s.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,14 +158,14 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error(&self, pos: Pos, msg: impl Into<String>) -> Error {
|
fn error(&self, pos: Pos, msg: impl Into<String>) -> Errors {
|
||||||
Error::ParseError {
|
Errors {
|
||||||
|
errors: vec![Error::ParseError {
|
||||||
msg: msg.into(),
|
msg: msg.into(),
|
||||||
src: Source::new(
|
span: Span::new_single(pos),
|
||||||
self.filenames[pos.file].clone(),
|
}],
|
||||||
self.file_texts[pos.file].clone(),
|
filenames: self.filenames.clone(),
|
||||||
),
|
file_texts: self.file_texts.clone(),
|
||||||
span: Span::new_single(self.pos()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,3 @@ pub mod parser;
|
|||||||
pub mod sema;
|
pub mod sema;
|
||||||
pub mod trie;
|
pub mod trie;
|
||||||
pub mod trie_again;
|
pub mod trie_again;
|
||||||
|
|
||||||
#[cfg(feature = "miette-errors")]
|
|
||||||
mod error_miette;
|
|
||||||
|
|||||||
@@ -3,20 +3,24 @@
|
|||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source, Span};
|
use crate::error::{self, Error, Span};
|
||||||
use crate::lexer::Pos;
|
use crate::lexer::Pos;
|
||||||
use crate::sema::{TermEnv, TermId, TermKind, TypeEnv};
|
use crate::sema::{TermEnv, TermId, TermKind, TypeEnv};
|
||||||
use crate::trie_again;
|
use crate::trie_again;
|
||||||
|
|
||||||
/// Check for overlap.
|
/// Check for overlap.
|
||||||
pub fn check(tyenv: &TypeEnv, termenv: &TermEnv) -> Result<()> {
|
pub fn check(tyenv: &TypeEnv, termenv: &TermEnv) -> Result<(), error::Errors> {
|
||||||
let (terms, mut errors) = trie_again::build(termenv, tyenv);
|
let (terms, mut errors) = trie_again::build(termenv);
|
||||||
errors.append(&mut check_overlaps(terms, termenv).report(tyenv));
|
errors.append(&mut check_overlaps(terms, termenv).report());
|
||||||
|
|
||||||
match errors.len() {
|
if errors.is_empty() {
|
||||||
0 => Ok(()),
|
Ok(())
|
||||||
1 => Err(errors.pop().unwrap()),
|
} else {
|
||||||
_ => Err(Error::Errors(errors)),
|
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
|
/// 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
|
/// 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.
|
/// overlap with many others first, and then report other more targeted overlaps later.
|
||||||
fn report(mut self, tyenv: &TypeEnv) -> Vec<Error> {
|
fn report(mut self) -> Vec<Error> {
|
||||||
let mut errors = Vec::new();
|
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
|
while let Some((&pos, _)) = self
|
||||||
.nodes
|
.nodes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -62,9 +56,9 @@ impl Errors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build the real error
|
// 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 {
|
errors.push(Error::OverlapError {
|
||||||
msg: String::from("rules are overlapping"),
|
msg: String::from("rules are overlapping"),
|
||||||
@@ -73,7 +67,7 @@ impl Errors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errors.sort_by_key(|err| match err {
|
errors.sort_by_key(|err| match err {
|
||||||
Error::OverlapError { rules, .. } => rules.first().unwrap().1.from,
|
Error::OverlapError { rules, .. } => rules.first().unwrap().from,
|
||||||
_ => Pos::default(),
|
_ => Pos::default(),
|
||||||
});
|
});
|
||||||
errors
|
errors
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
//! Parser for ISLE language.
|
//! Parser for ISLE language.
|
||||||
|
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use crate::error::*;
|
use crate::error::{Error, Errors, Span};
|
||||||
use crate::lexer::{Lexer, Pos, Token};
|
use crate::lexer::{Lexer, Pos, Token};
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Errors>;
|
||||||
|
|
||||||
/// Parse the top-level ISLE definitions and return their AST.
|
/// Parse the top-level ISLE definitions and return their AST.
|
||||||
pub fn parse(lexer: Lexer) -> Result<Defs> {
|
pub fn parse(lexer: Lexer) -> Result<Defs> {
|
||||||
let parser = Parser::new(lexer);
|
let parser = Parser::new(lexer);
|
||||||
@@ -32,14 +34,14 @@ impl<'a> Parser<'a> {
|
|||||||
Parser { lexer }
|
Parser { lexer }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error(&self, pos: Pos, msg: String) -> Error {
|
fn error(&self, pos: Pos, msg: String) -> Errors {
|
||||||
Error::ParseError {
|
Errors {
|
||||||
|
errors: vec![Error::ParseError {
|
||||||
msg,
|
msg,
|
||||||
src: Source::new(
|
|
||||||
self.lexer.filenames[pos.file].clone(),
|
|
||||||
self.lexer.file_texts[pos.file].clone(),
|
|
||||||
),
|
|
||||||
span: Span::new_single(pos),
|
span: Span::new_single(pos),
|
||||||
|
}],
|
||||||
|
filenames: self.lexer.filenames.clone(),
|
||||||
|
file_texts: self.lexer.file_texts.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ declare_id!(
|
|||||||
/// The type environment.
|
/// The type environment.
|
||||||
///
|
///
|
||||||
/// Keeps track of which symbols and rules have which types.
|
/// Keeps track of which symbols and rules have which types.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TypeEnv {
|
pub struct TypeEnv {
|
||||||
/// Arena of input ISLE source filenames.
|
/// Arena of input ISLE source filenames.
|
||||||
///
|
///
|
||||||
@@ -887,7 +887,7 @@ macro_rules! unwrap_or_continue {
|
|||||||
|
|
||||||
impl TypeEnv {
|
impl TypeEnv {
|
||||||
/// Construct the type environment from the AST.
|
/// Construct the type environment from the AST.
|
||||||
pub fn from_ast(defs: &ast::Defs) -> Result<TypeEnv> {
|
pub fn from_ast(defs: &ast::Defs) -> Result<TypeEnv, Errors> {
|
||||||
let mut tyenv = TypeEnv {
|
let mut tyenv = TypeEnv {
|
||||||
filenames: defs.filenames.clone(),
|
filenames: defs.filenames.clone(),
|
||||||
file_texts: defs.file_texts.clone(),
|
file_texts: defs.file_texts.clone(),
|
||||||
@@ -968,11 +968,15 @@ impl TypeEnv {
|
|||||||
Ok(tyenv)
|
Ok(tyenv)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn return_errors(&mut self) -> Result<()> {
|
fn return_errors(&mut self) -> Result<(), Errors> {
|
||||||
match self.errors.len() {
|
if self.errors.is_empty() {
|
||||||
0 => Ok(()),
|
Ok(())
|
||||||
1 => Err(self.errors.pop().unwrap()),
|
} else {
|
||||||
_ => Err(Error::Errors(std::mem::take(&mut self.errors))),
|
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<String>) -> Error {
|
fn error(&self, pos: Pos, msg: impl Into<String>) -> Error {
|
||||||
let e = Error::TypeError {
|
Error::TypeError {
|
||||||
msg: msg.into(),
|
msg: msg.into(),
|
||||||
src: Source::new(
|
|
||||||
self.filenames[pos.file].clone(),
|
|
||||||
self.file_texts[pos.file].clone(),
|
|
||||||
),
|
|
||||||
span: Span::new_single(pos),
|
span: Span::new_single(pos),
|
||||||
};
|
}
|
||||||
log!("{}", e);
|
|
||||||
e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_error(&mut self, pos: Pos, msg: impl Into<String>) {
|
fn report_error(&mut self, pos: Pos, msg: impl Into<String>) {
|
||||||
@@ -1121,7 +1119,7 @@ impl Bindings {
|
|||||||
|
|
||||||
impl TermEnv {
|
impl TermEnv {
|
||||||
/// Construct the term environment from the AST and the type environment.
|
/// Construct the term environment from the AST and the type environment.
|
||||||
pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result<TermEnv> {
|
pub fn from_ast(tyenv: &mut TypeEnv, defs: &ast::Defs) -> Result<TermEnv, Errors> {
|
||||||
let mut env = TermEnv {
|
let mut env = TermEnv {
|
||||||
terms: vec![],
|
terms: vec![],
|
||||||
term_map: StableMap::new(),
|
term_map: StableMap::new(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! A strongly-normalizing intermediate representation for ISLE rules. This representation is chosen
|
//! 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.
|
//! 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::lexer::Pos;
|
||||||
use crate::sema;
|
use crate::sema;
|
||||||
use crate::DisjointSets;
|
use crate::DisjointSets;
|
||||||
@@ -167,16 +167,13 @@ pub struct RuleSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a [RuleSet] for each term in `termenv` that has rules.
|
/// Construct a [RuleSet] for each term in `termenv` that has rules.
|
||||||
pub fn build(
|
pub fn build(termenv: &sema::TermEnv) -> (Vec<(sema::TermId, RuleSet)>, Vec<Error>) {
|
||||||
termenv: &sema::TermEnv,
|
|
||||||
tyenv: &sema::TypeEnv,
|
|
||||||
) -> (Vec<(sema::TermId, RuleSet)>, Vec<Error>) {
|
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let mut term = HashMap::new();
|
let mut term = HashMap::new();
|
||||||
for rule in termenv.rules.iter() {
|
for rule in termenv.rules.iter() {
|
||||||
term.entry(rule.root_term)
|
term.entry(rule.root_term)
|
||||||
.or_insert_with(RuleSetBuilder::default)
|
.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
|
// 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 {
|
impl RuleSetBuilder {
|
||||||
fn add_rule(
|
fn add_rule(&mut self, rule: &sema::Rule, termenv: &sema::TermEnv, errors: &mut Vec<Error>) {
|
||||||
&mut self,
|
|
||||||
rule: &sema::Rule,
|
|
||||||
termenv: &sema::TermEnv,
|
|
||||||
tyenv: &sema::TypeEnv,
|
|
||||||
errors: &mut Vec<Error>,
|
|
||||||
) {
|
|
||||||
self.current_rule.pos = rule.pos;
|
self.current_rule.pos = rule.pos;
|
||||||
self.current_rule.prio = rule.prio;
|
self.current_rule.prio = rule.prio;
|
||||||
self.current_rule.result = rule.visit(self, termenv);
|
self.current_rule.result = rule.visit(self, termenv);
|
||||||
@@ -299,20 +290,17 @@ impl RuleSetBuilder {
|
|||||||
self.rules.rules.push(rule);
|
self.rules.rules.push(rule);
|
||||||
} else {
|
} else {
|
||||||
// If this rule can never match, drop it so it doesn't affect overlap checking.
|
// If this rule can never match, drop it so it doesn't affect overlap checking.
|
||||||
errors.extend(self.unreachable.drain(..).map(|err| {
|
errors.extend(
|
||||||
let src = Source::new(
|
self.unreachable
|
||||||
tyenv.filenames[err.pos.file].clone(),
|
.drain(..)
|
||||||
tyenv.file_texts[err.pos.file].clone(),
|
.map(|err| Error::UnreachableError {
|
||||||
);
|
|
||||||
Error::UnreachableError {
|
|
||||||
msg: format!(
|
msg: format!(
|
||||||
"rule requires binding to match both {:?} and {:?}",
|
"rule requires binding to match both {:?} and {:?}",
|
||||||
err.constraint_a, err.constraint_b
|
err.constraint_a, err.constraint_b
|
||||||
),
|
),
|
||||||
src,
|
|
||||||
span: Span::new_single(err.pos),
|
span: Span::new_single(err.pos),
|
||||||
}
|
}),
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
//! Helper for autogenerated unit tests.
|
//! Helper for autogenerated unit tests.
|
||||||
|
|
||||||
use cranelift_isle::error::Result;
|
use cranelift_isle::compile;
|
||||||
use cranelift_isle::{compile, lexer, parser};
|
use cranelift_isle::error::Errors;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
|
||||||
fn build(filename: &str) -> Result<String> {
|
fn build(filename: &str) -> Result<String, Errors> {
|
||||||
let lexer = lexer::Lexer::from_files(vec![filename])?;
|
compile::from_files(&[filename], &Default::default())
|
||||||
let defs = parser::parse(lexer)?;
|
|
||||||
compile::compile(&defs, &Default::default())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_pass(filename: &str) {
|
pub fn run_pass(filename: &str) {
|
||||||
if let Err(err) = build(filename) {
|
if let Err(err) = build(filename) {
|
||||||
panic!("pass test failed:\n{}", err);
|
panic!("pass test failed:\n{:?}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ license = "Apache-2.0 WITH LLVM-exception"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cranelift-isle = { version = "*", path = "../isle/", features = ["miette-errors", "logging"] }
|
cranelift-isle = { version = "*", path = "../isle/", features = ["fancy-errors", "logging"] }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
miette = { version = "5.1.0", features = ["fancy"] }
|
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cranelift_isle::{compile, lexer, parser};
|
use cranelift_isle::compile;
|
||||||
use miette::{Context, IntoDiagnostic, Result};
|
use cranelift_isle::error::Errors;
|
||||||
use std::{
|
use std::{
|
||||||
default::Default,
|
default::Default,
|
||||||
fs,
|
fs,
|
||||||
@@ -20,33 +20,19 @@ struct Opts {
|
|||||||
inputs: Vec<PathBuf>,
|
inputs: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<(), Errors> {
|
||||||
let _ = env_logger::try_init();
|
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 opts = Opts::parse();
|
||||||
|
let code = compile::from_files(opts.inputs, &Default::default())?;
|
||||||
let lexer = lexer::Lexer::from_files(opts.inputs)?;
|
|
||||||
let defs = parser::parse(lexer)?;
|
|
||||||
let code = compile::compile(&defs, &Default::default())?;
|
|
||||||
|
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let (mut output, output_name): (Box<dyn Write>, _) = match &opts.output {
|
let (mut output, output_name): (Box<dyn Write>, _) = match &opts.output {
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
let output = Box::new(
|
let output =
|
||||||
fs::File::create(f)
|
Box::new(fs::File::create(f).map_err(|e| {
|
||||||
.into_diagnostic()
|
Errors::from_io(e, format!("failed to create '{}'", f.display()))
|
||||||
.with_context(|| format!("failed to create '{}'", f.display()))?,
|
})?);
|
||||||
);
|
|
||||||
(output, f.display().to_string())
|
(output, f.display().to_string())
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@@ -57,8 +43,7 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
output
|
output
|
||||||
.write_all(code.as_bytes())
|
.write_all(code.as_bytes())
|
||||||
.into_diagnostic()
|
.map_err(|e| Errors::from_io(e, format!("failed to write to '{}'", output_name)))?;
|
||||||
.with_context(|| format!("failed to write to '{}'", output_name))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,12 @@ criteria = "safe-to-deploy"
|
|||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
notes = "I am the author of this crate."
|
notes = "I am the author of this crate."
|
||||||
|
|
||||||
|
[[audits.codespan-reporting]]
|
||||||
|
who = "Jamey Sharp <jsharp@fastly.com>"
|
||||||
|
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]]
|
[[audits.criterion]]
|
||||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
criteria = "safe-to-run"
|
criteria = "safe-to-run"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ criteria = "safe-to-deploy"
|
|||||||
|
|
||||||
[[exemptions.adler]]
|
[[exemptions.adler]]
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-run"
|
||||||
|
|
||||||
[[exemptions.aead]]
|
[[exemptions.aead]]
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -394,10 +394,6 @@ criteria = "safe-to-deploy"
|
|||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
[[exemptions.is_ci]]
|
|
||||||
version = "1.1.1"
|
|
||||||
criteria = "safe-to-deploy"
|
|
||||||
|
|
||||||
[[exemptions.itertools]]
|
[[exemptions.itertools]]
|
||||||
version = "0.10.3"
|
version = "0.10.3"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
@@ -482,17 +478,9 @@ criteria = "safe-to-deploy"
|
|||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
criteria = "safe-to-deploy"
|
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]]
|
[[exemptions.miniz_oxide]]
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-run"
|
||||||
|
|
||||||
[[exemptions.mio]]
|
[[exemptions.mio]]
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -562,10 +550,6 @@ criteria = "safe-to-run"
|
|||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
[[exemptions.owo-colors]]
|
|
||||||
version = "3.4.0"
|
|
||||||
criteria = "safe-to-deploy"
|
|
||||||
|
|
||||||
[[exemptions.p256]]
|
[[exemptions.p256]]
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
@@ -818,10 +802,6 @@ criteria = "safe-to-deploy"
|
|||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
[[exemptions.smawk]]
|
|
||||||
version = "0.3.1"
|
|
||||||
criteria = "safe-to-deploy"
|
|
||||||
|
|
||||||
[[exemptions.socket2]]
|
[[exemptions.socket2]]
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
@@ -854,18 +834,6 @@ criteria = "safe-to-deploy"
|
|||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
criteria = "safe-to-deploy"
|
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]]
|
[[exemptions.symbolic_expressions]]
|
||||||
version = "5.0.3"
|
version = "5.0.3"
|
||||||
criteria = "safe-to-run"
|
criteria = "safe-to-run"
|
||||||
@@ -946,10 +914,6 @@ criteria = "safe-to-run"
|
|||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
|
|
||||||
[[exemptions.unicode-linebreak]]
|
|
||||||
version = "0.1.2"
|
|
||||||
criteria = "safe-to-deploy"
|
|
||||||
|
|
||||||
[[exemptions.unicode-width]]
|
[[exemptions.unicode-width]]
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
|
|||||||
Reference in New Issue
Block a user