Skip to content

Commit 8997e5b

Browse files
committed
Fix rust-lang#5489 - allow CONST and macrro metavariable as attribute value
1 parent 38659ec commit 8997e5b

File tree

4 files changed

+455
-28
lines changed

4 files changed

+455
-28
lines changed

src/attr.rs

Lines changed: 183 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
//! Format attributes and meta items.
22
33
use rustc_ast::ast;
4+
use rustc_ast::ast::{AttrKind, Lit, MetaItem, MetaItemKind, StrStyle};
5+
use rustc_ast::token;
6+
use rustc_ast::token::{Token, TokenKind};
7+
use rustc_ast::tokenstream::TokenStream;
8+
use rustc_ast::tokenstream::TokenTree;
9+
use rustc_ast::AttrItem;
410
use rustc_ast::HasAttrs;
11+
use rustc_ast::MacArgs;
12+
use rustc_ast::MacArgsEq;
13+
use rustc_ast::MacArgsEq::{Ast, Hir};
14+
use rustc_ast::NestedMetaItem;
15+
use rustc_ast::Path;
516
use rustc_span::{symbol::sym, Span, Symbol};
617

718
use self::doc_comment::DocCommentFormatter;
@@ -329,6 +340,137 @@ impl Rewrite for ast::MetaItem {
329340
}
330341
}
331342

343+
// A simple workaround for `ast::Attribute::meta()` for generating MetaItem from AttrItem that
344+
// includes constants (`CONST`) or macro metavariables (`$name`) as attribute value (issue #5489).
345+
fn meta_from_attr_item(
346+
item: &AttrItem,
347+
context: &RewriteContext<'_>,
348+
shape: Shape,
349+
) -> Option<MetaItem> {
350+
let meta = match &item.args {
351+
MacArgs::Delimited(_, _, token_stream) => meta_from_delimited_attr(token_stream.clone()),
352+
MacArgs::Eq(_, mac_args_eq) => meta_from_eq_attr(mac_args_eq, context, shape),
353+
MacArgs::Empty => None, // Should not get here - properly handled by `Attribute::meta()`
354+
};
355+
356+
match meta {
357+
Some(meta) => Some(MetaItem {
358+
path: item.clone().path,
359+
kind: meta,
360+
span: item.span(),
361+
}),
362+
None => None,
363+
}
364+
} // meta_from_item()
365+
366+
// Generating `MetaItemKind` from attribute `MacArgsEq`.
367+
fn meta_from_eq_attr(
368+
mac_args_eq: &MacArgsEq,
369+
context: &RewriteContext<'_>,
370+
shape: Shape,
371+
) -> Option<rustc_ast::MetaItemKind> {
372+
let lit = match mac_args_eq {
373+
Hir(lit) => lit.clone(),
374+
Ast(expr) => {
375+
let lit_symbol = Symbol::intern(&expr.rewrite(context, shape)?);
376+
Lit {
377+
token: token::Lit::new(token::Str, lit_symbol, None),
378+
kind: rustc_ast::LitKind::Str(lit_symbol, StrStyle::Cooked),
379+
span: expr.span,
380+
}
381+
}
382+
};
383+
384+
Some(MetaItemKind::NameValue(lit.clone()))
385+
} // meta_from_eq_attr()
386+
387+
// Generating `MetaItemKind` from `Delimited` attribute `TokenStream``, assiming the tokens are
388+
// gouped into 3-tokens groups, followed by an optional `,`. The 3 grop tokens are assumed to be:
389+
// Ident `=` Ident or Lit
390+
fn meta_from_delimited_attr(token_stream: TokenStream) -> Option<rustc_ast::MetaItemKind> {
391+
let mut token_count = token_stream.len();
392+
if token_count < 3 {
393+
return None;
394+
}
395+
let mut trees = token_stream.into_trees();
396+
let mut meta_items_list: Vec<NestedMetaItem> = Vec::new();
397+
398+
// Closure t get the next token from a TokenTree
399+
let mut next_token = || -> Option<Token> {
400+
if let TokenTree::Token(token, _) = trees.next()? {
401+
Some(token)
402+
} else {
403+
None
404+
}
405+
}; // next_token() = ||
406+
407+
// Loop over the 3-tokens groups of the ToekenTree
408+
while token_count >= 3 {
409+
// 1st token is the lhs Ident
410+
let attr_name_token = next_token()?;
411+
if let TokenKind::Ident(attr_name_sym, _) = attr_name_token.kind {
412+
// 2nd token is the "="
413+
if !matches!(next_token()?.kind, TokenKind::Eq) {
414+
return None;
415+
} else {
416+
// 3rd token is the rhs Ident/Lit
417+
let token = next_token()?;
418+
token_count -= 3;
419+
if token_count % 4 == 1 {
420+
// Only comma or next 3-tokens group can follow
421+
if !matches!(next_token()?.kind, TokenKind::Comma) {
422+
return None;
423+
}
424+
token_count -= 1;
425+
}
426+
427+
// rhs can be either Lit or Ident
428+
let lit = match token.kind {
429+
TokenKind::Literal(lit) => {
430+
let lit_symbol = Symbol::intern(&lit.to_string());
431+
Lit {
432+
token: lit,
433+
kind: rustc_ast::LitKind::Str(lit_symbol, StrStyle::Cooked),
434+
span: token.span,
435+
}
436+
}
437+
TokenKind::Ident(_, _) => {
438+
let ident_name = if let TokenKind::Ident(name, _) = token.kind {
439+
name.to_ident_string()
440+
} else {
441+
"".to_string()
442+
};
443+
let lit_symbol = Symbol::intern(&ident_name);
444+
Lit {
445+
token: token::Lit::new(token::Str, lit_symbol, None),
446+
kind: rustc_ast::LitKind::Str(lit_symbol, StrStyle::Cooked),
447+
span: token.span,
448+
}
449+
}
450+
_ => return None,
451+
};
452+
453+
let attr_name_ident = rustc_span::symbol::Ident {
454+
name: attr_name_sym,
455+
span: attr_name_token.span,
456+
};
457+
458+
let meta_item = MetaItem {
459+
path: Path::from_ident(attr_name_ident),
460+
kind: MetaItemKind::NameValue(lit.clone()),
461+
span: lit.span,
462+
};
463+
464+
meta_items_list.push(NestedMetaItem::MetaItem(meta_item));
465+
}
466+
} else {
467+
return None;
468+
}
469+
} // while
470+
471+
return Some(MetaItemKind::List(meta_items_list));
472+
} // meta_from_delimited_attr()
473+
332474
impl Rewrite for ast::Attribute {
333475
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
334476
let snippet = context.snippet(self.span);
@@ -345,35 +487,49 @@ impl Rewrite for ast::Attribute {
345487
return Some(snippet.to_owned());
346488
}
347489

348-
if let Some(ref meta) = self.meta() {
349-
// This attribute is possibly a doc attribute needing normalization to a doc comment
350-
if context.config.normalize_doc_attributes() && meta.has_name(sym::doc) {
351-
if let Some(ref literal) = meta.value_str() {
352-
let comment_style = match self.style {
353-
ast::AttrStyle::Inner => CommentStyle::Doc,
354-
ast::AttrStyle::Outer => CommentStyle::TripleSlash,
355-
};
356-
357-
let literal_str = literal.as_str();
358-
let doc_comment_formatter =
359-
DocCommentFormatter::new(literal_str, comment_style);
360-
let doc_comment = format!("{}", doc_comment_formatter);
361-
return rewrite_doc_comment(
362-
&doc_comment,
363-
shape.comment(context.config),
364-
context.config,
365-
);
490+
let meta = match self.meta() {
491+
Some(meta) => Some(meta),
492+
None => match self.kind {
493+
// FIXME: `ast::Attribute::meta()` should be fixed to allow CONST and
494+
// macro metavariables (`$name`) as valid attributes values.
495+
//
496+
// A simplified workaround for `ast::Attribute::meta()`that does not
497+
// allows CONST and macro metavariables (`$name`) as attributes values.
498+
AttrKind::Normal(ref item, _) => meta_from_attr_item(&item, context, shape),
499+
_ => None,
500+
},
501+
};
502+
503+
match meta {
504+
Some(ref meta) => {
505+
// This attribute is possibly a doc attr needing normalization to a doc comment
506+
if context.config.normalize_doc_attributes() && meta.has_name(sym::doc) {
507+
if let Some(ref literal) = meta.value_str() {
508+
let comment_style = match self.style {
509+
ast::AttrStyle::Inner => CommentStyle::Doc,
510+
ast::AttrStyle::Outer => CommentStyle::TripleSlash,
511+
};
512+
513+
let literal_str = literal.as_str();
514+
let doc_comment_formatter =
515+
DocCommentFormatter::new(literal_str, comment_style);
516+
let doc_comment = format!("{}", doc_comment_formatter);
517+
return rewrite_doc_comment(
518+
&doc_comment,
519+
shape.comment(context.config),
520+
context.config,
521+
);
522+
}
366523
}
367-
}
368524

369-
// 1 = `[`
370-
let shape = shape.offset_left(prefix.len() + 1)?;
371-
Some(
372-
meta.rewrite(context, shape)
373-
.map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)),
374-
)
375-
} else {
376-
Some(snippet.to_owned())
525+
// 1 = `[`
526+
let shape = shape.offset_left(prefix.len() + 1)?;
527+
Some(
528+
meta.rewrite(context, shape)
529+
.map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)),
530+
)
531+
}
532+
None => Some(snippet.to_owned()),
377533
}
378534
}
379535
}

tests/source/issue-5489.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// rustfmt-format_macro_matchers: true
2+
3+
// Original issue #5489 case - macro metavariable ($name) as attribute value.
4+
use clap::Parser;
5+
6+
macro_rules! generate_the_struct {
7+
($a:literal) => {
8+
#[derive(Parser)]
9+
pub struct Struct {
10+
#[clap(
11+
long = "--alpha-beta-gamma",
12+
env = "ALPHA_BETA_GAMMA",
13+
default_value = $a,
14+
)]
15+
alpha_beta_gamma: usize,
16+
}
17+
};
18+
}
19+
20+
fn main() {
21+
generate_the_struct!("77777");
22+
let s = Struct { alpha_beta_gamma: 66666 };
23+
println!("{}", s.alpha_beta_gamma);
24+
}
25+
26+
//
27+
// Other case with macro metavariables ($name) as attribute value.
28+
macro_rules! generate_the_struct {
29+
( $a:literal ) => {
30+
#[derive(Parser)]
31+
pub struct Struct {
32+
#[ clap(
33+
long = "--alpha-beta-gamma",
34+
env = "ALPHA_BETA_GAMMA",
35+
default_value = $a,
36+
)]
37+
alpha_beta_gamma: usize,
38+
}
39+
};
40+
}
41+
42+
macro_rules! generate_the_struct {
43+
($bbbbb:literal) => {
44+
#[derive(Parser)]
45+
pub struct Struct {
46+
#[ clap(
47+
default_value( $bbbbb )
48+
)]
49+
aaaaa: str,
50+
}
51+
};
52+
}
53+
54+
macro_rules! generate_the_struct {
55+
( $bbbbb:literal , $ccccc:literal ) => {
56+
#[ clap(
57+
default_value = $bbbbb , other_value = $ccccc ,
58+
) ]
59+
const aaaaa: str;
60+
};
61+
}
62+
63+
macro_rules! generate_the_struct {
64+
( $bbbbb:literal , $ccccc:literal ) => {
65+
#[ clap(
66+
default_value = $bbbbb , other_value = $ccccc , lit = "Lit" ,
67+
) ]
68+
const aaaaa: str;
69+
};
70+
}
71+
72+
//
73+
// Cases with COSNT as attribute value.
74+
const CONSTVAR: usize = 8;
75+
#[ clap(
76+
default_value = CONSTVAR
77+
)]
78+
const aaaaa: str;
79+
80+
macro_rules! generate_the_struct {
81+
($a:literal) => {
82+
#[ derive(Parser)]
83+
pub struct Struct {
84+
#[ clap(
85+
long = "--alpha-beta-gamma",
86+
env = CONSTVAR,
87+
)]
88+
alpha_beta_gamma: usize,
89+
}
90+
};
91+
}
92+
93+
macro_rules! generate_the_struct {
94+
($a:literal) => {
95+
#[derive(Parser)]
96+
pub struct Struct {
97+
#[ clap(
98+
long = "--alpha-beta-gamma",
99+
env = CONSTVAR,
100+
default_value = $a,
101+
)]
102+
alpha_beta_gamma: usize,
103+
}
104+
};
105+
}
106+
107+
//
108+
// Variations of #2470 from `macro_rules.rs`.
109+
macro foo($type_name: ident, $docs: expr) {
110+
#[ doc= "Lit" ]
111+
pub struct $type_name;
112+
}
113+
114+
macro foo($type_name: ident, $docs: expr) {
115+
# [ doc = $docs ]
116+
pub struct $type_name;
117+
}
118+
119+
macro foo($type_name: ident, $docs: expr) {
120+
# [ $docs ]
121+
pub struct $type_name;
122+
}
123+
124+
//
125+
// Cases with no macro metavariable ($name) or CONST as attribute value
126+
macro_rules! generate_the_struct {
127+
($a:literal) => {
128+
#[derive(Parser)]
129+
pub struct Struct {
130+
#[ clap(
131+
long = "--alpha-beta-gamma",
132+
env = "ALPHA_BETA_GAMMA",
133+
default_value = "Lit",
134+
)]
135+
alpha_beta_gamma: usize,
136+
}
137+
};
138+
}
139+
140+
#[ clap(
141+
default_value = "Lit"
142+
)]
143+
const aaaaa: str;

0 commit comments

Comments
 (0)