Skip to content

Commit c4b8026

Browse files
committed
check earlier for misused crate-level attributes
1 parent 20f19f4 commit c4b8026

16 files changed

Lines changed: 185 additions & 143 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use rustc_macros::{Diagnostic, Subdiagnostic};
2+
use rustc_span::{Span, Symbol};
3+
4+
#[derive(Diagnostic)]
5+
#[diag("`{$name}` attribute cannot be used at crate level")]
6+
pub(crate) struct InvalidAttrAtCrateLevel {
7+
#[primary_span]
8+
pub span: Span,
9+
#[suggestion(
10+
"perhaps you meant to use an outer attribute",
11+
code = "#[",
12+
applicability = "machine-applicable",
13+
style = "verbose"
14+
)]
15+
pub pound_to_opening_bracket: Span,
16+
pub name: Symbol,
17+
#[subdiagnostic]
18+
pub item: Option<ItemFollowingInnerAttr>,
19+
}
20+
21+
#[derive(Clone, Copy, Subdiagnostic)]
22+
#[label("the inner attribute doesn't annotate this item")]
23+
pub(crate) struct ItemFollowingInnerAttr {
24+
#[primary_span]
25+
pub span: Span,
26+
}

compiler/rustc_attr_parsing/src/interface.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
304304
kind: DocFragmentKind::Sugared(*comment_kind),
305305
span: attr_span,
306306
comment: *symbol,
307-
}))
307+
}));
308308
}
309309
ast::AttrKind::Normal(n) => {
310310
attr_paths.push(PathParser(&n.item.path));
@@ -408,15 +408,23 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
408408
// "attribute {path} wasn't parsed and isn't a know tool attribute",
409409
// );
410410

411-
attributes.push(Attribute::Unparsed(Box::new(AttrItem {
411+
let attr = AttrItem {
412412
path: attr_path.clone(),
413413
args: self
414414
.lower_attr_args(n.item.args.unparsed_ref().unwrap(), lower_span),
415415
id: HashIgnoredAttrId { attr_id: attr.id },
416416
style: attr.style,
417417
span: attr_span,
418-
})));
419-
}
418+
};
419+
420+
if !matches!(self.stage.should_emit(), ShouldEmit::Nothing)
421+
&& target == Target::Crate
422+
{
423+
self.check_invalid_crate_level_attr_item(&attr, n.item.span());
424+
}
425+
426+
attributes.push(Attribute::Unparsed(Box::new(attr)));
427+
};
420428
}
421429
}
422430
}

compiler/rustc_attr_parsing/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
// tidy-alphabetical-start
8080
#![feature(decl_macro)]
8181
#![feature(iter_intersperse)]
82+
#![feature(try_blocks)]
8283
#![recursion_limit = "256"]
8384
// tidy-alphabetical-end
8485

@@ -99,6 +100,7 @@ mod interface;
99100
pub mod parser;
100101

101102
mod early_parsed;
103+
mod errors;
102104
mod safety;
103105
mod session_diagnostics;
104106
mod target_checking;

compiler/rustc_attr_parsing/src/target_checking.rs

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use std::borrow::Cow;
22

33
use rustc_ast::AttrStyle;
4-
use rustc_errors::DiagArgValue;
4+
use rustc_errors::{DiagArgValue, StashKey};
55
use rustc_feature::Features;
66
use rustc_hir::lints::AttributeLintKind;
7-
use rustc_hir::{MethodKind, Target};
8-
use rustc_span::sym;
7+
use rustc_hir::{AttrItem, MethodKind, Target};
8+
use rustc_span::{BytePos, Span, Symbol, sym};
99

1010
use crate::AttributeParser;
1111
use crate::context::{AcceptContext, Stage};
12+
use crate::errors::{InvalidAttrAtCrateLevel, ItemFollowingInnerAttr};
1213
use crate::session_diagnostics::InvalidTarget;
1314
use crate::target_checking::Policy::Allow;
1415

@@ -96,6 +97,25 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
9697
return;
9798
}
9899

100+
if matches!(cx.attr_path.segments.as_ref(), [sym::repr]) && target == Target::Crate {
101+
// The allowed targets of `repr` depend on its arguments. They can't be checked using
102+
// the `AttributeParser` code.
103+
let span = cx.attr_span;
104+
let item =
105+
cx.cx.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span });
106+
107+
let pound_to_opening_bracket = cx.attr_span.until(cx.inner_span);
108+
109+
cx.dcx()
110+
.create_err(InvalidAttrAtCrateLevel {
111+
span,
112+
pound_to_opening_bracket,
113+
name: sym::repr,
114+
item,
115+
})
116+
.emit();
117+
}
118+
99119
match allowed_targets.is_allowed(target) {
100120
AllowedResult::Allowed => {}
101121
AllowedResult::Warn => {
@@ -163,6 +183,87 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
163183

164184
cx.emit_lint(rustc_session::lint::builtin::UNUSED_ATTRIBUTES, kind, attr_span);
165185
}
186+
187+
// FIXME: Fix "Cannot determine resolution" error and remove built-in macros
188+
// from this check.
189+
pub(crate) fn check_invalid_crate_level_attr_item(&self, attr: &AttrItem, inner_span: Span) {
190+
// Check for builtin attributes at the crate level
191+
// which were unsuccessfully resolved due to cannot determine
192+
// resolution for the attribute macro error.
193+
const ATTRS_TO_CHECK: &[Symbol] =
194+
&[sym::derive, sym::test, sym::test_case, sym::global_allocator, sym::bench];
195+
196+
// FIXME(jdonszelmann): all attrs should be combined here cleaning this up some day.
197+
if let Some(name) = ATTRS_TO_CHECK.iter().find(|attr_to_check| matches!(attr.path.segments.as_ref(), [segment] if segment == *attr_to_check)) {
198+
let span = attr.span;
199+
let name = *name;
200+
201+
let item = self.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span });
202+
203+
let err = self.dcx().create_err(InvalidAttrAtCrateLevel {
204+
span,
205+
pound_to_opening_bracket: span.until(inner_span),
206+
name,
207+
item,
208+
});
209+
210+
self.dcx().try_steal_replace_and_emit_err(
211+
attr.path.span,
212+
StashKey::UndeterminedMacroResolution,
213+
err,
214+
);
215+
}
216+
}
217+
218+
fn first_line_of_next_item(&self, span: Span) -> Option<Span> {
219+
// We can't exactly call `tcx.hir_free_items()` here because it's too early and querying
220+
// this would create a circular dependency. Instead, we resort to getting the original
221+
// source code that follows `span` and find the next item from here.
222+
223+
self.sess()
224+
.source_map()
225+
.span_to_source(span, |content, _, span_end| {
226+
let mut source = &content[span_end..];
227+
let initial_source_len = source.len();
228+
let span = try {
229+
loop {
230+
let first = source.chars().next()?;
231+
232+
if first.is_whitespace() {
233+
let split_idx = source.find(|c: char| !c.is_whitespace())?;
234+
source = &source[split_idx..];
235+
} else if source.starts_with("//") {
236+
let line_idx = source.find('\n')?;
237+
source = &source[line_idx + '\n'.len_utf8()..];
238+
} else if source.starts_with("/*") {
239+
// FIXME: support nested comments.
240+
let close_idx = source.find("*/")?;
241+
source = &source[close_idx + "*/".len()..];
242+
} else if first == '#' {
243+
// FIXME: properly find the end of the attributes in order to accurately
244+
// skip them. This version just consumes the source code until the next
245+
// `]`.
246+
let close_idx = source.find(']')?;
247+
source = &source[close_idx + ']'.len_utf8()..];
248+
} else {
249+
let lo = span_end + initial_source_len - source.len();
250+
let last_line = source.split('\n').next().map(|s| s.trim_end())?;
251+
252+
let hi = lo + last_line.len();
253+
let lo = BytePos(lo as u32);
254+
let hi = BytePos(hi as u32);
255+
let next_item_span = Span::new(lo, hi, span.ctxt(), None);
256+
257+
break next_item_span;
258+
}
259+
}
260+
};
261+
262+
Ok(span)
263+
})
264+
.ok()
265+
.flatten()
266+
}
166267
}
167268

168269
/// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to.

compiler/rustc_passes/src/check_attr.rs

Lines changed: 6 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ use rustc_attr_parsing::{AttributeParser, Late};
1515
use rustc_data_structures::fx::FxHashMap;
1616
use rustc_data_structures::thin_vec::ThinVec;
1717
use rustc_data_structures::unord::UnordMap;
18-
use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey, msg};
19-
use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
18+
use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, msg};
19+
use rustc_feature::{
20+
AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP,
21+
BuiltinAttribute,
22+
};
2023
use rustc_hir::attrs::diagnostic::Directive;
2124
use rustc_hir::attrs::{
2225
AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr,
@@ -45,7 +48,7 @@ use rustc_session::lint::builtin::{
4548
};
4649
use rustc_session::parse::feature_err;
4750
use rustc_span::edition::Edition;
48-
use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, sym};
51+
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
4952
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
5053
use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs};
5154
use rustc_trait_selection::traits::ObligationCtxt;
@@ -2025,64 +2028,6 @@ fn is_c_like_enum(item: &Item<'_>) -> bool {
20252028
}
20262029
}
20272030

2028-
// FIXME: Fix "Cannot determine resolution" error and remove built-in macros
2029-
// from this check.
2030-
fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
2031-
// Check for builtin attributes at the crate level
2032-
// which were unsuccessfully resolved due to cannot determine
2033-
// resolution for the attribute macro error.
2034-
const ATTRS_TO_CHECK: &[Symbol] =
2035-
&[sym::derive, sym::test, sym::test_case, sym::global_allocator, sym::bench];
2036-
2037-
for attr in attrs {
2038-
// FIXME(jdonszelmann): all attrs should be combined here cleaning this up some day.
2039-
let (span, name) = if let Some(a) =
2040-
ATTRS_TO_CHECK.iter().find(|attr_to_check| attr.has_name(**attr_to_check))
2041-
{
2042-
(attr.span(), *a)
2043-
} else if let Attribute::Parsed(AttributeKind::Repr {
2044-
reprs: _,
2045-
first_span: first_attr_span,
2046-
}) = attr
2047-
{
2048-
(*first_attr_span, sym::repr)
2049-
} else {
2050-
continue;
2051-
};
2052-
2053-
let item = tcx
2054-
.hir_free_items()
2055-
.map(|id| tcx.hir_item(id))
2056-
.find(|item| !item.span.is_dummy()) // Skip prelude `use`s
2057-
.map(|item| errors::ItemFollowingInnerAttr {
2058-
span: if let Some(ident) = item.kind.ident() { ident.span } else { item.span },
2059-
kind: tcx.def_descr(item.owner_id.to_def_id()),
2060-
});
2061-
let err = tcx.dcx().create_err(errors::InvalidAttrAtCrateLevel {
2062-
span,
2063-
sugg_span: tcx
2064-
.sess
2065-
.source_map()
2066-
.span_to_snippet(span)
2067-
.ok()
2068-
.filter(|src| src.starts_with("#!["))
2069-
.map(|_| span.with_lo(span.lo() + BytePos(1)).with_hi(span.lo() + BytePos(2))),
2070-
name,
2071-
item,
2072-
});
2073-
2074-
if let Attribute::Unparsed(p) = attr {
2075-
tcx.dcx().try_steal_replace_and_emit_err(
2076-
p.path.span,
2077-
StashKey::UndeterminedMacroResolution,
2078-
err,
2079-
);
2080-
} else {
2081-
err.emit();
2082-
}
2083-
}
2084-
}
2085-
20862031
fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>) {
20872032
let attrs = tcx.hir_attrs(item.hir_id());
20882033

@@ -2098,7 +2043,6 @@ fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
20982043
tcx.hir_visit_item_likes_in_module(module_def_id, check_attr_visitor);
20992044
if module_def_id.to_local_def_id().is_top_level_module() {
21002045
check_attr_visitor.check_attributes(CRATE_HIR_ID, DUMMY_SP, Target::Mod, None);
2101-
check_invalid_crate_level_attr(tcx, tcx.hir_krate_attrs());
21022046
}
21032047
if check_attr_visitor.abort.get() {
21042048
tcx.dcx().abort_if_errors()

compiler/rustc_passes/src/errors.rs

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ use std::path::{Path, PathBuf};
33

44
use rustc_errors::codes::*;
55
use rustc_errors::{
6-
Applicability, Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level,
7-
MultiSpan, msg,
6+
Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level, MultiSpan, msg,
87
};
98
use rustc_hir::Target;
109
use rustc_macros::{Diagnostic, Subdiagnostic};
@@ -447,44 +446,6 @@ pub(crate) struct LangItemOnIncorrectTarget {
447446
pub actual_target: Target,
448447
}
449448

450-
pub(crate) struct InvalidAttrAtCrateLevel {
451-
pub span: Span,
452-
pub sugg_span: Option<Span>,
453-
pub name: Symbol,
454-
pub item: Option<ItemFollowingInnerAttr>,
455-
}
456-
457-
#[derive(Clone, Copy)]
458-
pub(crate) struct ItemFollowingInnerAttr {
459-
pub span: Span,
460-
pub kind: &'static str,
461-
}
462-
463-
impl<G: EmissionGuarantee> Diagnostic<'_, G> for InvalidAttrAtCrateLevel {
464-
#[track_caller]
465-
fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
466-
let mut diag =
467-
Diag::new(dcx, level, msg!("`{$name}` attribute cannot be used at crate level"));
468-
diag.span(self.span);
469-
diag.arg("name", self.name);
470-
// Only emit an error with a suggestion if we can create a string out
471-
// of the attribute span
472-
if let Some(span) = self.sugg_span {
473-
diag.span_suggestion_verbose(
474-
span,
475-
msg!("perhaps you meant to use an outer attribute"),
476-
String::new(),
477-
Applicability::MachineApplicable,
478-
);
479-
}
480-
if let Some(item) = self.item {
481-
diag.arg("kind", item.kind);
482-
diag.span_label(item.span, msg!("the inner attribute doesn't annotate this {$kind}"));
483-
}
484-
diag
485-
}
486-
}
487-
488449
#[derive(Diagnostic)]
489450
#[diag("duplicate diagnostic item in crate `{$crate_name}`: `{$name}`")]
490451
pub(crate) struct DuplicateDiagnosticItemInCrate {

tests/ui/attributes/malformed-reprs.stderr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,27 @@ LL | #![repr]
66
|
77
= note: for more information, visit <https://doc.rust-lang.org/reference/type-layout.html#representations>
88

9-
error[E0589]: invalid `repr(align)` attribute: not a power of two
10-
--> $DIR/malformed-reprs.rs:9:14
11-
|
12-
LL | #[repr(align(0))]
13-
| ^
14-
159
error: `repr` attribute cannot be used at crate level
1610
--> $DIR/malformed-reprs.rs:4:1
1711
|
1812
LL | #![repr]
1913
| ^^^^^^^^
2014
...
2115
LL | enum Foo {}
22-
| --- the inner attribute doesn't annotate this enum
16+
| ----------- the inner attribute doesn't annotate this item
2317
|
2418
help: perhaps you meant to use an outer attribute
2519
|
2620
LL - #![repr]
2721
LL + #[repr]
2822
|
2923

24+
error[E0589]: invalid `repr(align)` attribute: not a power of two
25+
--> $DIR/malformed-reprs.rs:9:14
26+
|
27+
LL | #[repr(align(0))]
28+
| ^
29+
3030
error[E0084]: unsupported representation for zero-variant enum
3131
--> $DIR/malformed-reprs.rs:9:1
3232
|

0 commit comments

Comments
 (0)