Skip to content

Commit

Permalink
Auto merge of rust-lang#115691 - jsgf:typed-json-diags, r=est31,dtolnay
Browse files Browse the repository at this point in the history
Add `$message_type` field to distinguish json diagnostic outputs

Currently the json-formatted outputs have no way to unambiguously determine which kind of message is being output. A consumer can look for specific fields in the json object (eg "message"), but there's no guarantee that in future some other kind of output will have a field of the same name.

This PR adds a `"type"` field to add json outputs which can be used to unambiguously determine which kind of output it is. The mapping is:

`diagnostic`: regular compiler diagnostics
`artifact`: artifact notifications
`future_incompat`: Future incompatibility report
`unused_extern`: Unused crate warnings/errors

This matches the "internally tagged" representation for serde enums.
  • Loading branch information
bors committed Nov 21, 2023
2 parents 390e3c8 + fe50c53 commit 85c42b7
Show file tree
Hide file tree
Showing 17 changed files with 100 additions and 67 deletions.
65 changes: 35 additions & 30 deletions compiler/rustc_errors/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ impl JsonEmitter {
pub fn ignored_directories_in_source_blocks(self, value: Vec<String>) -> Self {
Self { ignored_directories_in_source_blocks: value, ..self }
}

fn emit(&mut self, val: EmitTyped<'_>) -> io::Result<()> {
if self.pretty {
serde_json::to_writer_pretty(&mut *self.dst, &val)?
} else {
serde_json::to_writer(&mut *self.dst, &val)?
};
self.dst.write_all(b"\n")?;
self.dst.flush()
}
}

#[derive(Serialize)]
#[serde(tag = "$message_type", rename_all = "snake_case")]
enum EmitTyped<'a> {
Diagnostic(Diagnostic),
Artifact(ArtifactNotification<'a>),
FutureIncompat(FutureIncompatReport<'a>),
UnusedExtern(UnusedExterns<'a, 'a, 'a>),
}

impl Translate for JsonEmitter {
Expand All @@ -160,47 +179,36 @@ impl Translate for JsonEmitter {
impl Emitter for JsonEmitter {
fn emit_diagnostic(&mut self, diag: &crate::Diagnostic) {
let data = Diagnostic::from_errors_diagnostic(diag, self);
let result = if self.pretty {
writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&data).unwrap())
} else {
writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
}
.and_then(|_| self.dst.flush());
let result = self.emit(EmitTyped::Diagnostic(data));
if let Err(e) = result {
panic!("failed to print diagnostics: {e:?}");
}
}

fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
let data = ArtifactNotification { artifact: path, emit: artifact_type };
let result = if self.pretty {
writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&data).unwrap())
} else {
writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
}
.and_then(|_| self.dst.flush());
let result = self.emit(EmitTyped::Artifact(data));
if let Err(e) = result {
panic!("failed to print notification: {e:?}");
}
}

fn emit_future_breakage_report(&mut self, diags: Vec<crate::Diagnostic>) {
let data: Vec<FutureBreakageItem> = diags
let data: Vec<FutureBreakageItem<'_>> = diags
.into_iter()
.map(|mut diag| {
if diag.level == crate::Level::Allow {
diag.level = crate::Level::Warning(None);
}
FutureBreakageItem { diagnostic: Diagnostic::from_errors_diagnostic(&diag, self) }
FutureBreakageItem {
diagnostic: EmitTyped::Diagnostic(Diagnostic::from_errors_diagnostic(
&diag, self,
)),
}
})
.collect();
let report = FutureIncompatReport { future_incompat_report: data };
let result = if self.pretty {
writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&report).unwrap())
} else {
writeln!(&mut self.dst, "{}", serde_json::to_string(&report).unwrap())
}
.and_then(|_| self.dst.flush());
let result = self.emit(EmitTyped::FutureIncompat(report));
if let Err(e) = result {
panic!("failed to print future breakage report: {e:?}");
}
Expand All @@ -209,12 +217,7 @@ impl Emitter for JsonEmitter {
fn emit_unused_externs(&mut self, lint_level: rustc_lint_defs::Level, unused_externs: &[&str]) {
let lint_level = lint_level.as_str();
let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
let result = if self.pretty {
writeln!(&mut self.dst, "{}", serde_json::to_string_pretty(&data).unwrap())
} else {
writeln!(&mut self.dst, "{}", serde_json::to_string(&data).unwrap())
}
.and_then(|_| self.dst.flush());
let result = self.emit(EmitTyped::UnusedExtern(data));
if let Err(e) = result {
panic!("failed to print unused externs: {e:?}");
}
Expand Down Expand Up @@ -313,13 +316,15 @@ struct ArtifactNotification<'a> {
}

#[derive(Serialize)]
struct FutureBreakageItem {
diagnostic: Diagnostic,
struct FutureBreakageItem<'a> {
// Always EmitTyped::Diagnostic, but we want to make sure it gets serialized
// with "$message_type".
diagnostic: EmitTyped<'a>,
}

#[derive(Serialize)]
struct FutureIncompatReport {
future_incompat_report: Vec<FutureBreakageItem>,
struct FutureIncompatReport<'a> {
future_incompat_report: Vec<FutureBreakageItem<'a>>,
}

// NOTE: Keep this in sync with the equivalent structs in rustdoc's
Expand Down
14 changes: 11 additions & 3 deletions src/doc/rustc/src/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ If parsing the output with Rust, the
[`cargo_metadata`](https://crates.io/crates/cargo_metadata) crate provides
some support for parsing the messages.

When parsing, care should be taken to be forwards-compatible with future changes
to the format. Optional values may be `null`. New fields may be added. Enumerated
fields like "level" or "suggestion_applicability" may add new values.
Each type of message has a `$message_type` field which can be used to
distinguish the different formats. When parsing, care should be taken
to be forwards-compatible with future changes to the format. Optional
values may be `null`. New fields may be added. Enumerated fields like
"level" or "suggestion_applicability" may add new values.

## Diagnostics

Expand All @@ -29,6 +31,8 @@ Diagnostics have the following format:

```javascript
{
/* Type of this message */
"$message_type": "diagnostic",
/* The primary message. */
"message": "unused variable: `x`",
/* The diagnostic code.
Expand Down Expand Up @@ -217,6 +221,8 @@ flag][option-emit] documentation.

```javascript
{
/* Type of this message */
"$message_type": "artifact",
/* The filename that was generated. */
"artifact": "libfoo.rlib",
/* The kind of artifact that was generated. Possible values:
Expand All @@ -239,6 +245,8 @@ information, even if the diagnostics have been suppressed (such as with an

```javascript
{
/* Type of this message */
"$message_type": "future_incompat",
/* An array of objects describing a warning that will become a hard error
in the future.
*/
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/diagnostic-width/flag-json.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{"message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.
{"$message_type":"diagnostic","message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.

Erroneous code examples:

Expand Down Expand Up @@ -33,8 +33,8 @@ LL | ..._: () = 42;
| expected due to this

"}
{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to previous error
{"$message_type":"diagnostic","message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to previous error

"}
{"message":"For more information about this error, try `rustc --explain E0308`.","code":null,"level":"failure-note","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0308`.
{"$message_type":"diagnostic","message":"For more information about this error, try `rustc --explain E0308`.","code":null,"level":"failure-note","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0308`.
"}
10 changes: 5 additions & 5 deletions tests/ui/json/json-bom-plus-crlf-multifile.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{"message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.
{"$message_type":"diagnostic","message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.

Erroneous code examples:

Expand Down Expand Up @@ -26,7 +26,7 @@ most common being when calling a function and passing an argument which has a
different type than the matching type in the function declaration.
"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":621,"byte_end":622,"line_start":17,"line_end":17,"column_start":22,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":22,"highlight_end":23}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":612,"byte_end":618,"line_start":17,"line_end":17,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":622,"byte_end":622,"line_start":17,"line_end":17,"column_start":23,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1; // Error in the middle of line.","highlight_start":23,"highlight_end":23}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf-multifile-aux.rs:17:22: error[E0308]: mismatched types
"}
{"message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.
{"$message_type":"diagnostic","message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.

Erroneous code examples:

Expand Down Expand Up @@ -54,7 +54,7 @@ most common being when calling a function and passing an argument which has a
different type than the matching type in the function declaration.
"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":681,"byte_end":682,"line_start":19,"line_end":19,"column_start":22,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1","highlight_start":22,"highlight_end":23}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":672,"byte_end":678,"line_start":19,"line_end":19,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = 1","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":682,"byte_end":682,"line_start":19,"line_end":19,"column_start":23,"column_end":23,"is_primary":true,"text":[{"text":" let s : String = 1","highlight_start":23,"highlight_end":23}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf-multifile-aux.rs:19:22: error[E0308]: mismatched types
"}
{"message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.
{"$message_type":"diagnostic","message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.

Erroneous code examples:

Expand Down Expand Up @@ -82,7 +82,7 @@ most common being when calling a function and passing an argument which has a
different type than the matching type in the function declaration.
"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":745,"byte_end":746,"line_start":23,"line_end":23,"column_start":1,"column_end":2,"is_primary":true,"text":[{"text":"1; // Error after the newline.","highlight_start":1,"highlight_end":2}],"label":"expected `String`, found integer","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":735,"byte_end":741,"line_start":22,"line_end":22,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String =","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"try using a conversion method","code":null,"level":"help","spans":[{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":746,"byte_end":746,"line_start":23,"line_end":23,"column_start":2,"column_end":2,"is_primary":true,"text":[{"text":"1; // Error after the newline.","highlight_start":2,"highlight_end":2}],"label":null,"suggested_replacement":".to_string()","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"$DIR/json-bom-plus-crlf-multifile-aux.rs:23:1: error[E0308]: mismatched types
"}
{"message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.
{"$message_type":"diagnostic","message":"mismatched types","code":{"code":"E0308","explanation":"Expected type did not match the received type.

Erroneous code examples:

Expand Down Expand Up @@ -110,5 +110,5 @@ most common being when calling a function and passing an argument which has a
different type than the matching type in the function declaration.
"},"level":"error","spans":[{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":801,"byte_end":809,"line_start":25,"line_end":26,"column_start":22,"column_end":6,"is_primary":true,"text":[{"text":" let s : String = (","highlight_start":22,"highlight_end":23},{"text":" ); // Error spanning the newline.","highlight_start":1,"highlight_end":6}],"label":"expected `String`, found `()`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null},{"file_name":"$DIR/json-bom-plus-crlf-multifile-aux.rs","byte_start":792,"byte_end":798,"line_start":25,"line_end":25,"column_start":13,"column_end":19,"is_primary":false,"text":[{"text":" let s : String = (","highlight_start":13,"highlight_end":19}],"label":"expected due to this","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"$DIR/json-bom-plus-crlf-multifile-aux.rs:25:22: error[E0308]: mismatched types
"}
{"message":"aborting due to 4 previous errors","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to 4 previous errors
{"$message_type":"diagnostic","message":"aborting due to 4 previous errors","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to 4 previous errors
"}
Loading

0 comments on commit 85c42b7

Please sign in to comment.