@@ -13,7 +13,7 @@ use quote::{format_ident, quote};
13
13
use std:: collections:: HashMap ;
14
14
use std:: fmt;
15
15
use std:: str:: FromStr ;
16
- use syn:: { spanned:: Spanned , Meta , MetaList , MetaNameValue } ;
16
+ use syn:: { parse_quote , spanned:: Spanned , Meta , MetaList , MetaNameValue , NestedMeta , Path } ;
17
17
use synstructure:: { BindingInfo , Structure , VariantInfo } ;
18
18
19
19
/// Which kind of suggestion is being created?
@@ -194,8 +194,8 @@ struct SessionSubdiagnosticDeriveBuilder<'a> {
194
194
kind : Option < ( SubdiagnosticKind , proc_macro:: Span ) > ,
195
195
196
196
/// 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 ) > ,
199
199
/// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]`
200
200
/// attribute on the type or variant.
201
201
code : Option < ( TokenStream , proc_macro:: Span ) > ,
@@ -224,9 +224,34 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
224
224
let meta = attr. parse_meta ( ) ?;
225
225
let kind = match meta {
226
226
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 {
228
253
let meta = match nested_attr {
229
- syn :: NestedMeta :: Meta ( ref meta) => meta,
254
+ NestedMeta :: Meta ( ref meta) => meta,
230
255
_ => throw_invalid_nested_attr ! ( attr, & nested_attr) ,
231
256
} ;
232
257
@@ -241,7 +266,6 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
241
266
let formatted_str = self . build_format ( & s. value ( ) , s. span ( ) ) ;
242
267
self . code . set_once ( ( formatted_str, span) ) ;
243
268
}
244
- "slug" => self . slug . set_once ( ( s. value ( ) , span) ) ,
245
269
"applicability" => {
246
270
let value = match Applicability :: from_str ( & s. value ( ) ) {
247
271
Ok ( v) => v,
@@ -253,11 +277,23 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
253
277
self . applicability . set_once ( ( quote ! { #value } , span) ) ;
254
278
}
255
279
_ => 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
+ )
257
284
} ) ,
258
285
}
259
286
}
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
+ } ) ,
261
297
}
262
298
}
263
299
@@ -281,10 +317,27 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
281
317
) ;
282
318
}
283
319
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
+
284
334
if self . slug . is_none ( ) {
285
335
throw_span_err ! (
286
336
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
+ )
288
341
) ;
289
342
}
290
343
@@ -335,7 +388,10 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
335
388
return Ok ( quote ! { } ) ;
336
389
}
337
390
_ => 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
+ )
339
395
} ) ,
340
396
} ,
341
397
_ => throw_invalid_attr ! ( attr, & meta) ,
@@ -375,7 +431,11 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
375
431
}
376
432
377
433
// 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 } ) ;
379
439
let code = match self . code . as_ref ( ) {
380
440
Some ( ( code, _) ) => Some ( quote ! { #code } ) ,
381
441
None if is_suggestion => {
@@ -397,7 +457,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
397
457
398
458
let diag = & self . diag ;
399
459
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 } ;
401
461
let call = if matches ! ( kind, SubdiagnosticKind :: Suggestion ( ..) ) {
402
462
if let Some ( span) = span_field {
403
463
quote ! { #diag. #name( #span, #message, #code, #applicability) ; }
0 commit comments