From 33cf2e5bcad869b408b87ec7924886dacf86a5be Mon Sep 17 00:00:00 2001 From: jumbatm Date: Mon, 16 Nov 2020 17:48:41 +1000 Subject: [PATCH 1/3] Document SessionDiagnostic --- src/SUMMARY.md | 1 + src/diagnostics.md | 4 ++ src/diagnostics/sessiondiagnostic.md | 97 ++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 src/diagnostics/sessiondiagnostic.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 41de14954..a94010eb9 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -127,6 +127,7 @@ - [Two-phase-borrows](./borrow_check/two_phase_borrows.md) - [Parameter Environments](./param_env.md) - [Errors and Lints](diagnostics.md) + - [Creating Errors With SessionDiagnostic](./diagnostics/sessiondiagnostic.md) - [`LintStore`](./diagnostics/lintstore.md) - [Diagnostic Codes](./diagnostics/diagnostic-codes.md) diff --git a/src/diagnostics.md b/src/diagnostics.md index 04745ee98..b2191086e 100644 --- a/src/diagnostics.md +++ b/src/diagnostics.md @@ -338,6 +338,10 @@ if let Ok(snippet) = sess.source_map().span_to_snippet(sp) { err.emit(); ``` +Alternatively, for less-complex diagnostics, the `SessionDiagnostic` derive +macro can be used -- see [Creating Errors With SessionDiagnostic](./diagnostics/sessiondiagnostic.md). + + ## Suggestions In addition to telling the user exactly _why_ their code is wrong, it's diff --git a/src/diagnostics/sessiondiagnostic.md b/src/diagnostics/sessiondiagnostic.md new file mode 100644 index 000000000..b64c62007 --- /dev/null +++ b/src/diagnostics/sessiondiagnostic.md @@ -0,0 +1,97 @@ +# Creating Errors With SessionDiagnostic + +The SessionDiagnostic derive macro gives an alternate way to the DiagnosticBuilder API for defining +and emitting errors. It allows a struct to be annotated with information which allows it to be +transformed and emitted as a Diagnostic. + +As an example, we'll take a look at how the "field already declared" diagnostic is actually defined +in the compiler (see the definition +[here](https://github.com/rust-lang/rust/blob/75042566d1c90d912f22e4db43b6d3af98447986/compiler/rustc_typeck/src/errors.rs#L65-L74) +and usage +[here](https://github.com/rust-lang/rust/blob/75042566d1c90d912f22e4db43b6d3af98447986/compiler/rustc_typeck/src/collect.rs#L863-L867)): + +```rust,ignore +#[derive(SessionDiagnostic)] +#[error = "E0124"] +pub struct FieldAlreadyDeclared { + pub field_name: Ident, + #[message = "field `{field_name}` is already declared"] + #[label = "field already declared"] + pub span: Span, + #[label = "`{field_name}` first declared here"] + pub prev_span: Span, +} +// ... +tcx.sess.emit_err(FieldAlreadyDeclared { + field_name: f.ident, + span: f.span, + prev_span, +}); +``` + +We see that using `SessionDiagnostic` is relatively straight forward. The `#[error = "..."]` +attribute is used to supply the error code for the diagnostic. We are then annotate fields in the +struct with various information of how to convert an instance of the struct into a proper +diagnostic. The attributes above produce code which is roughly equivalent to the following (in +pseudo-Rust): + +```rust,ignore +impl SessionDiagnostic for FieldAlreadyDeclared { + fn into_diagnostic(self, sess: &'_ rustc_session::Session) -> DiagnosticBuilder<'_> { + let mut diag = sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error("E0124")); + diag.set_span(self.span); + diag.set_primary_message(format!("field `{field_name}` is already declared", field_name = self.field_name)); + diag.span_label(self.span, "field already declared"); + diag.span_label(self.prev_span, format!("`{field_name}` first declared here", field_name = self.field_name)); + diag + } +} +``` + +The generated code draws attention to a number of features. First, we see that within the strings +passed to each attribute, we see that field names can be referenced without needing to be passed +explicitly into the format string -- in this example here, `#[message = "field {field_name} is +already declared"]` produces a call to `format!` with the appropriate arguments to format +`self.field_name` into the string. This applies to strings passed to all attributes. + +We also see that labelling `Span` fields in the struct produces calls which pass that `Span` to the +produced diagnostic. In the example above, we see that putting the `#[message = "..."]` attribute +on a `Span` leads to the primary span of the diagnostic being set to that `Span`, while applying the +`#[label = "..."]` attribute on a Span will simply set the span for that label. +Each attribute has different requirements for what they can be applied on, differing on position +(on the struct, or on a specific field), type (if it's applied on a field), and whether or not the +attribute is optional. + +## Attributes Listing + +Below is a listing of all the currently-available attributes that `#[derive(SessionDiagnostic)]` +understands: + +Attribute | Applied to | Mandatory | Behaviour +:-------------- | :-------------------- |:--------- | :--------- +`#[code = "..."]` | Struct | Yes | Sets the Diagnostic's error code +`#[message = "..."]` | Struct / `Span` fields | Yes | Sets the Diagnostic's primary message. If on `Span` field, also sets the Diagnostic's span. +`#[label = "..."]` | `Span` fields | No | Equivalent to calling `span_label` with that Span and message. +`#[suggestion(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion`. Note `code` is optional. +`#[suggestion_short(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_short`. Note `code` is optional. +`#[suggestion_hidden(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_hidden`. Note `code` is optional. +`#[suggestion_verbose(message = "..." , code = "..."]` | `(Span, MachineApplicability)` or `Span` fields | No | Equivalent to calling `span_suggestion_verbose`. Note `code` is optional. + + +## Optional Diagnostic Attributes + +There may be some cases where you want one of the decoration attributes to be applied optionally; +for example, if a suggestion can only be generated sometimes. In this case, simply wrap the field's +type in an `Option`. At runtime, if the field is set to `None`, the attribute for that field won't +be used in creating the diagnostic. For example: + +```rust,ignored +#[derive(SessionDiagnostic)] +#[code = "E0123"] +struct SomeKindOfError { + ... + #[suggestion(message = "informative error message")] + opt_sugg: Option<(Span, Applicability)> + ... +} +``` From 532d28be8958c1e0f0b7a1cb06caac14b3b82d4c Mon Sep 17 00:00:00 2001 From: jumbatm Date: Mon, 16 Nov 2020 17:48:41 +1000 Subject: [PATCH 2/3] Apply suggestions from review. Co-authored-by: Camelid --- src/diagnostics/sessiondiagnostic.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diagnostics/sessiondiagnostic.md b/src/diagnostics/sessiondiagnostic.md index b64c62007..4583e6d3d 100644 --- a/src/diagnostics/sessiondiagnostic.md +++ b/src/diagnostics/sessiondiagnostic.md @@ -30,8 +30,8 @@ tcx.sess.emit_err(FieldAlreadyDeclared { ``` We see that using `SessionDiagnostic` is relatively straight forward. The `#[error = "..."]` -attribute is used to supply the error code for the diagnostic. We are then annotate fields in the -struct with various information of how to convert an instance of the struct into a proper +attribute is used to supply the error code for the diagnostic. We then annotate fields in the +struct with various information on how to convert an instance of the struct into a rendered diagnostic. The attributes above produce code which is roughly equivalent to the following (in pseudo-Rust): @@ -49,7 +49,7 @@ impl SessionDiagnostic for FieldAlreadyDeclared { ``` The generated code draws attention to a number of features. First, we see that within the strings -passed to each attribute, we see that field names can be referenced without needing to be passed +passed to each attribute, field names can be referenced without needing to be passed explicitly into the format string -- in this example here, `#[message = "field {field_name} is already declared"]` produces a call to `format!` with the appropriate arguments to format `self.field_name` into the string. This applies to strings passed to all attributes. From 01be82c1ac7574593b92687d383c13eee30a09b9 Mon Sep 17 00:00:00 2001 From: jumbatm Date: Mon, 16 Nov 2020 17:48:41 +1000 Subject: [PATCH 3/3] Use shorter [][]-style link to not pass 100 chars. --- src/diagnostics.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/diagnostics.md b/src/diagnostics.md index b2191086e..978ef3988 100644 --- a/src/diagnostics.md +++ b/src/diagnostics.md @@ -339,7 +339,9 @@ err.emit(); ``` Alternatively, for less-complex diagnostics, the `SessionDiagnostic` derive -macro can be used -- see [Creating Errors With SessionDiagnostic](./diagnostics/sessiondiagnostic.md). +macro can be used -- see [Creating Errors With SessionDiagnostic][sessiondiagnostic]. + +[sessiondiagnostic]: ./diagnostics/sessiondiagnostic.md ## Suggestions