@@ -13,7 +13,7 @@ use quote::{format_ident, quote};
1313use std:: collections:: HashMap ;
1414use std:: fmt;
1515use std:: str:: FromStr ;
16- use syn:: { spanned:: Spanned , Meta , MetaList , MetaNameValue } ;
16+ use syn:: { parse_quote , spanned:: Spanned , Meta , MetaList , MetaNameValue , NestedMeta , Path } ;
1717use synstructure:: { BindingInfo , Structure , VariantInfo } ;
1818
1919/// Which kind of suggestion is being created?
@@ -194,8 +194,8 @@ struct SessionSubdiagnosticDeriveBuilder<'a> {
194194 kind : Option < ( SubdiagnosticKind , proc_macro:: Span ) > ,
195195
196196 /// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the
197- /// `#[kind(slug = "..." )]` attribute on the type or variant.
198- slug : Option < ( String , proc_macro:: Span ) > ,
197+ /// `#[kind(slug)]` attribute on the type or variant.
198+ slug : Option < ( Path , proc_macro:: Span ) > ,
199199 /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]`
200200 /// attribute on the type or variant.
201201 code : Option < ( TokenStream , proc_macro:: Span ) > ,
@@ -224,9 +224,34 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
224224 let meta = attr. parse_meta ( ) ?;
225225 let kind = match meta {
226226 Meta :: List ( MetaList { ref nested, .. } ) => {
227- for nested_attr in nested {
227+ let mut nested_iter = nested. into_iter ( ) ;
228+ if let Some ( nested_attr) = nested_iter. next ( ) {
229+ match nested_attr {
230+ NestedMeta :: Meta ( Meta :: Path ( path) ) => {
231+ self . slug . set_once ( ( path. clone ( ) , span) ) ;
232+ }
233+ NestedMeta :: Meta ( meta @ Meta :: NameValue ( _) )
234+ if matches ! (
235+ meta. path( ) . segments. last( ) . unwrap( ) . ident. to_string( ) . as_str( ) ,
236+ "code" | "applicability"
237+ ) =>
238+ {
239+ // don't error for valid follow-up attributes
240+ }
241+ nested_attr => {
242+ throw_invalid_nested_attr ! ( attr, & nested_attr, |diag| {
243+ diag. help(
244+ "first argument of the attribute should be the diagnostic \
245+ slug",
246+ )
247+ } )
248+ }
249+ } ;
250+ }
251+
252+ for nested_attr in nested_iter {
228253 let meta = match nested_attr {
229- syn :: NestedMeta :: Meta ( ref meta) => meta,
254+ NestedMeta :: Meta ( ref meta) => meta,
230255 _ => throw_invalid_nested_attr ! ( attr, & nested_attr) ,
231256 } ;
232257
@@ -241,7 +266,6 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
241266 let formatted_str = self . build_format ( & s. value ( ) , s. span ( ) ) ;
242267 self . code . set_once ( ( formatted_str, span) ) ;
243268 }
244- "slug" => self . slug . set_once ( ( s. value ( ) , span) ) ,
245269 "applicability" => {
246270 let value = match Applicability :: from_str ( & s. value ( ) ) {
247271 Ok ( v) => v,
@@ -253,11 +277,23 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
253277 self . applicability . set_once ( ( quote ! { #value } , span) ) ;
254278 }
255279 _ => throw_invalid_nested_attr ! ( attr, & nested_attr, |diag| {
256- diag. help( "only `code`, `slug` and `applicability` are valid nested attributes" )
280+ diag. help(
281+ "only `code` and `applicability` are valid nested \
282+ attributes",
283+ )
257284 } ) ,
258285 }
259286 }
260- _ => throw_invalid_nested_attr ! ( attr, & nested_attr) ,
287+ _ => throw_invalid_nested_attr ! ( attr, & nested_attr, |diag| {
288+ if matches!( meta, Meta :: Path ( _) ) {
289+ diag. help(
290+ "a diagnostic slug must be the first argument to the \
291+ attribute",
292+ )
293+ } else {
294+ diag
295+ }
296+ } ) ,
261297 }
262298 }
263299
@@ -281,10 +317,27 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
281317 ) ;
282318 }
283319
320+ if matches ! (
321+ kind,
322+ SubdiagnosticKind :: Label | SubdiagnosticKind :: Help | SubdiagnosticKind :: Note
323+ ) && self . applicability . is_some ( )
324+ {
325+ throw_span_err ! (
326+ span,
327+ & format!(
328+ "`applicability` is not a valid nested attribute of a `{}` attribute" ,
329+ name
330+ )
331+ ) ;
332+ }
333+
284334 if self . slug . is_none ( ) {
285335 throw_span_err ! (
286336 span,
287- & format!( "`slug` must be set in a `#[{}(...)]` attribute" , name)
337+ & format!(
338+ "diagnostic slug must be first argument of a `#[{}(...)]` attribute" ,
339+ name
340+ )
288341 ) ;
289342 }
290343
@@ -335,7 +388,10 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
335388 return Ok ( quote ! { } ) ;
336389 }
337390 _ => throw_invalid_attr ! ( attr, & meta, |diag| {
338- diag. help( "only `primary_span`, `applicability` and `skip_arg` are valid field attributes" )
391+ diag. help(
392+ "only `primary_span`, `applicability` and `skip_arg` are valid field \
393+ attributes",
394+ )
339395 } ) ,
340396 } ,
341397 _ => throw_invalid_attr ! ( attr, & meta) ,
@@ -375,7 +431,11 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
375431 }
376432
377433 // Missing slug errors will already have been reported.
378- let slug = self . slug . as_ref ( ) . map ( |( slug, _) | & * * slug) . unwrap_or ( "missing-slug" ) ;
434+ let slug = self
435+ . slug
436+ . as_ref ( )
437+ . map ( |( slug, _) | slug. clone ( ) )
438+ . unwrap_or_else ( || parse_quote ! { you:: need:: to:: specify:: a:: slug } ) ;
379439 let code = match self . code . as_ref ( ) {
380440 Some ( ( code, _) ) => Some ( quote ! { #code } ) ,
381441 None if is_suggestion => {
@@ -397,7 +457,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
397457
398458 let diag = & self . diag ;
399459 let name = format_ident ! ( "{}{}" , if span_field. is_some( ) { "span_" } else { "" } , kind) ;
400- let message = quote ! { rustc_errors:: SubdiagnosticMessage :: message ( #slug) } ;
460+ let message = quote ! { rustc_errors:: fluent :: #slug } ;
401461 let call = if matches ! ( kind, SubdiagnosticKind :: Suggestion ( ..) ) {
402462 if let Some ( span) = span_field {
403463 quote ! { #diag. #name( #span, #message, #code, #applicability) ; }
0 commit comments