|
1 | 1 | use std::borrow::Cow; |
2 | 2 |
|
3 | 3 | use rustc_ast::AttrStyle; |
4 | | -use rustc_errors::DiagArgValue; |
| 4 | +use rustc_errors::{DiagArgValue, StashKey}; |
5 | 5 | use rustc_feature::Features; |
6 | 6 | 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}; |
9 | 9 |
|
10 | 10 | use crate::AttributeParser; |
11 | 11 | use crate::context::{AcceptContext, Stage}; |
| 12 | +use crate::errors::{InvalidAttrAtCrateLevel, ItemFollowingInnerAttr}; |
12 | 13 | use crate::session_diagnostics::InvalidTarget; |
13 | 14 | use crate::target_checking::Policy::Allow; |
14 | 15 |
|
@@ -96,6 +97,25 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { |
96 | 97 | return; |
97 | 98 | } |
98 | 99 |
|
| 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 | + |
99 | 119 | match allowed_targets.is_allowed(target) { |
100 | 120 | AllowedResult::Allowed => {} |
101 | 121 | AllowedResult::Warn => { |
@@ -163,6 +183,87 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { |
163 | 183 |
|
164 | 184 | cx.emit_lint(rustc_session::lint::builtin::UNUSED_ATTRIBUTES, kind, attr_span); |
165 | 185 | } |
| 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 | + } |
166 | 267 | } |
167 | 268 |
|
168 | 269 | /// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to. |
|
0 commit comments