From f5d01316eea6f6475ac38c1343b2d0584ae8a162 Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Mon, 23 Dec 2024 18:53:15 +0100 Subject: [PATCH] [CLI] Add `compiler-message-format-json` experiment value to `aptos move compile` and `aptos move lint` (#15540) --- Cargo.lock | 2 + crates/aptos/src/move_tool/lint.rs | 6 ++ third_party/move/move-compiler-v2/Cargo.toml | 2 + .../move-compiler-v2/src/diagnostics/human.rs | 33 +++++++++++ .../move-compiler-v2/src/diagnostics/json.rs | 44 ++++++++++++++ .../move-compiler-v2/src/diagnostics/mod.rs | 58 +++++++++++++++++++ .../move/move-compiler-v2/src/experiments.rs | 6 ++ third_party/move/move-compiler-v2/src/lib.rs | 38 +++++++----- .../compiler-message-format-json/errors.exp | 4 ++ .../compiler-message-format-json/errors.move | 6 ++ .../compiler-message-format-json/warnings.exp | 4 ++ .../warnings.move | 6 ++ .../move/move-compiler-v2/tests/testsuite.rs | 43 +++++++++----- third_party/move/move-model/src/model.rs | 20 ++++--- .../src/framework.rs | 5 +- .../move-cli/src/base/test_validation.rs | 7 ++- .../move/tools/move-linter/tests/testsuite.rs | 5 +- .../src/compilation/compiled_package.rs | 10 ++-- 18 files changed, 253 insertions(+), 46 deletions(-) create mode 100644 third_party/move/move-compiler-v2/src/diagnostics/human.rs create mode 100644 third_party/move/move-compiler-v2/src/diagnostics/json.rs create mode 100644 third_party/move/move-compiler-v2/src/diagnostics/mod.rs create mode 100644 third_party/move/move-compiler-v2/tests/compiler-message-format-json/errors.exp create mode 100644 third_party/move/move-compiler-v2/tests/compiler-message-format-json/errors.move create mode 100644 third_party/move/move-compiler-v2/tests/compiler-message-format-json/warnings.exp create mode 100644 third_party/move/move-compiler-v2/tests/compiler-message-format-json/warnings.move diff --git a/Cargo.lock b/Cargo.lock index 1d82d5ce72d44..6248ce77ab1d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11253,6 +11253,7 @@ dependencies = [ "anyhow", "bcs 0.1.4", "clap 4.5.21", + "codespan", "codespan-reporting", "datatest-stable", "ethnum", @@ -11277,6 +11278,7 @@ dependencies = [ "num 0.4.1", "once_cell", "petgraph 0.6.5", + "serde_json", "walkdir", ] diff --git a/crates/aptos/src/move_tool/lint.rs b/crates/aptos/src/move_tool/lint.rs index 0b320098d65bf..954f83b9b9269 100644 --- a/crates/aptos/src/move_tool/lint.rs +++ b/crates/aptos/src/move_tool/lint.rs @@ -75,6 +75,10 @@ pub struct LintPackage { /// See #[clap(long, env = "APTOS_CHECK_TEST_CODE")] pub check_test_code: bool, + + /// Experiments + #[clap(long, hide(true))] + pub experiments: Vec, } impl LintPackage { @@ -89,6 +93,7 @@ impl LintPackage { language_version, skip_attribute_checks, check_test_code, + experiments, } = self.clone(); MovePackageDir { dev, @@ -100,6 +105,7 @@ impl LintPackage { language_version, skip_attribute_checks, check_test_code, + experiments, ..MovePackageDir::new() } } diff --git a/third_party/move/move-compiler-v2/Cargo.toml b/third_party/move/move-compiler-v2/Cargo.toml index f24853b9f9b2b..74654702066d0 100644 --- a/third_party/move/move-compiler-v2/Cargo.toml +++ b/third_party/move/move-compiler-v2/Cargo.toml @@ -14,6 +14,7 @@ abstract-domain-derive = { path = "../move-model/bytecode/abstract_domain_derive anyhow = { workspace = true } bcs = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } +codespan = { workspace = true } codespan-reporting = { workspace = true, features = ["serde", "serialization"] } ethnum = { workspace = true } flexi_logger = { workspace = true } @@ -35,6 +36,7 @@ move-symbol-pool = { workspace = true } num = { workspace = true } once_cell = { workspace = true } petgraph = { workspace = true } +serde_json = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/third_party/move/move-compiler-v2/src/diagnostics/human.rs b/third_party/move/move-compiler-v2/src/diagnostics/human.rs new file mode 100644 index 0000000000000..ea15b03a3f791 --- /dev/null +++ b/third_party/move/move-compiler-v2/src/diagnostics/human.rs @@ -0,0 +1,33 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::diagnostics::Emitter; +use codespan::{FileId, Files}; +use codespan_reporting::{ + diagnostic::Diagnostic, + term::{emit, termcolor::WriteColor, Config}, +}; + +/// It's used in the native aptos-cli output to show error messages. +/// Wraps the `codespan_reporting::term::emit()` method. +pub struct HumanEmitter<'w, W: WriteColor> { + writer: &'w mut W, +} + +impl<'w, W> HumanEmitter<'w, W> +where + W: WriteColor, +{ + pub fn new(writer: &'w mut W) -> Self { + HumanEmitter { writer } + } +} + +impl<'w, W> Emitter for HumanEmitter<'w, W> +where + W: WriteColor, +{ + fn emit(&mut self, source_files: &Files, diag: &Diagnostic) { + emit(&mut self.writer, &Config::default(), source_files, diag).expect("emit must not fail") + } +} diff --git a/third_party/move/move-compiler-v2/src/diagnostics/json.rs b/third_party/move/move-compiler-v2/src/diagnostics/json.rs new file mode 100644 index 0000000000000..ee1825c37b48c --- /dev/null +++ b/third_party/move/move-compiler-v2/src/diagnostics/json.rs @@ -0,0 +1,44 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::diagnostics::Emitter; +use codespan::{FileId, Files}; +use codespan_reporting::diagnostic::{Diagnostic, Label}; +use std::io::Write; + +/// Shows compiler errors as a structured JSON output. +/// Exists to support various tools external to the aptos-cli, i.e. IDEs. +pub struct JsonEmitter<'w, W: Write> { + writer: &'w mut W, +} + +impl<'w, W: Write> JsonEmitter<'w, W> { + pub fn new(writer: &'w mut W) -> Self { + JsonEmitter { writer } + } +} + +impl<'w, W: Write> Emitter for JsonEmitter<'w, W> { + fn emit(&mut self, source_files: &Files, diag: &Diagnostic) { + let fpath_labels = diag + .labels + .iter() + .map(|label| { + let fpath = codespan_reporting::files::Files::name(source_files, label.file_id) + .expect("always Ok() in the impl") + .to_string(); + Label::new(label.style, fpath, label.range.clone()) + }) + .collect(); + let mut json_diag = Diagnostic::new(diag.severity) + .with_message(diag.message.clone()) + .with_labels(fpath_labels) + .with_notes(diag.notes.clone()); + if let Some(code) = &diag.code { + json_diag = json_diag.with_code(code) + } + serde_json::to_writer(&mut self.writer, &json_diag).expect("it should be serializable"); + writeln!(&mut self.writer) + .expect("dest is stderr / in-memory buffer, it should always be available"); + } +} diff --git a/third_party/move/move-compiler-v2/src/diagnostics/mod.rs b/third_party/move/move-compiler-v2/src/diagnostics/mod.rs new file mode 100644 index 0000000000000..fc2cb767a3a2f --- /dev/null +++ b/third_party/move/move-compiler-v2/src/diagnostics/mod.rs @@ -0,0 +1,58 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + diagnostics::{human::HumanEmitter, json::JsonEmitter}, + options, Experiment, +}; +use anyhow::bail; +use codespan::{FileId, Files}; +use codespan_reporting::{ + diagnostic::{Diagnostic, Severity}, + term::termcolor::WriteColor, +}; +use move_model::model::GlobalEnv; + +pub mod human; +pub mod json; + +impl options::Options { + pub fn error_emitter<'w, W>(&self, dest: &'w mut W) -> Box + where + W: WriteColor, + { + if self.experiment_on(Experiment::MESSAGE_FORMAT_JSON) { + Box::new(JsonEmitter::new(dest)) + } else { + Box::new(HumanEmitter::new(dest)) + } + } +} + +pub trait Emitter { + fn emit(&mut self, source_files: &Files, diag: &Diagnostic); + + /// Writes accumulated diagnostics of given or higher severity. + fn report_diag(&mut self, global_env: &GlobalEnv, severity: Severity) { + global_env.report_diag_with_filter( + |files, diag| self.emit(files, diag), + |d| d.severity >= severity, + ); + } + + /// Helper function to report diagnostics, check for errors, and fail with a message on + /// errors. This function is idempotent and will not report the same diagnostics again. + fn check_diag( + &mut self, + global_env: &GlobalEnv, + report_severity: Severity, + msg: &str, + ) -> anyhow::Result<()> { + self.report_diag(global_env, report_severity); + if global_env.has_errors() { + bail!("exiting with {}", msg); + } else { + Ok(()) + } + } +} diff --git a/third_party/move/move-compiler-v2/src/experiments.rs b/third_party/move/move-compiler-v2/src/experiments.rs index a519049690013..e7a5176aa78a8 100644 --- a/third_party/move/move-compiler-v2/src/experiments.rs +++ b/third_party/move/move-compiler-v2/src/experiments.rs @@ -271,6 +271,11 @@ pub static EXPERIMENTS: Lazy> = Lazy::new(|| { .to_string(), default: Given(false), }, + Experiment { + name: Experiment::MESSAGE_FORMAT_JSON.to_string(), + description: "Enable json format for compiler messages".to_string(), + default: Given(false), + }, ]; experiments .into_iter() @@ -302,6 +307,7 @@ impl Experiment { pub const LAMBDA_LIFTING: &'static str = "lambda-lifting"; pub const LAMBDA_VALUES: &'static str = "lambda-values"; pub const LINT_CHECKS: &'static str = "lint-checks"; + pub const MESSAGE_FORMAT_JSON: &'static str = "compiler-message-format-json"; pub const OPTIMIZE: &'static str = "optimize"; pub const OPTIMIZE_EXTRA: &'static str = "optimize-extra"; pub const OPTIMIZE_WAITING_FOR_COMPARE_TESTS: &'static str = diff --git a/third_party/move/move-compiler-v2/src/lib.rs b/third_party/move/move-compiler-v2/src/lib.rs index 2b7e6d18b8c25..7f5c6c4139c43 100644 --- a/third_party/move/move-compiler-v2/src/lib.rs +++ b/third_party/move/move-compiler-v2/src/lib.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 mod bytecode_generator; +pub mod diagnostics; pub mod env_pipeline; mod experiments; pub mod external_checks; @@ -14,6 +15,7 @@ pub mod pipeline; pub mod plan_builder; use crate::{ + diagnostics::Emitter, env_pipeline::{ acquires_checker, ast_simplifier, cyclic_instantiation_checker, flow_insensitive_checkers, function_checker, inliner, lambda_lifter, lambda_lifter::LambdaLiftingOptions, @@ -71,30 +73,31 @@ use move_stackless_bytecode::function_target_pipeline::{ }; use move_symbol_pool::Symbol; pub use options::Options; -use std::{collections::BTreeSet, io::Write, path::Path}; +use std::{collections::BTreeSet, path::Path}; /// Run Move compiler and print errors to stderr. pub fn run_move_compiler_to_stderr( options: Options, ) -> anyhow::Result<(GlobalEnv, Vec)> { - let mut error_writer = StandardStream::stderr(ColorChoice::Auto); - run_move_compiler(&mut error_writer, options) + let mut stderr = StandardStream::stderr(ColorChoice::Auto); + let mut emitter = options.error_emitter(&mut stderr); + run_move_compiler(emitter.as_mut(), options) } /// Run move compiler and print errors to given writer. Returns the set of compiled units. -pub fn run_move_compiler( - error_writer: &mut W, +pub fn run_move_compiler( + emitter: &mut E, options: Options, ) -> anyhow::Result<(GlobalEnv, Vec)> where - W: WriteColor + Write, + E: Emitter + ?Sized, { logging::setup_logging(); info!("Move Compiler v2"); // Run context check. let mut env = run_checker_and_rewriters(options.clone())?; - check_errors(&env, error_writer, "checking errors")?; + check_errors(&env, emitter, "checking errors")?; if options.experiment_on(Experiment::STOP_BEFORE_STACKLESS_BYTECODE) { std::process::exit(0) @@ -102,7 +105,8 @@ where // Run code generator let mut targets = run_bytecode_gen(&env); - check_errors(&env, error_writer, "code generation errors")?; + check_errors(&env, emitter, "code generation errors")?; + debug!("After bytecode_gen, GlobalEnv={}", env.dump_env()); // Run transformation pipeline @@ -129,14 +133,14 @@ where } else { pipeline.run_with_hook(&env, &mut targets, |_| {}, |_, _, _| !env.has_errors()) } - check_errors(&env, error_writer, "stackless-bytecode analysis errors")?; + check_errors(&env, emitter, "stackless-bytecode analysis errors")?; if options.experiment_on(Experiment::STOP_BEFORE_FILE_FORMAT) { std::process::exit(0) } let modules_and_scripts = run_file_format_gen(&mut env, &targets); - check_errors(&env, error_writer, "assembling errors")?; + check_errors(&env, emitter, "assembling errors")?; debug!( "File format bytecode:\n{}", @@ -145,7 +149,7 @@ where let annotated_units = annotate_units(modules_and_scripts); run_bytecode_verifier(&annotated_units, &mut env); - check_errors(&env, error_writer, "bytecode verification errors")?; + check_errors(&env, emitter, "bytecode verification errors")?; // Finally mark this model to be generated by v2 env.set_compiler_v2(true); @@ -163,7 +167,8 @@ pub fn run_move_compiler_for_analysis( options.whole_program = true; // will set `treat_everything_as_target` options = options.set_experiment(Experiment::SPEC_REWRITE, true); options = options.set_experiment(Experiment::ATTACH_COMPILED_MODULE, true); - let (env, _units) = run_move_compiler(error_writer, options)?; + let mut emitter = options.error_emitter(error_writer); + let (env, _units) = run_move_compiler(emitter.as_mut(), options)?; // Reset for subsequent analysis env.treat_everything_as_target(false); Ok(env) @@ -605,13 +610,14 @@ fn get_vm_error_loc(env: &GlobalEnv, source_map: &SourceMap, e: &VMError) -> Opt } /// Report any diags in the env to the writer and fail if there are errors. -pub fn check_errors(env: &GlobalEnv, error_writer: &mut W, msg: &str) -> anyhow::Result<()> +pub fn check_errors(env: &GlobalEnv, emitter: &mut E, msg: &str) -> anyhow::Result<()> where - W: WriteColor + Write, + E: Emitter + ?Sized, { let options = env.get_extension::().unwrap_or_default(); - env.report_diag(error_writer, options.report_severity()); - env.check_diag(error_writer, options.report_severity(), msg) + + emitter.report_diag(env, options.report_severity()); + emitter.check_diag(env, options.report_severity(), msg) } /// Annotate the given compiled units. diff --git a/third_party/move/move-compiler-v2/tests/compiler-message-format-json/errors.exp b/third_party/move/move-compiler-v2/tests/compiler-message-format-json/errors.exp new file mode 100644 index 0000000000000..f64b61007a78d --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/compiler-message-format-json/errors.exp @@ -0,0 +1,4 @@ + +Diagnostics: +{"severity":"Error","code":null,"message":"cannot use `bool` with an operator which expects a value of type `integer`","labels":[{"style":"Primary","file_id":"tests/compiler-message-format-json/errors.move","range":{"start":51,"end":55},"message":""}],"notes":[]} +{"severity":"Error","code":null,"message":"cannot use `bool` with an operator which expects a value of type `integer`","labels":[{"style":"Primary","file_id":"tests/compiler-message-format-json/errors.move","range":{"start":69,"end":73},"message":""}],"notes":[]} diff --git a/third_party/move/move-compiler-v2/tests/compiler-message-format-json/errors.move b/third_party/move/move-compiler-v2/tests/compiler-message-format-json/errors.move new file mode 100644 index 0000000000000..4038807912f3a --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/compiler-message-format-json/errors.move @@ -0,0 +1,6 @@ +module 0x42::errors { + fun main() { + 1 + true; + 2 + true; + } +} diff --git a/third_party/move/move-compiler-v2/tests/compiler-message-format-json/warnings.exp b/third_party/move/move-compiler-v2/tests/compiler-message-format-json/warnings.exp new file mode 100644 index 0000000000000..7950ff370d030 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/compiler-message-format-json/warnings.exp @@ -0,0 +1,4 @@ + +Diagnostics: +{"severity":"Warning","code":null,"message":"Unused local variable `a`. Consider removing or prefixing with an underscore: `_a`","labels":[{"style":"Primary","file_id":"tests/compiler-message-format-json/warnings.move","range":{"start":53,"end":54},"message":""}],"notes":[]} +{"severity":"Warning","code":null,"message":"Unused local variable `b`. Consider removing or prefixing with an underscore: `_b`","labels":[{"style":"Primary","file_id":"tests/compiler-message-format-json/warnings.move","range":{"start":72,"end":73},"message":""}],"notes":[]} diff --git a/third_party/move/move-compiler-v2/tests/compiler-message-format-json/warnings.move b/third_party/move/move-compiler-v2/tests/compiler-message-format-json/warnings.move new file mode 100644 index 0000000000000..f9e857c702fe7 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/compiler-message-format-json/warnings.move @@ -0,0 +1,6 @@ +module 0x42::warnings { + fun main() { + let a = 1; + let b = 2; + } +} diff --git a/third_party/move/move-compiler-v2/tests/testsuite.rs b/third_party/move/move-compiler-v2/tests/testsuite.rs index a5e6ef2322d8d..9233703a0986b 100644 --- a/third_party/move/move-compiler-v2/tests/testsuite.rs +++ b/third_party/move/move-compiler-v2/tests/testsuite.rs @@ -737,6 +737,20 @@ const TEST_CONFIGS: Lazy> = Lazy::new(|| { dump_bytecode: DumpLevel::EndStage, dump_bytecode_filter: None, }, + TestConfig { + name: "compiler-message-format-json", + runner: |p| run_test(p, get_config_by_name("compiler-message-format-json")), + include: vec!["/compiler-message-format-json/"], + exclude: vec![], + exp_suffix: None, + options: opts + .clone() + .set_experiment(Experiment::MESSAGE_FORMAT_JSON, true), + stop_after: StopAfter::AstPipeline, + dump_ast: DumpLevel::None, + dump_bytecode: DumpLevel::None, + dump_bytecode_filter: None, + }, ]; configs.into_iter().map(|c| (c.name, c)).collect() }); @@ -780,7 +794,7 @@ fn run_test(path: &Path, config: TestConfig) -> datatest_stable::Result<()> { // Run context checker let mut env = move_compiler_v2::run_checker(options.clone())?; - let mut ok = check_diags(&mut test_output.borrow_mut(), &env); + let mut ok = check_diags(&mut test_output.borrow_mut(), &env, &options); if ok { // Run env processor pipeline. @@ -796,10 +810,10 @@ fn run_test(path: &Path, config: TestConfig) -> datatest_stable::Result<()> { test_output .borrow_mut() .push_str(&String::from_utf8_lossy(&out.into_inner())); - ok = check_diags(&mut test_output.borrow_mut(), &env); + ok = check_diags(&mut test_output.borrow_mut(), &env, &options); } else { env_pipeline.run(&mut env); - ok = check_diags(&mut test_output.borrow_mut(), &env); + ok = check_diags(&mut test_output.borrow_mut(), &env, &options); if ok && config.dump_ast == DumpLevel::EndStage { test_output.borrow_mut().push_str(&format!( "// -- Model dump before bytecode pipeline\n{}\n", @@ -824,13 +838,13 @@ fn run_test(path: &Path, config: TestConfig) -> datatest_stable::Result<()> { // In real use, this is run outside of the compilation process, but the needed info is // available in `env` once we finish the AST. plan_builder::construct_test_plan(&env, None); - ok = check_diags(&mut test_output.borrow_mut(), &env); + ok = check_diags(&mut test_output.borrow_mut(), &env, &options); } if ok && config.stop_after > StopAfter::AstPipeline { // Run stackless bytecode generator let mut targets = move_compiler_v2::run_bytecode_gen(&env); - ok = check_diags(&mut test_output.borrow_mut(), &env); + ok = check_diags(&mut test_output.borrow_mut(), &env, &options); if ok { // Run the target pipeline. let bytecode_pipeline = if config.stop_after == StopAfter::BytecodeGen { @@ -852,7 +866,7 @@ fn run_test(path: &Path, config: TestConfig) -> datatest_stable::Result<()> { // bytecode from the generator, if requested. |targets_before| { let out = &mut test_output.borrow_mut(); - update_diags(ok.borrow_mut(), out, &env); + update_diags(ok.borrow_mut(), out, &env, &options); if bytecode_dump_enabled(&config, true, INITIAL_BYTECODE_STAGE) { let dump = &move_stackless_bytecode::print_targets_with_annotations_for_test( @@ -870,7 +884,7 @@ fn run_test(path: &Path, config: TestConfig) -> datatest_stable::Result<()> { // bytecode after the processor, if requested. |i, processor, targets_after| { let out = &mut test_output.borrow_mut(); - update_diags(ok.borrow_mut(), out, &env); + update_diags(ok.borrow_mut(), out, &env, &options); if bytecode_dump_enabled(&config, i + 1 == count, processor.name().as_str()) { let title = format!("after {}:", processor.name()); let dump = @@ -890,7 +904,7 @@ fn run_test(path: &Path, config: TestConfig) -> datatest_stable::Result<()> { if *ok.borrow() && config.stop_after == StopAfter::FileFormat { let units = run_file_format_gen(&mut env, &targets); let out = &mut test_output.borrow_mut(); - update_diags(ok.borrow_mut(), out, &env); + update_diags(ok.borrow_mut(), out, &env, &options); if *ok.borrow() { if bytecode_dump_enabled(&config, true, FILE_FORMAT_STAGE) { out.push_str( @@ -904,7 +918,7 @@ fn run_test(path: &Path, config: TestConfig) -> datatest_stable::Result<()> { } else { out.push_str("\n============ bytecode verification failed ========\n"); } - check_diags(out, &env); + check_diags(out, &env, &options); } } } @@ -929,9 +943,12 @@ fn bytecode_dump_enabled(config: &TestConfig, is_last: bool, name: &str) -> bool } /// Checks for diagnostics and adds them to the baseline. -fn check_diags(baseline: &mut String, env: &GlobalEnv) -> bool { +fn check_diags(baseline: &mut String, env: &GlobalEnv, options: &Options) -> bool { let mut error_writer = Buffer::no_color(); - env.report_diag(&mut error_writer, Severity::Note); + { + let mut emitter = options.error_emitter(&mut error_writer); + emitter.report_diag(env, Severity::Note); + } let diag = String::from_utf8_lossy(&error_writer.into_inner()).to_string(); if !diag.is_empty() { *baseline += &format!("\nDiagnostics:\n{}", diag); @@ -941,8 +958,8 @@ fn check_diags(baseline: &mut String, env: &GlobalEnv) -> bool { ok } -fn update_diags(mut ok: RefMut, baseline: &mut String, env: &GlobalEnv) { - if !check_diags(baseline, env) { +fn update_diags(mut ok: RefMut, baseline: &mut String, env: &GlobalEnv, options: &Options) { + if !check_diags(baseline, env, options) { *ok = false; } } diff --git a/third_party/move/move-model/src/model.rs b/third_party/move/move-model/src/model.rs index c4d08a9f63b74..175923e0dade2 100644 --- a/third_party/move/move-model/src/model.rs +++ b/third_party/move/move-model/src/model.rs @@ -1218,7 +1218,12 @@ impl GlobalEnv { /// Writes accumulated diagnostics of given or higher severity. pub fn report_diag(&self, writer: &mut W, severity: Severity) { - self.report_diag_with_filter(writer, |d| d.severity >= severity) + self.report_diag_with_filter( + |files, diag| { + emit(writer, &Config::default(), files, diag).expect("emit must not fail") + }, + |d| d.severity >= severity, + ); } /// Helper function to report diagnostics, check for errors, and fail with a message on @@ -1317,11 +1322,11 @@ impl GlobalEnv { } /// Writes accumulated diagnostics that pass through `filter` - pub fn report_diag_with_filter) -> bool>( - &self, - writer: &mut W, - mut filter: F, - ) { + pub fn report_diag_with_filter(&self, mut emitter: E, mut filter: F) + where + E: FnMut(&Files, &Diagnostic), + F: FnMut(&Diagnostic) -> bool, + { let mut shown = BTreeSet::new(); self.diags.borrow_mut().sort_by(|a, b| { let reported_ordering = a.1.cmp(&b.1); @@ -1343,8 +1348,7 @@ impl GlobalEnv { // Avoid showing the same message twice. This can happen e.g. because of // duplication of expressions via schema inclusion. if shown.insert(format!("{:?}", diag)) { - emit(writer, &Config::default(), &self.source_files, diag) - .expect("emit must not fail"); + emitter(&self.source_files, diag); } *reported = true; } diff --git a/third_party/move/testing-infra/transactional-test-runner/src/framework.rs b/third_party/move/testing-infra/transactional-test-runner/src/framework.rs index 9fcf18dc3f188..2158f95b6a2b7 100644 --- a/third_party/move/testing-infra/transactional-test-runner/src/framework.rs +++ b/third_party/move/testing-infra/transactional-test-runner/src/framework.rs @@ -825,7 +825,10 @@ fn compile_source_unit_v2( options = options.set_experiment(exp, value) } let mut error_writer = termcolor::Buffer::no_color(); - let result = move_compiler_v2::run_move_compiler(&mut error_writer, options); + let result = { + let mut emitter = options.error_emitter(&mut error_writer); + move_compiler_v2::run_move_compiler(emitter.as_mut(), options) + }; let error_str = String::from_utf8_lossy(&error_writer.into_inner()).to_string(); let (model, mut units) = result.map_err(|_| anyhow::anyhow!("compilation errors:\n {}", error_str))?; diff --git a/third_party/move/tools/move-cli/src/base/test_validation.rs b/third_party/move/tools/move-cli/src/base/test_validation.rs index e721eba9fcad4..1203cd581c221 100644 --- a/third_party/move/tools/move-cli/src/base/test_validation.rs +++ b/third_party/move/tools/move-cli/src/base/test_validation.rs @@ -5,7 +5,7 @@ //! This module manages validation of the unit tests, in addition to standard compiler //! checking. -use codespan_reporting::term::{termcolor, termcolor::StandardStream}; +use codespan_reporting::term::{emit, termcolor, termcolor::StandardStream, Config}; use move_model::model::GlobalEnv; use once_cell::sync::Lazy; use std::sync::Mutex; @@ -35,8 +35,11 @@ pub(crate) fn validate(env: &GlobalEnv) { pub fn has_errors_then_report(model: &GlobalEnv) -> bool { let mut has_errors = false; + let mut writer = StandardStream::stderr(termcolor::ColorChoice::Auto); model.report_diag_with_filter( - &mut StandardStream::stderr(termcolor::ColorChoice::Auto), + |files, diag| { + emit(&mut writer, &Config::default(), files, diag).expect("emit must not fail") + }, |d| { let include = d.labels.iter().all(|l| { let fname = model.get_file(l.file_id).to_string_lossy(); diff --git a/third_party/move/tools/move-linter/tests/testsuite.rs b/third_party/move/tools/move-linter/tests/testsuite.rs index 454957cdd161b..02ef906c1bdc9 100644 --- a/third_party/move/tools/move-linter/tests/testsuite.rs +++ b/third_party/move/tools/move-linter/tests/testsuite.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use codespan_reporting::{diagnostic::Severity, term::termcolor::Buffer}; -use move_compiler_v2::{run_move_compiler, Experiment}; +use move_compiler_v2::{diagnostics::human::HumanEmitter, run_move_compiler, Experiment}; use move_linter::MoveLintChecks; use move_model::metadata::{CompilerVersion, LanguageVersion}; use move_prover_test_utils::baseline_test; @@ -30,7 +30,8 @@ fn test_runner(path: &Path) -> datatest_stable::Result<()> { }; let mut output = String::new(); let mut error_writer = Buffer::no_color(); - match run_move_compiler(&mut error_writer, compiler_options) { + let mut emitter = HumanEmitter::new(&mut error_writer); + match run_move_compiler(&mut emitter, compiler_options) { Err(e) => { output.push_str(&format!( "Aborting with compilation errors:\n{:#}\n{}\n", diff --git a/third_party/move/tools/move-package/src/compilation/compiled_package.rs b/third_party/move/tools/move-package/src/compilation/compiled_package.rs index 31fb59bd73bef..3ed670dc3ddb1 100644 --- a/third_party/move/tools/move-package/src/compilation/compiled_package.rs +++ b/third_party/move/tools/move-package/src/compilation/compiled_package.rs @@ -1137,8 +1137,9 @@ pub fn unimplemented_v2_driver(_options: move_compiler_v2::Options) -> CompilerD /// Runs the v2 compiler, exiting the process if any errors occurred. pub fn build_and_report_v2_driver(options: move_compiler_v2::Options) -> CompilerDriverResult { - let mut writer = StandardStream::stderr(ColorChoice::Auto); - match move_compiler_v2::run_move_compiler(&mut writer, options) { + let mut stderr = StandardStream::stderr(ColorChoice::Auto); + let mut emitter = options.error_emitter(&mut stderr); + match move_compiler_v2::run_move_compiler(emitter.as_mut(), options) { Ok((env, units)) => Ok(( move_compiler_v2::make_files_source_text(&env), units, @@ -1155,8 +1156,9 @@ pub fn build_and_report_v2_driver(options: move_compiler_v2::Options) -> Compile pub fn build_and_report_no_exit_v2_driver( options: move_compiler_v2::Options, ) -> CompilerDriverResult { - let mut writer = StandardStream::stderr(ColorChoice::Auto); - let (env, units) = move_compiler_v2::run_move_compiler(&mut writer, options)?; + let mut stderr = StandardStream::stderr(ColorChoice::Auto); + let mut emitter = options.error_emitter(&mut stderr); + let (env, units) = move_compiler_v2::run_move_compiler(emitter.as_mut(), options)?; Ok(( move_compiler_v2::make_files_source_text(&env), units,