Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ impl Rewrite for ast::MetaItem {
}
}

pub(crate) mod meta2;
impl Rewrite for ast::Attribute {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
Expand All @@ -342,7 +343,7 @@ impl Rewrite for ast::Attribute {
return Ok(snippet.to_owned());
}

if let Some(ref meta) = self.meta() {
if let Some(meta) = meta2::MetaItem2::from_attr(self, context) {
// This attribute is possibly a doc attribute needing normalization to a doc comment
if context.config.normalize_doc_attributes() && meta.has_name(sym::doc) {
if let Some(ref literal) = meta.value_str() {
Expand Down
243 changes: 243 additions & 0 deletions src/attr/meta2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
use rustc_ast::ptr::P;
use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_ast::{ast, token};
use rustc_parse::exp;
use rustc_parse::parser::Parser;
use rustc_session::parse::ParseSess;
use rustc_span::Span;

use crate::config::lists::SeparatorTactic;
use crate::expr::rewrite_literal;
use crate::overflow;
use crate::rewrite::{Rewrite, RewriteContext, RewriteResult};
use crate::shape::Shape;
use crate::spanned::Spanned;
use crate::types::{PathContext, rewrite_path};

fn is_eof(token: &token::Token) -> bool {
matches!(
token,
rustc_ast::token::Token {
kind: rustc_ast::token::TokenKind::Eof,
..
}
)
}
fn try_parse<'a, T>(
parser: &mut Parser<'a>,
parse: impl FnOnce(&mut Parser<'a>) -> rustc_errors::PResult<'a, Option<T>>,
) -> Option<T> {
let mut fork = parser.clone();
match parse(&mut fork) {
Ok(x) => match parser.psess.dcx().has_errors() {
Some(_) => {
parser.psess.dcx().reset_err_count();
None
}
None => match x {
Some(x) => {
*parser = fork;
Some(x)
}
None => None,
},
},
Err(e) => {
e.cancel();
parser.psess.dcx().reset_err_count();
None
}
}
}

#[derive(Debug)]
pub(crate) struct MetaItem2 {
#[allow(dead_code)] // not used here, but part of the ast copied over from rustc
pub unsafety: ast::Safety,
pub path: ast::Path,
pub kind: MetaItemKind2,
pub span: Span,
}
impl MetaItem2 {
pub(crate) fn from_attr(attr: &ast::Attribute, context: &RewriteContext<'_>) -> Option<Self> {
match &attr.kind {
ast::AttrKind::Normal(normal) => {
let _guard = context.enter_macro();
Self::from_attr_item(&normal.item, context.psess.inner())
}
ast::AttrKind::DocComment(..) => None,
}
}
fn from_attr_item(attr: &ast::AttrItem, sess: &ParseSess) -> Option<Self> {
Some(Self {
kind: MetaItemKind2::from_attr_args(&attr.args, sess)?,
unsafety: attr.unsafety,
path: attr.path.clone(),
span: attr.span(),
})
}
pub(crate) fn has_name(&self, name: rustc_span::Symbol) -> bool {
self.path == name
}
pub(crate) fn value_str(&self) -> Option<rustc_span::Symbol> {
if let MetaItemKind2::NameValue(expr) = &self.kind {
if let ast::Expr {
kind: ast::ExprKind::Lit(token_lit),
..
} = &**expr
{
return ast::LitKind::from_token_lit(*token_lit)
.ok()
.and_then(|it| it.str());
}
}
None
}
}

#[derive(Debug)]
pub(crate) enum MetaItemKind2 {
Word,
List(Vec<MetaItemInner2>),
NameValue(P<ast::Expr>),
}
impl MetaItemKind2 {
fn from_attr_args(args: &ast::AttrArgs, sess: &ParseSess) -> Option<Self> {
match args {
ast::AttrArgs::Empty => Some(Self::Word),
ast::AttrArgs::Delimited(ast::DelimArgs {
dspan: _,
delim: token::Delimiter::Parenthesis,
tokens,
}) => Self::list_from_tokens(tokens.clone(), sess).map(Self::List),
ast::AttrArgs::Delimited(..) => None,
ast::AttrArgs::Eq { expr, .. } => Some(Self::NameValue(expr.clone())),
}
}

fn list_from_tokens(tokens: TokenStream, sess: &ParseSess) -> Option<Vec<MetaItemInner2>> {
let mut parser = Parser::new(sess, tokens, None);
let mut result = Vec::new();
while !is_eof(&parser.token) {
let eat_opt_comma =
|parser: &mut Parser<'_>| parser.eat(exp!(Eof)) || parser.eat(exp!(Comma));
let inner = try_parse(&mut parser, |parser| {
Ok(MetaItemInner2::parse_noexpr(parser).take_if(|_| eat_opt_comma(parser)))
})
.or_else(|| {
try_parse(&mut parser, |parser| {
let expr = MetaItemInner2::Expr(parser.parse_expr()?);
Ok(eat_opt_comma(parser).then_some(expr))
})
});
result.push(inner?);
}
Some(result)
}
}

#[derive(Debug)]
pub(crate) enum MetaItemInner2 {
MetaItem(MetaItem2),
Lit(ast::MetaItemLit),
Expr(P<ast::Expr>),
}

impl MetaItemInner2 {
fn parse_noexpr(parser: &mut Parser<'_>) -> Option<Self> {
if let Some(lit) = ast::MetaItemLit::from_token(&parser.token) {
parser.bump();
Some(Self::Lit(lit))
} else if let token::TokenKind::OpenDelim(token::Delimiter::Invisible(_)) =
&parser.token.kind
{
if let TokenTree::Delimited(.., token::Delimiter::Invisible(_), inner) =
parser.parse_token_tree()
{
Self::parse_noexpr(&mut Parser::new(parser.psess, inner, None))
} else {
None
}
} else {
try_parse(parser, |fork| {
let item = fork.parse_attr_item(rustc_parse::parser::ForceCollect::No)?;
Ok(MetaItem2::from_attr_item(&item, parser.psess).map(Self::MetaItem))
})
}
}
}

impl Rewrite for MetaItem2 {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}

fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
Ok(match self.kind {
MetaItemKind2::Word => {
rewrite_path(context, PathContext::Type, &None, &self.path, shape)?
}
MetaItemKind2::List(ref list) => {
let path = rewrite_path(context, PathContext::Type, &None, &self.path, shape)?;
let has_trailing_comma = crate::expr::span_ends_with_comma(context, self.span);
overflow::rewrite_with_parens(
context,
&path,
list.iter(),
// 1 = "]"
shape.sub_width(1, self.span)?,
self.span,
context.config.attr_fn_like_width(),
Some(if has_trailing_comma {
SeparatorTactic::Always
} else {
SeparatorTactic::Never
}),
)?
}
MetaItemKind2::NameValue(ref expr) => {
let path = rewrite_path(context, PathContext::Type, &None, &self.path, shape)?;
// 3 = ` = `
let lit_shape = shape.shrink_left(path.len() + 3, self.span)?;
let value = match expr.kind {
ast::ExprKind::Lit(ref lit) => {
// `rewrite_literal` returns `None` when `lit` exceeds max
// width. Since a literal is basically unformattable unless it
// is a string literal (and only if `format_strings` is set),
// we might be better off ignoring the fact that the attribute
// is longer than the max width and continue on formatting.
// See #2479 for example.
rewrite_literal(context, *lit, expr.span, lit_shape)
.unwrap_or_else(|_| context.snippet(expr.span).to_owned())
}
_ => expr.rewrite_result(context, lit_shape)?,
};
format!("{path} = {value}")
}
})
}
}

impl Spanned for MetaItemInner2 {
fn span(&self) -> Span {
match self {
Self::MetaItem(meta) => meta.span,
Self::Lit(lit) => lit.span,
Self::Expr(expr) => expr.span(),
}
}
}

impl Rewrite for MetaItemInner2 {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
self.rewrite_result(context, shape).ok()
}

fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
match self {
Self::MetaItem(ref meta_item) => meta_item.rewrite_result(context, shape),
Self::Lit(ref l) => rewrite_literal(context, l.as_token_lit(), l.span, shape),
Self::Expr(ref expr) => expr.rewrite_result(context, shape),
}
}
}
7 changes: 1 addition & 6 deletions src/cargo-fmt/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,7 @@ pub struct Opts {
version: bool,

/// Specify package to format
#[arg(
short = 'p',
long = "package",
value_name = "package",
num_args = 1..
)]
#[arg(short = 'p', long = "package", value_name = "package", num_args = 1..)]
packages: Vec<String>,

/// Specify path to Cargo.toml
Expand Down
22 changes: 21 additions & 1 deletion src/overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use crate::spanned::Spanned;
use crate::types::{SegmentParam, can_be_overflowed_type};
use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp};

use crate::attr::meta2::MetaItemInner2;

/// A list of `format!`-like macros, that take a long format string and a list of arguments to
/// format.
///
Expand Down Expand Up @@ -79,6 +81,7 @@ pub(crate) enum OverflowableItem<'a> {
GenericParam(&'a ast::GenericParam),
MacroArg(&'a MacroArg),
MetaItemInner(&'a ast::MetaItemInner),
MetaItemInner2(&'a crate::attr::meta2::MetaItemInner2),
SegmentParam(&'a SegmentParam<'a>),
FieldDef(&'a ast::FieldDef),
TuplePatField(&'a TuplePatField<'a>),
Expand Down Expand Up @@ -124,6 +127,7 @@ impl<'a> OverflowableItem<'a> {
OverflowableItem::GenericParam(gp) => f(*gp),
OverflowableItem::MacroArg(macro_arg) => f(*macro_arg),
OverflowableItem::MetaItemInner(nmi) => f(*nmi),
OverflowableItem::MetaItemInner2(nmi) => f(*nmi),
OverflowableItem::SegmentParam(sp) => f(*sp),
OverflowableItem::FieldDef(sf) => f(*sf),
OverflowableItem::TuplePatField(pat) => f(*pat),
Expand All @@ -144,6 +148,12 @@ impl<'a> OverflowableItem<'a> {
matches!(meta_item.kind, ast::MetaItemKind::Word)
}
},
OverflowableItem::MetaItemInner2(meta_item_inner) => match meta_item_inner {
MetaItemInner2::Lit(..) | MetaItemInner2::Expr(..) => true,
MetaItemInner2::MetaItem(ref meta_item) => {
matches!(meta_item.kind, crate::attr::meta2::MetaItemKind2::Word)
}
},
// FIXME: Why don't we consider `SegmentParam` to be simple?
// FIXME: If we also fix `SegmentParam`, then we should apply the same
// heuristic to `PreciseCapturingArg`.
Expand Down Expand Up @@ -188,6 +198,12 @@ impl<'a> OverflowableItem<'a> {
ast::MetaItemInner::Lit(..) => false,
ast::MetaItemInner::MetaItem(..) => true,
},
OverflowableItem::MetaItemInner2(meta_item_inner) if len == 1 => {
match meta_item_inner {
MetaItemInner2::Lit(..) | MetaItemInner2::Expr(..) => false,
MetaItemInner2::MetaItem(..) => true,
}
}
OverflowableItem::SegmentParam(SegmentParam::Type(ty)) => {
can_be_overflowed_type(context, ty, len)
}
Expand All @@ -201,6 +217,7 @@ impl<'a> OverflowableItem<'a> {
let base_cases = match self {
OverflowableItem::MacroArg(..) => SPECIAL_CASE_MACROS,
OverflowableItem::MetaItemInner(..) => SPECIAL_CASE_ATTR,
OverflowableItem::MetaItemInner2(..) => SPECIAL_CASE_ATTR,
_ => &[],
};
let additional_cases = match self {
Expand Down Expand Up @@ -265,7 +282,10 @@ impl_into_overflowable_item_for_ast_node!(
Pat,
PreciseCapturingArg
);
impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]);
impl_into_overflowable_item_for_rustfmt_types!(
[MacroArg, MetaItemInner2],
[SegmentParam, TuplePatField]
);

pub(crate) fn into_overflowable_list<'a, T>(
iter: impl Iterator<Item = &'a T>,
Expand Down
2 changes: 1 addition & 1 deletion tests/cargo-fmt/source/issue_3164/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ macro_rules! foo {
($id:ident) => {
macro_rules! bar {
($id2:tt) => {
#[cfg(any(target_feature = $id2, target_feature = $id2, target_feature = $id2, target_feature = $id2, target_feature = $id2))]
#[cfg(any(target_feature = $id2, target_feature = $id2, target_feature = $id2, target_feature = $id2, target_feature = $id2 /**/))]
fn $id() {}
};
}
Expand Down
12 changes: 12 additions & 0 deletions tests/source/attrib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,15 @@ fn issue3509() {
1,
}
}

// #3781
enum E {
#[error(display = "invalid max keyframe interval {} (expected > 0, < {})", _0, i32::max_value() as u64)]
InvalidMaxKeyFrameInterval(u64),
#[error(display = "invalid max keyframe interval {} (expected > 0, < {})", _0, i32::max_value())]
InvalidMaxKeyFrameInterval(u64),
}

// #6374
#[nutype(validate(len_char_min = 5, len_char_max = 20, regex = EMAIL_REGEX, extra_args = make_sure_this_line_is_split,))]
struct Email(String);
25 changes: 25 additions & 0 deletions tests/target/attrib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,28 @@ fn issue3509() {
}
}
}

// #3781
enum E {
#[error(
display = "invalid max keyframe interval {} (expected > 0, < {})",
_0,
i32::max_value() as u64
)]
InvalidMaxKeyFrameInterval(u64),
#[error(
display = "invalid max keyframe interval {} (expected > 0, < {})",
_0,
i32::max_value()
)]
InvalidMaxKeyFrameInterval(u64),
}

// #6374
#[nutype(validate(
len_char_min = 5,
len_char_max = 20,
regex = EMAIL_REGEX,
extra_args = make_sure_this_line_is_split,
))]
struct Email(String);
Loading
Loading