Skip to content

Commit c5dc1c1

Browse files
committed
check earlier for misused crate-level attributes
1 parent ea5573a commit c5dc1c1

27 files changed

Lines changed: 234 additions & 155 deletions

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use rustc_ast::{self as ast, AttrVec, NodeId, PatKind, attr, token};
33
use rustc_attr_parsing::AttributeParser;
44
use rustc_errors::msg;
55
use rustc_feature::{AttributeGate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, Features};
6-
use rustc_hir::Attribute;
76
use rustc_hir::attrs::AttributeKind;
7+
use rustc_hir::{Attribute, Target};
88
use rustc_session::Session;
99
use rustc_session::parse::{feature_err, feature_warn};
1010
use rustc_span::source_map::Spanned;
@@ -657,6 +657,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate)
657657
sess,
658658
&krate.attrs,
659659
sym::feature,
660+
Target::Crate,
660661
DUMMY_SP,
661662
krate.id,
662663
Some(&features),
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 sugg_span: Option<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: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ use std::convert::identity;
33
use rustc_ast as ast;
44
use rustc_ast::token::DocFragmentKind;
55
use rustc_ast::{AttrItemKind, AttrStyle, NodeId, Safety};
6-
use rustc_errors::DiagCtxtHandle;
6+
use rustc_errors::{DiagCtxtHandle, StashKey};
77
use rustc_feature::{AttributeTemplate, Features};
88
use rustc_hir::attrs::AttributeKind;
99
use rustc_hir::lints::AttributeLintKind;
1010
use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, Target};
1111
use rustc_session::Session;
1212
use rustc_session::lint::{BuiltinLintDiag, LintId};
13-
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
13+
use rustc_span::{BytePos, DUMMY_SP, Span, Symbol, sym};
1414

1515
use crate::context::{AcceptContext, FinalizeContext, FinalizeFn, SharedContext, Stage};
1616
use crate::early_parsed::{EARLY_PARSED_ATTRIBUTES, EarlyParsedState};
17+
use crate::errors::{InvalidAttrAtCrateLevel, ItemFollowingInnerAttr};
1718
use crate::parser::{ArgParser, PathParser, RefPathParser};
1819
use crate::session_diagnostics::ParsedDescription;
1920
use crate::{Early, Late, OmitDoc, ShouldEmit};
@@ -51,6 +52,7 @@ impl<'sess> AttributeParser<'sess, Early> {
5152
sess: &'sess Session,
5253
attrs: &[ast::Attribute],
5354
sym: Symbol,
55+
target: Target,
5456
target_span: Span,
5557
target_node_id: NodeId,
5658
features: Option<&'sess Features>,
@@ -59,6 +61,7 @@ impl<'sess> AttributeParser<'sess, Early> {
5961
sess,
6062
attrs,
6163
sym,
64+
target,
6265
target_span,
6366
target_node_id,
6467
features,
@@ -72,6 +75,7 @@ impl<'sess> AttributeParser<'sess, Early> {
7275
sess: &'sess Session,
7376
attrs: &[ast::Attribute],
7477
sym: Symbol,
78+
target: Target,
7579
target_span: Span,
7680
target_node_id: NodeId,
7781
features: Option<&'sess Features>,
@@ -81,7 +85,7 @@ impl<'sess> AttributeParser<'sess, Early> {
8185
sess,
8286
attrs,
8387
Some(sym),
84-
Target::Crate, // Does not matter, we're not going to emit errors anyways
88+
target,
8589
target_span,
8690
target_node_id,
8791
features,
@@ -302,7 +306,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
302306
kind: DocFragmentKind::Sugared(*comment_kind),
303307
span: attr_span,
304308
comment: *symbol,
305-
}))
309+
}));
306310
}
307311
ast::AttrKind::Normal(n) => {
308312
attr_paths.push(PathParser(&n.item.path));
@@ -405,15 +409,23 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
405409
// "attribute {path} wasn't parsed and isn't a know tool attribute",
406410
// );
407411

408-
attributes.push(Attribute::Unparsed(Box::new(AttrItem {
412+
let attr = AttrItem {
409413
path: attr_path.clone(),
410414
args: self
411415
.lower_attr_args(n.item.args.unparsed_ref().unwrap(), lower_span),
412416
id: HashIgnoredAttrId { attr_id: attr.id },
413417
style: attr.style,
414418
span: attr_span,
415-
})));
416-
}
419+
};
420+
421+
if !matches!(self.stage.should_emit(), ShouldEmit::Nothing)
422+
&& target == Target::Crate
423+
{
424+
self.check_invalid_crate_level_attr_item(&attr);
425+
}
426+
427+
attributes.push(Attribute::Unparsed(Box::new(attr)));
428+
};
417429
}
418430
}
419431
}
@@ -477,4 +489,96 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
477489
}
478490
}
479491
}
492+
493+
// FIXME: Fix "Cannot determine resolution" error and remove built-in macros
494+
// from this check.
495+
fn check_invalid_crate_level_attr_item(&self, attr: &AttrItem) {
496+
// Check for builtin attributes at the crate level
497+
// which were unsuccessfully resolved due to cannot determine
498+
// resolution for the attribute macro error.
499+
const ATTRS_TO_CHECK: &[Symbol] =
500+
&[sym::derive, sym::test, sym::test_case, sym::global_allocator, sym::bench];
501+
502+
// FIXME(jdonszelmann): all attrs should be combined here cleaning this up some day.
503+
if let Some(name) = ATTRS_TO_CHECK.iter().find(|attr_to_check| matches!(attr.path.segments.as_ref(), [segment] if segment == *attr_to_check)) {
504+
let span = attr.span;
505+
let name = *name;
506+
507+
let item = self.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span });
508+
509+
let err = self.dcx().create_err(InvalidAttrAtCrateLevel {
510+
span,
511+
sugg_span: self
512+
.sess
513+
.source_map()
514+
.span_to_snippet(attr.span)
515+
.ok()
516+
.filter(|src| src.starts_with("#!["))
517+
.map(|_| {
518+
span
519+
.with_lo(span.lo() + BytePos(1))
520+
.with_hi(span.lo() + BytePos(2))
521+
}),
522+
name,
523+
item,
524+
});
525+
526+
self.dcx().try_steal_replace_and_emit_err(
527+
attr.path.span,
528+
StashKey::UndeterminedMacroResolution,
529+
err,
530+
);
531+
}
532+
}
533+
534+
pub(crate) fn first_line_of_next_item(&self, span: Span) -> Option<Span> {
535+
// We can't exactly call `tcx.hir_free_items()` here because it's too early and querying
536+
// this would create a circular dependency. Instead, we resort to getting the original
537+
// source code that follows `span` and find the next item from here.
538+
539+
self.sess()
540+
.source_map()
541+
.span_to_source(span, |content, _, span_end| {
542+
let mut source = &content[span_end..];
543+
let initial_source_len = source.len();
544+
let span = try {
545+
loop {
546+
let first = source.chars().next()?;
547+
548+
if first.is_whitespace() {
549+
let split_idx = source.find(|c: char| !c.is_whitespace())?;
550+
source = &source[split_idx..];
551+
} else if source.starts_with("//") {
552+
let line_idx = source.find('\n')?;
553+
source = &source[line_idx + '\n'.len_utf8()..];
554+
} else if source.starts_with("/*") {
555+
// FIXME: support nested comments.
556+
let close_idx = source.find("*/")?;
557+
source = &source[close_idx + "*/".len()..];
558+
} else if first == '#' {
559+
// FIXME: properly find the end of the attributes in order to accurately
560+
// skip them. This version just consumes the source code until the next
561+
// `]`.
562+
let close_idx = source.find(']')?;
563+
source = &source[close_idx + ']'.len_utf8()..];
564+
} else {
565+
let lo = span_end + initial_source_len - source.len();
566+
let last_line = source.split('\n').next().map(|s| s.trim_end())?;
567+
568+
let hi = lo + last_line.len();
569+
let lo = BytePos(lo as u32);
570+
let hi = BytePos(hi as u32);
571+
let next_item_span = Span::new(lo, hi, span.ctxt(), None);
572+
573+
break next_item_span;
574+
}
575+
}
576+
};
577+
578+
579+
Ok(span)
580+
})
581+
.ok()
582+
.flatten()
583+
}
480584
}

compiler/rustc_attr_parsing/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
#![cfg_attr(bootstrap, feature(if_let_guard))]
8181
#![feature(decl_macro)]
8282
#![feature(iter_intersperse)]
83+
#![feature(try_blocks)]
8384
#![recursion_limit = "256"]
8485
// tidy-alphabetical-end
8586

@@ -100,6 +101,7 @@ mod interface;
100101
pub mod parser;
101102

102103
mod early_parsed;
104+
mod errors;
103105
mod safety;
104106
mod session_diagnostics;
105107
mod target_checking;

compiler/rustc_attr_parsing/src/target_checking.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_errors::DiagArgValue;
55
use rustc_feature::Features;
66
use rustc_hir::lints::AttributeLintKind;
77
use rustc_hir::{MethodKind, Target};
8-
use rustc_span::sym;
8+
use rustc_span::{BytePos, sym};
99

1010
use crate::AttributeParser;
1111
use crate::context::{AcceptContext, Stage};
@@ -96,6 +96,34 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
9696
return;
9797
}
9898

99+
if matches!(cx.attr_path.segments.as_ref(), [sym::repr]) && target == Target::Crate {
100+
// The allowed targets of `repr` depend on its arguments. They can't be checked using
101+
// the `AttributeParser` code.
102+
let span = cx.attr_span;
103+
let item = cx
104+
.cx
105+
.first_line_of_next_item(span)
106+
.map(|span| crate::errors::ItemFollowingInnerAttr { span });
107+
108+
let sugg_span = cx
109+
.cx
110+
.sess
111+
.source_map()
112+
.span_to_snippet(span)
113+
.ok()
114+
.filter(|src| src.starts_with("#!["))
115+
.map(|_| span.with_lo(span.lo() + BytePos(1)).with_hi(span.lo() + BytePos(2)));
116+
117+
cx.dcx()
118+
.create_err(crate::errors::InvalidAttrAtCrateLevel {
119+
span,
120+
sugg_span,
121+
name: sym::repr,
122+
item,
123+
})
124+
.emit();
125+
}
126+
99127
match allowed_targets.is_allowed(target) {
100128
AllowedResult::Allowed => {}
101129
AllowedResult::Warn => {

compiler/rustc_builtin_macros/src/deriving/generic/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ use rustc_ast::{
188188
};
189189
use rustc_attr_parsing::AttributeParser;
190190
use rustc_expand::base::{Annotatable, ExtCtxt};
191-
use rustc_hir::Attribute;
192191
use rustc_hir::attrs::{AttributeKind, ReprPacked};
192+
use rustc_hir::{Attribute, Target};
193193
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
194194
use thin_vec::{ThinVec, thin_vec};
195195
use ty::{Bounds, Path, Ref, Self_, Ty};
@@ -493,7 +493,7 @@ impl<'a> TraitDef<'a> {
493493
match item {
494494
Annotatable::Item(item) => {
495495
let is_packed = matches!(
496-
AttributeParser::parse_limited(cx.sess, &item.attrs, sym::repr, item.span, item.id, None),
496+
AttributeParser::parse_limited(cx.sess, &item.attrs, sym::repr, Target::from_ast_item(item), item.span, item.id, None),
497497
Some(Attribute::Parsed(AttributeKind::Repr { reprs, .. })) if reprs.iter().any(|(x, _)| matches!(x, ReprPacked(..)))
498498
);
499499

compiler/rustc_builtin_macros/src/proc_macro_harness.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_errors::DiagCtxtHandle;
88
use rustc_expand::base::{ExtCtxt, ResolverExpand};
99
use rustc_expand::expand::{AstFragment, ExpansionConfig};
1010
use rustc_feature::Features;
11+
use rustc_hir::Target;
1112
use rustc_hir::attrs::AttributeKind;
1213
use rustc_session::Session;
1314
use rustc_span::hygiene::AstPass;
@@ -109,6 +110,7 @@ impl<'a> CollectProcMacros<'a> {
109110
self.session,
110111
slice::from_ref(attr),
111112
sym::proc_macro_derive,
113+
Target::from_ast_item(item),
112114
item.span,
113115
item.node_id(),
114116
None,

compiler/rustc_builtin_macros/src/test.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use rustc_attr_parsing::AttributeParser;
99
use rustc_data_structures::assert_matches;
1010
use rustc_errors::{Applicability, Diag, Level};
1111
use rustc_expand::base::*;
12-
use rustc_hir::Attribute;
1312
use rustc_hir::attrs::AttributeKind;
13+
use rustc_hir::{Attribute, Target};
1414
use rustc_span::{ErrorGuaranteed, Ident, RemapPathScopeComponents, Span, Symbol, sym};
1515
use thin_vec::{ThinVec, thin_vec};
1616
use tracing::debug;
@@ -485,6 +485,7 @@ fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
485485
cx.sess,
486486
&i.attrs,
487487
sym::should_panic,
488+
Target::from_ast_item(i),
488489
i.span,
489490
i.node_id(),
490491
None,

compiler/rustc_builtin_macros/src/test_harness.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_attr_parsing::AttributeParser;
1212
use rustc_expand::base::{ExtCtxt, ResolverExpand};
1313
use rustc_expand::expand::{AstFragment, ExpansionConfig};
1414
use rustc_feature::Features;
15+
use rustc_hir::Target;
1516
use rustc_hir::attrs::AttributeKind;
1617
use rustc_session::Session;
1718
use rustc_session::lint::builtin::UNNAMEABLE_TEST_ITEMS;
@@ -392,6 +393,7 @@ fn get_test_runner(sess: &Session, features: &Features, krate: &ast::Crate) -> O
392393
sess,
393394
&krate.attrs,
394395
sym::test_runner,
396+
Target::Crate,
395397
krate.spans.inner_span,
396398
krate.id,
397399
Some(features),

compiler/rustc_expand/src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) -
5454
sess,
5555
krate_attrs,
5656
sym::feature,
57+
Target::Crate,
5758
DUMMY_SP,
5859
DUMMY_NODE_ID,
5960
Some(&features),

0 commit comments

Comments
 (0)