Report Which Plugin a Diagnostic came from#784
Conversation
| /// An annotated version of a [`Diagnostic`], whose [`DiagnosticLevel`] has been computed (taking into account any | ||
| /// 'allow' attributes or command-line flags), and that has pre-extracted text snippets to display alongside messages. | ||
| #[derive(Clone, Debug, Eq, PartialEq)] | ||
| #[derive(Clone, Debug, Eq)] |
There was a problem hiding this comment.
We implement PartialEq by hand now, so that we exclude the reported_by from the equality check.
| }); | ||
|
|
||
| // If the diagnostic was reported by a plugin, we just use the filename of the plugin (not its entire path). | ||
| let reported_by = diagnostic.plugin.as_deref().map_or("slicec", |plugin_path| { |
There was a problem hiding this comment.
I went back and forth on this, but I decided that in the case where the diagnostic is issued by slicec, it's worthwhile to still explicitly say that this came from slicec, and not a plugin.
It also makes the implementation cleaner and keep the error format 100% consistent, instead of being a little different for plugin diagnostics vs those about bad syntax for example.
There was a problem hiding this comment.
Note that AnnotatedDiagnostic is the "finished" form of a diagnostic.
These are always de-duplicated, and are what we're actually going to print to the user.
The normal Diagnostic type is what gets reported throughout the code, and before we exit, we convert the Diagnostics to AnnotatedDiagnostics so that we can print them.
Therefore Diagnostic can have at most one reporter (since only one thing reported it).
But AnnotatedDiagnostic can have multiple (since duplicate diagnostics will of been merged together).
| if let Some(original) = annotated_diagnostics.iter_mut().find(|d| **d == converted) { | ||
| original.reported_by.extend(converted.reported_by); | ||
| } else { | ||
| annotated_diagnostics.push(converted); |
There was a problem hiding this comment.
Previously, we completely threw away duplicate diagnostics.
Now if we encounter a duplicate, we update the original's reported_by before throwing away the duplicate now.
| /// present in the returned [`Vec`]. | ||
| pub fn get_annotated_diagnostics(&self, options: &SliceOptions) -> Vec<AnnotatedDiagnostic> { | ||
| let mut annotated_diagnostics = Vec::new(); | ||
| let mut annotated_diagnostics = Vec::<AnnotatedDiagnostic>::new(); |
There was a problem hiding this comment.
I have no idea why, but the compiler insists on putting a type-hint here now.
It seems unnecessary to me, but who am I to argue with the compiler...
| writeln!(self.output, "{prefix}: {}", console::style(&diagnostic.message).bold())?; | ||
|
|
||
| // Emit what the diagnostic was reported by. | ||
| writeln!(self.output, " Reported by: [{}]", diagnostic.reported_by.join(", "))?; |
There was a problem hiding this comment.
See the diagnostic_output_test or my PR description for an example of what the output looks like.
| diagnostics.extend(generator_diagnostics.into_inner()); | ||
| // Store generator's diagnostics for later emission, after setting the plugin that reported them. | ||
| for mut generator_diagnostic in generator_diagnostics.into_inner() { | ||
| generator_diagnostic.plugin = Some(generator.path.clone()); |
There was a problem hiding this comment.
Where we actually set plugin.
This is after we've finished (or tried) to run the plugin and are storing its diagnostics.
|
|
||
| // Construct and push the converted 'slicec' `Diagnostic`. | ||
| let converted_diagnostic = slicec::diagnostics::Diagnostic { kind, span, scope, notes }; | ||
| let converted_diagnostic = slicec::diagnostics::Diagnostic { kind, span, scope, notes, plugin: None }; |
There was a problem hiding this comment.
We construct the diagnostic in one-shot here, so had to update it to include the new field.
We manually set it to it's default value (None).
Rust doesn't have the idea of default field values like other languages.
| @@ -156,9 +180,13 @@ error [E008]: invalid enum 'E': enums must contain at least one enumerator | |||
| // Assert | |||
| assert_eq!(converted_diagnostics.len(), 4); | |||
| assert_eq!(converted_diagnostics[0].message, "This is a test"); | |||
There was a problem hiding this comment.
Check that the reported_bys are correct after merging.
There was a problem hiding this comment.
Pull request overview
This PR adds diagnostic provenance so emitted diagnostics indicate whether they came from slicec or one or more plugins, including JSON output support.
Changes:
- Adds plugin/reporting metadata to diagnostics and annotated diagnostics.
- Emits
Reported byin human output andreported_byin JSON output. - Updates diagnostic output tests and duplicate diagnostic merging behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
slicec/src/diagnostics/diagnostic.rs |
Adds optional plugin source tracking to core diagnostics. |
slicec/src/diagnostics/annotated_diagnostic.rs |
Converts plugin paths into displayed reporter names. |
slicec/src/compilation_state.rs |
Merges duplicate annotated diagnostics while aggregating reporters. |
slicec/src/diagnostic_emitter.rs |
Emits reporter information in human and JSON diagnostic formats. |
slicec/src/main.rs |
Tags plugin-generated diagnostics with the generator path. |
slicec/src/slice_file_converter.rs |
Initializes converted plugin response diagnostics with no direct plugin source. |
slicec/tests/diagnostic_output_tests.rs |
Updates expected output and duplicate merging tests for reporter metadata. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Any additional information that should be reported alongside this diagnostic's main message. | ||
| pub notes: Vec<Note>, | ||
|
|
||
| /// The plugin that reported this diagnostic, or `None` if this diagnostic was reported by 'slicec' itself. |
There was a problem hiding this comment.
We use the term plugin and not generator in slicec?
This PR adds a field to our Diagnostic types tracking which plugin (if any) a diagnostic came from.
In the case of multiple plugins reporting the same error, we still only emit a single error, but it's now more clear that it was reported by multiple plugins.
The main advantage to doing this is that errors about the plugin itself (like providing a bad option) will clearly identify which plugin had the problem.
Here is sample output from the console:

And for JSON output, we now include a new
"reported_by":[...]field.The Task will need to be updated to properly handle this field in a follow-up PR.