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: 3 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4330,6 +4330,7 @@ dependencies = [
"rustc_apfloat",
"rustc_ast",
"rustc_ast_pretty",
"rustc_attr_ir",
"rustc_attr_parsing",
"rustc_data_structures",
"rustc_errors",
Expand All @@ -4348,6 +4349,7 @@ dependencies = [
"rustc_target",
"rustc_trait_selection",
"smallvec",
"thin-vec",
"tracing",
"unicode-security",
]
Expand Down Expand Up @@ -4477,6 +4479,7 @@ dependencies = [
"rustc_apfloat",
"rustc_arena",
"rustc_ast",
"rustc_attr_ir",
"rustc_data_structures",
"rustc_errors",
"rustc_hir",
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_attr_ir/src/data_structures.rs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see the perf regression solved, or at least explained.
The previous attempt is a lot more green: #155691 (comment)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did make a change where the lint name is stored as {tool_name: Option<Symbol>, lint_name: Symbol} which did a little better in #162811 (comment) But I think i'd like a vibecheck before I start dropping segments on (unsupported?) things like lint::blah::foo

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that did get rid of a lot of allocations, though

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use thin_vec::ThinVec;
pub use crate::canonical_symbols::{CanonicalSymbol, CanonicalSymbols};
use crate::diagnostic::*;
use crate::lang_items::LangItem;
use crate::lint::LintCheck;
use crate::pretty_printing::PrintAttribute;
use crate::stability::{DefaultBodyStability, PartialConstStability, Stability};

Expand Down Expand Up @@ -933,6 +934,9 @@ pub enum AttributeKind {
/// Represents `#[linkage]`.
Linkage(Linkage, Span),

/// Represents `#[allow]`, `#[warn]`, `#[deny]`, `#[forbid]`, and `#[expect]`.
LintCheck(ThinVec<LintCheck>),

/// Represents `#[loop_match]`.
LoopMatch(Span),

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_ir/src/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl AttributeKind {
LinkOrdinal { .. } => No,
LinkSection { .. } => Yes, // Needed for rustdoc
Linkage(..) => No,
LintCheck(..) => No,
LoopMatch(..) => No,
MacroEscape => No,
MacroExport { .. } => Yes,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_ir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod diagnostic;
pub mod diagnostic_items;
mod encode_cross_crate;
pub mod lang_items;
pub mod lint;
mod pretty_printing;
mod stability;
pub mod target;
Expand Down
72 changes: 72 additions & 0 deletions compiler/rustc_attr_ir/src/lint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use rustc_macros::{Decodable, Encodable, PrintAttribute, StableHash};
use rustc_span::{Span, Symbol, sym};

use crate::{HashIgnoredAttrId, PrintAttribute};
#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
pub enum LintCheckKind {
Allow,
Warn,
Deny,
Forbid,
Expect,
}

impl LintCheckKind {
pub fn sym(self) -> Symbol {
match self {
LintCheckKind::Allow => sym::allow,
LintCheckKind::Warn => sym::warn,
LintCheckKind::Deny => sym::deny,
LintCheckKind::Forbid => sym::forbid,
LintCheckKind::Expect => sym::expect,
}
}
}

/// A lint check attribute.
///
/// For example `#[deny(clippy::blah, reason = "reason")]` is lowered into this.
///
/// These are smooshed and flattened together;
/// ```rust
/// #[allow(dead_code)]
/// #[deny(unused, unsafe_code)]
/// # const _: () = ();
/// ```
/// is lowered into
/// ```text
/// #[attr = LintCheck([
/// LintCheck { lint_name: "dead_code", kind: Allow },
/// LintCheck { lint_name: "unused", kind: Deny },
/// LintCheck { lint_name: "unsafe_code", kind: Deny },
/// ])]
/// ```
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
pub struct LintCheck {
/// The lint's tool name, if present.
pub tool_name: Option<Symbol>,
/// The lint's name.
///
/// With e.g. `clippy:blah` this will be `blah`.
/// Any extra segments are stored in `rest`.
pub lint_name: Symbol,
/// The span of the lint name.
///
/// For example `#[deny(foo, bar, reason = "reason")]`
/// produces multiple `LintCheck`s, one with a span pointing to `foo`
/// and another pointing to `bar`.
pub lint_span: Span,
pub kind: LintCheckKind,
pub reason: Option<Symbol>,
/// Needed by `LintExpectationId` to track fulfilled expectations
pub attr_id: HashIgnoredAttrId,
/// The span of the attribute this lintcheck came from.
///
/// Like mentioned above, multiple lint attributes and multiple lints
/// inside one attribute are all smooshed and flattened together, so
/// multiple `LintCheck`s can have the same `attr_span`.
pub attr_span: Span,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a doc comment clarifying the difference between span and attr_span?

/// Any extra segments of the lint name, this should be rare and indicates misuse of
/// the attribute as nothing supports 3+ segment lints like `#[allow(tool::two::three)]`.
pub rest: Option<Box<[Symbol]>>,
}
21 changes: 17 additions & 4 deletions compiler/rustc_attr_ir/src/pretty_printing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
use rustc_structures::{CollapseMacroDebuginfo, CrateType, Limit, NativeLibKind, SanitizerSet};
use thin_vec::ThinVec;

use crate::HashIgnoredAttrId;

/// This trait is used to print attributes in `rustc_hir_pretty`.
///
/// For structs and enums it can be derived using [`rustc_macros::PrintAttribute`].
Expand All @@ -32,7 +34,7 @@ pub trait PrintAttribute {
fn print_attribute(&self, p: &mut Printer);
}

impl<T: PrintAttribute> PrintAttribute for &T {
impl<T: ?Sized + PrintAttribute> PrintAttribute for &T {
fn should_render(&self) -> bool {
T::should_render(self)
}
Expand All @@ -41,7 +43,7 @@ impl<T: PrintAttribute> PrintAttribute for &T {
T::print_attribute(self, p)
}
}
impl<T: PrintAttribute> PrintAttribute for Box<T> {
impl<T: ?Sized + PrintAttribute> PrintAttribute for Box<T> {
fn should_render(&self) -> bool {
self.deref().should_render()
}
Expand All @@ -61,7 +63,8 @@ impl<T: PrintAttribute> PrintAttribute for Option<T> {
}
}
}
impl<T: PrintAttribute> PrintAttribute for ThinVec<T> {

impl<T: PrintAttribute> PrintAttribute for [T] {
fn should_render(&self) -> bool {
self.is_empty() || self[0].should_render()
}
Expand All @@ -79,6 +82,16 @@ impl<T: PrintAttribute> PrintAttribute for ThinVec<T> {
p.word("]");
}
}

impl<T: PrintAttribute> PrintAttribute for ThinVec<T> {
fn should_render(&self) -> bool {
self.as_slice().should_render()
}

fn print_attribute(&self, p: &mut Printer) {
self.as_slice().print_attribute(p)
}
}
impl<T: PrintAttribute, T2: PrintAttribute> PrintAttribute for FxIndexMap<T, T2> {
fn should_render(&self) -> bool {
self.is_empty() || self[0].should_render()
Expand Down Expand Up @@ -189,7 +202,7 @@ macro_rules! print_tup {
}

print_tup!(A B C D E F G H);
print_skip!(Span, (), ErrorGuaranteed, AttrId);
print_skip!(Span, (), ErrorGuaranteed, AttrId, HashIgnoredAttrId);
print_disp!(u8, u16, u32, u128, usize, bool, NonZero<u32>, Limit);
print_debug!(
Symbol,
Expand Down
205 changes: 205 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/lint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
use rustc_attr_ir::AttributeKind;
use rustc_attr_ir::lint::{LintCheck, LintCheckKind};
use rustc_attr_ir::target::{AssocCtxt, MethodKind, Target};
use rustc_lint_defs::builtin::UNUSED_ATTRIBUTES;
use rustc_span::{Span, Symbol, sym};
use thin_vec::ThinVec;

use crate::attributes::{AcceptMapping, AttributeParser, AttributeStability};
use crate::context::{AcceptContext, ExpectStringLiteral, FinalizeContext};
use crate::parser::ArgParser;
use crate::target_checking::AllowedTargets;
use crate::target_checking::Policy::{Allow, Warn};
use crate::{AttributeTemplate, diagnostics, template};

const LINT_TEMPLATE: AttributeTemplate = template!(
List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#],
"https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"
);

#[derive(Default, Debug)]
pub(crate) struct LintParser {
lints: ThinVec<LintCheck>,
}

impl LintParser {
fn parse(&mut self, kind: LintCheckKind, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
let attr_span = cx.attr_span;
let attr_id = cx.attr_id.expect("no `AttrId` for lint attribute");
let mut lints: Vec<(Option<Symbol>, Symbol, Option<Box<[Symbol]>>, Span)> = Vec::new();

if let Some(list) = cx.expect_list(args, cx.attr_span) {
let mut parsers = list.sub_parsers();
// Optionally, the last (and only the last)
// element can be `reason = "reason"`
let reason = try {
let p = parsers.last()?.meta_item()?;
let nv = p.args().as_name_value()?;
if !p.path().word_is(sym::reason) {
cx.emit_err(diagnostics::MalformedAttribute {
span: p.span(),
sub: diagnostics::MalformedAttributeSub::BadAttributeArgument(p.span()),
});
} else {
parsers = &parsers[..(parsers.len() - 1)];
}

let reason = nv.expect_string_literal(cx)?;
reason
};

for item in parsers {
if let Some(p) = item.meta_item() {
match p.args() {
ArgParser::NoArgs => {
let (tool_name, lint_name, rest) = match &*p.path().0.segments {
[] => unreachable!(),
[lint_name] => (None, lint_name.ident.name, None),
[tool_name, lint_name] => {
(Some(tool_name.ident.name), lint_name.ident.name, None)
}
[tool_name, lint_name, rest @ ..] => {
let rest = rest
.iter()
.map(|s| s.ident.name)
.collect::<Vec<_>>()
.into();
(Some(tool_name.ident.name), lint_name.ident.name, Some(rest))
}
};

lints.push((tool_name, lint_name, rest, p.span()))
}
// We're found a `reason = "reason"` but we're not the last element.
ArgParser::NameValue(nv) if p.path().word_is(sym::reason) => {
cx.emit_err(diagnostics::MalformedAttribute {
span: p.span(),
sub: diagnostics::MalformedAttributeSub::ReasonMustComeLast(
item.span(),
),
});
nv.expect_string_literal(cx);
}
ArgParser::NameValue(_) | ArgParser::List(_) => {
cx.emit_err(diagnostics::MalformedAttribute {
span: p.span(),
sub: diagnostics::MalformedAttributeSub::BadAttributeArgument(
item.span(),
),
});
}
}
} else {
cx.emit_err(diagnostics::MalformedAttribute {
span: item.span(),
sub: diagnostics::MalformedAttributeSub::BadAttributeArgument(item.span()),
});
}
}
if parsers.is_empty() {
cx.emit_lint(
UNUSED_ATTRIBUTES,
diagnostics::Unused {
attr_span,
note: if list.is_empty() {
diagnostics::UnusedNote::EmptyList { name: kind.sym() }
} else {
diagnostics::UnusedNote::NoLints { name: kind.sym() }
},
},
attr_span,
);
}

for (tool_name, lint_name, rest, lint_span) in lints.into_iter() {
self.lints.push(LintCheck {
tool_name,
lint_name,
lint_span,
kind,
attr_id,
reason,
attr_span,
rest,
})
}
}
}
}

impl AttributeParser for LintParser {
const ATTRIBUTES: AcceptMapping<Self> = &[
(&[sym::allow], LINT_TEMPLATE, AttributeStability::Stable, |this, cx, args| {
this.parse(LintCheckKind::Allow, cx, args)
}),
(&[sym::warn], LINT_TEMPLATE, AttributeStability::Stable, |this, cx, args| {
this.parse(LintCheckKind::Warn, cx, args)
}),
(&[sym::deny], LINT_TEMPLATE, AttributeStability::Stable, |this, cx, args| {
this.parse(LintCheckKind::Deny, cx, args)
}),
(&[sym::forbid], LINT_TEMPLATE, AttributeStability::Stable, |this, cx, args| {
this.parse(LintCheckKind::Forbid, cx, args)
}),
(&[sym::expect], LINT_TEMPLATE, AttributeStability::Stable, |this, cx, args| {
this.parse(LintCheckKind::Expect, cx, args)
}),
];
const ALLOWED_TARGETS: AllowedTargets<'_> = {
AllowedTargets::AllowList(&[
Allow(Target::ExternCrate),
Allow(Target::Use),
Allow(Target::Static),
Allow(Target::Const),
Allow(Target::Fn),
Allow(Target::Closure),
Allow(Target::Mod),
Allow(Target::ForeignMod),
Allow(Target::GlobalAsm),
Allow(Target::TyAlias),
Allow(Target::Enum),
Allow(Target::Variant),
Allow(Target::Struct),
Allow(Target::Field),
Allow(Target::Union),
Allow(Target::Trait),
Allow(Target::TraitAlias),
Allow(Target::Impl { of_trait: false }),
Allow(Target::Impl { of_trait: true }),
Allow(Target::Expression),
Allow(Target::Statement),
Allow(Target::Arm),
Allow(Target::AssocConst(AssocCtxt::Impl { of_trait: false })),
Allow(Target::AssocConst(AssocCtxt::Trait)),
Allow(Target::AssocConst(AssocCtxt::Impl { of_trait: true })),
Allow(Target::Method(MethodKind::Inherent)),
Allow(Target::Method(MethodKind::Trait { body: false })),
Allow(Target::Method(MethodKind::Trait { body: true })),
Allow(Target::Method(MethodKind::TraitImpl)),
Allow(Target::AssocTy(AssocCtxt::Impl { of_trait: false })),
Allow(Target::AssocTy(AssocCtxt::Trait)),
Allow(Target::AssocTy(AssocCtxt::Impl { of_trait: true })),
Allow(Target::ForeignFn),
Allow(Target::ForeignStatic),
Allow(Target::ForeignTy),
Allow(Target::MacroDef),
Allow(Target::Param),
Allow(Target::PatField),
Allow(Target::ExprField),
Allow(Target::Crate),
Allow(Target::Delegation { mac: false }),
Allow(Target::Delegation { mac: true }),
Allow(Target::ConstParam),
Allow(Target::LifetimeParam),
Allow(Target::TypeParam),
Allow(Target::Loop),
Allow(Target::ForLoop),
Allow(Target::While),
Allow(Target::Break),
Warn(Target::MacroCall),
])
};
fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
if self.lints.is_empty() { None } else { Some(AttributeKind::LintCheck(self.lints)) }
}
}
Loading
Loading