use rustc_middle::ty::query::Providers;
use rustc_middle::ty::TyCtxt;
-use rustc_ast::{Attribute, LitKind, NestedMetaItem};
+use rustc_ast::{Attribute, Lit, LitKind, NestedMetaItem};
use rustc_errors::{pluralize, struct_span_err};
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
self, FnSig, ForeignItem, ForeignItemKind, HirId, Item, ItemKind, TraitItem, CRATE_HIR_ID,
};
use rustc_hir::{MethodKind, Target};
-use rustc_session::lint::builtin::{CONFLICTING_REPR_HINTS, UNUSED_ATTRIBUTES};
+use rustc_session::lint::builtin::{
+ CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES,
+};
use rustc_session::parse::feature_err;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::{Span, DUMMY_SP};
match impl_item.kind {
hir::ImplItemKind::Const(..) => Target::AssocConst,
hir::ImplItemKind::Fn(..) => {
- let parent_hir_id = tcx.hir().get_parent_item(impl_item.hir_id);
+ let parent_hir_id = tcx.hir().get_parent_item(impl_item.hir_id());
let containing_item = tcx.hir().expect_item(parent_hir_id);
let containing_impl_is_for_trait = match &containing_item.kind {
hir::ItemKind::Impl(impl_) => impl_.of_trait.is_some(),
fn check_attributes(
&self,
hir_id: HirId,
- attrs: &'hir [Attribute],
span: &Span,
target: Target,
item: Option<ItemLike<'_>>,
) {
let mut is_valid = true;
+ let attrs = self.tcx.hir().attrs(hir_id);
for attr in attrs {
is_valid &= if self.tcx.sess.check_name(attr, sym::inline) {
self.check_inline(hir_id, attr, span, target)
self.check_export_name(hir_id, &attr, span, target)
} else if self.tcx.sess.check_name(attr, sym::rustc_args_required_const) {
self.check_rustc_args_required_const(&attr, span, target, item)
+ } else if self.tcx.sess.check_name(attr, sym::rustc_layout_scalar_valid_range_start) {
+ self.check_rustc_layout_scalar_valid_range(&attr, span, target)
+ } else if self.tcx.sess.check_name(attr, sym::rustc_layout_scalar_valid_range_end) {
+ self.check_rustc_layout_scalar_valid_range(&attr, span, target)
} else if self.tcx.sess.check_name(attr, sym::allow_internal_unstable) {
self.check_allow_internal_unstable(hir_id, &attr, span, target, &attrs)
} else if self.tcx.sess.check_name(attr, sym::rustc_allow_const_fn_unstable) {
self.check_rustc_allow_const_fn_unstable(hir_id, &attr, span, target)
} else if self.tcx.sess.check_name(attr, sym::naked) {
self.check_naked(hir_id, attr, span, target)
+ } else if self.tcx.sess.check_name(attr, sym::rustc_legacy_const_generics) {
+ self.check_rustc_legacy_const_generics(&attr, span, target, item)
} else {
// lint-only checks
if self.tcx.sess.check_name(attr, sym::cold) {
.emit();
}
- fn check_doc_alias(&self, meta: &NestedMetaItem, hir_id: HirId, target: Target) -> bool {
- let doc_alias = meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new);
+ fn check_doc_alias_value(
+ &self,
+ meta: &NestedMetaItem,
+ doc_alias: &str,
+ hir_id: HirId,
+ target: Target,
+ is_list: bool,
+ ) -> bool {
+ let tcx = self.tcx;
+ let err_fn = move |span: Span, msg: &str| {
+ tcx.sess.span_err(
+ span,
+ &format!(
+ "`#[doc(alias{})]` {}",
+ if is_list { "(\"...\")" } else { " = \"...\"" },
+ msg,
+ ),
+ );
+ false
+ };
if doc_alias.is_empty() {
- self.doc_attr_str_error(meta, "alias");
- return false;
+ return err_fn(
+ meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
+ "attribute cannot have empty value",
+ );
}
if let Some(c) =
doc_alias.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
{
- self.tcx
- .sess
- .struct_span_err(
- meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
- &format!("{:?} character isn't allowed in `#[doc(alias = \"...\")]`", c),
- )
- .emit();
+ self.tcx.sess.span_err(
+ meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
+ &format!(
+ "{:?} character isn't allowed in `#[doc(alias{})]`",
+ c,
+ if is_list { "(\"...\")" } else { " = \"...\"" },
+ ),
+ );
return false;
}
if doc_alias.starts_with(' ') || doc_alias.ends_with(' ') {
- self.tcx
- .sess
- .struct_span_err(
- meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
- "`#[doc(alias = \"...\")]` cannot start or end with ' '",
- )
- .emit();
- return false;
+ return err_fn(
+ meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
+ "cannot start or end with ' '",
+ );
}
if let Some(err) = match target {
Target::Impl => Some("implementation block"),
}
_ => None,
} {
- self.tcx
- .sess
- .struct_span_err(
- meta.span(),
- &format!("`#[doc(alias = \"...\")]` isn't allowed on {}", err),
- )
- .emit();
- return false;
+ return err_fn(meta.span(), &format!("isn't allowed on {}", err));
}
let item_name = self.tcx.hir().name(hir_id);
if &*item_name.as_str() == doc_alias {
+ return err_fn(meta.span(), "is the same as the item's name");
+ }
+ true
+ }
+
+ fn check_doc_alias(&self, meta: &NestedMetaItem, hir_id: HirId, target: Target) -> bool {
+ if let Some(values) = meta.meta_item_list() {
+ let mut errors = 0;
+ for v in values {
+ match v.literal() {
+ Some(l) => match l.kind {
+ LitKind::Str(s, _) => {
+ if !self.check_doc_alias_value(v, &s.as_str(), hir_id, target, true) {
+ errors += 1;
+ }
+ }
+ _ => {
+ self.tcx
+ .sess
+ .struct_span_err(
+ v.span(),
+ "`#[doc(alias(\"a\"))]` expects string literals",
+ )
+ .emit();
+ errors += 1;
+ }
+ },
+ None => {
+ self.tcx
+ .sess
+ .struct_span_err(
+ v.span(),
+ "`#[doc(alias(\"a\"))]` expects string literals",
+ )
+ .emit();
+ errors += 1;
+ }
+ }
+ }
+ errors == 0
+ } else if let Some(doc_alias) = meta.value_str().map(|s| s.to_string()) {
+ self.check_doc_alias_value(meta, &doc_alias, hir_id, target, false)
+ } else {
self.tcx
.sess
.struct_span_err(
meta.span(),
- &format!("`#[doc(alias = \"...\")]` is the same as the item's name"),
+ "doc alias attribute expects a string `#[doc(alias = \"a\")]` or a list of \
+ strings `#[doc(alias(\"a\", \"b\"))]`",
)
.emit();
- return false;
+ false
}
- true
}
fn check_doc_keyword(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool {
.struct_span_err(
meta.span(),
&format!(
- "`#![doc({} = \"...\")]` isn't allowed as a crate level attribute",
+ "`#![doc({} = \"...\")]` isn't allowed as a crate-level attribute",
attr_name,
),
)
}
fn check_doc_attrs(&self, attr: &Attribute, hir_id: HirId, target: Target) -> bool {
- if let Some(mi) = attr.meta() {
- if let Some(list) = mi.meta_item_list() {
- for meta in list {
- if meta.has_name(sym::alias) {
- if !self.check_attr_crate_level(meta, hir_id, "alias")
- || !self.check_doc_alias(meta, hir_id, target)
+ let mut is_valid = true;
+
+ if let Some(list) = attr.meta().and_then(|mi| mi.meta_item_list().map(|l| l.to_vec())) {
+ for meta in list {
+ if let Some(i_meta) = meta.meta_item() {
+ match i_meta.name_or_empty() {
+ sym::alias
+ if !self.check_attr_crate_level(&meta, hir_id, "alias")
+ || !self.check_doc_alias(&meta, hir_id, target) =>
{
- return false;
+ is_valid = false
}
- } else if meta.has_name(sym::keyword) {
- if !self.check_attr_crate_level(meta, hir_id, "keyword")
- || !self.check_doc_keyword(meta, hir_id)
+
+ sym::keyword
+ if !self.check_attr_crate_level(&meta, hir_id, "keyword")
+ || !self.check_doc_keyword(&meta, hir_id) =>
{
- return false;
+ is_valid = false
+ }
+
+ sym::test if CRATE_HIR_ID != hir_id => {
+ self.tcx.struct_span_lint_hir(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ meta.span(),
+ |lint| {
+ lint.build(
+ "`#![doc(test(...)]` is only allowed \
+ as a crate-level attribute",
+ )
+ .emit();
+ },
+ );
+ is_valid = false;
+ }
+
+ // no_default_passes: deprecated
+ // passes: deprecated
+ // plugins: removed, but rustdoc warns about it itself
+ sym::alias
+ | sym::cfg
+ | sym::hidden
+ | sym::html_favicon_url
+ | sym::html_logo_url
+ | sym::html_no_source
+ | sym::html_playground_url
+ | sym::html_root_url
+ | sym::include
+ | sym::inline
+ | sym::issue_tracker_base_url
+ | sym::keyword
+ | sym::masked
+ | sym::no_default_passes
+ | sym::no_inline
+ | sym::passes
+ | sym::plugins
+ | sym::primitive
+ | sym::spotlight
+ | sym::test => {}
+
+ _ => {
+ self.tcx.struct_span_lint_hir(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ i_meta.span,
+ |lint| {
+ let msg = format!(
+ "unknown `doc` attribute `{}`",
+ rustc_ast_pretty::pprust::path_to_string(&i_meta.path),
+ );
+ lint.build(&msg).emit();
+ },
+ );
+ is_valid = false;
}
}
+ } else {
+ self.tcx.struct_span_lint_hir(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ meta.span(),
+ |lint| {
+ lint.build(&format!("invalid `doc` attribute")).emit();
+ },
+ );
+ is_valid = false;
}
}
}
- true
+
+ is_valid
}
/// Checks if `#[cold]` is applied to a non-function. Returns `true` if valid.
}
}
+ fn check_rustc_layout_scalar_valid_range(
+ &self,
+ attr: &Attribute,
+ span: &Span,
+ target: Target,
+ ) -> bool {
+ if target != Target::Struct {
+ self.tcx
+ .sess
+ .struct_span_err(attr.span, "attribute should be applied to a struct")
+ .span_label(*span, "not a struct")
+ .emit();
+ return false;
+ }
+
+ let list = match attr.meta_item_list() {
+ None => return false,
+ Some(it) => it,
+ };
+
+ if matches!(&list[..], &[NestedMetaItem::Literal(Lit { kind: LitKind::Int(..), .. })]) {
+ true
+ } else {
+ self.tcx
+ .sess
+ .struct_span_err(attr.span, "expected exactly one integer literal argument")
+ .emit();
+ false
+ }
+ }
+
+ /// Checks if `#[rustc_legacy_const_generics]` is applied to a function and has a valid argument.
+ fn check_rustc_legacy_const_generics(
+ &self,
+ attr: &Attribute,
+ span: &Span,
+ target: Target,
+ item: Option<ItemLike<'_>>,
+ ) -> bool {
+ let is_function = matches!(target, Target::Fn | Target::Method(..));
+ if !is_function {
+ self.tcx
+ .sess
+ .struct_span_err(attr.span, "attribute should be applied to a function")
+ .span_label(*span, "not a function")
+ .emit();
+ return false;
+ }
+
+ let list = match attr.meta_item_list() {
+ // The attribute form is validated on AST.
+ None => return false,
+ Some(it) => it,
+ };
+
+ let (decl, generics) = match item {
+ Some(ItemLike::Item(Item {
+ kind: ItemKind::Fn(FnSig { decl, .. }, generics, _),
+ ..
+ })) => (decl, generics),
+ _ => bug!("should be a function item"),
+ };
+
+ for param in generics.params {
+ match param.kind {
+ hir::GenericParamKind::Const { .. } => {}
+ _ => {
+ self.tcx
+ .sess
+ .struct_span_err(
+ attr.span,
+ "#[rustc_legacy_const_generics] functions must \
+ only have const generics",
+ )
+ .span_label(param.span, "non-const generic parameter")
+ .emit();
+ return false;
+ }
+ }
+ }
+
+ if list.len() != generics.params.len() {
+ self.tcx
+ .sess
+ .struct_span_err(
+ attr.span,
+ "#[rustc_legacy_const_generics] must have one index for each generic parameter",
+ )
+ .span_label(generics.span, "generic parameters")
+ .emit();
+ return false;
+ }
+
+ let arg_count = decl.inputs.len() as u128 + generics.params.len() as u128;
+ let mut invalid_args = vec![];
+ for meta in list {
+ if let Some(LitKind::Int(val, _)) = meta.literal().map(|lit| &lit.kind) {
+ if *val >= arg_count {
+ let span = meta.span();
+ self.tcx
+ .sess
+ .struct_span_err(span, "index exceeds number of arguments")
+ .span_label(
+ span,
+ format!(
+ "there {} only {} argument{}",
+ if arg_count != 1 { "are" } else { "is" },
+ arg_count,
+ pluralize!(arg_count)
+ ),
+ )
+ .emit();
+ return false;
+ }
+ } else {
+ invalid_args.push(meta.span());
+ }
+ }
+
+ if !invalid_args.is_empty() {
+ self.tcx
+ .sess
+ .struct_span_err(invalid_args, "arguments should be non-negative integers")
+ .emit();
+ false
+ } else {
+ true
+ }
+ }
+
/// Checks if `#[link_section]` is applied to a function or static.
fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
match target {
fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
let target = Target::from_item(item);
- self.check_attributes(
- item.hir_id,
- item.attrs,
- &item.span,
- target,
- Some(ItemLike::Item(item)),
- );
+ self.check_attributes(item.hir_id(), &item.span, target, Some(ItemLike::Item(item)));
intravisit::walk_item(self, item)
}
fn visit_generic_param(&mut self, generic_param: &'tcx hir::GenericParam<'tcx>) {
let target = Target::from_generic_param(generic_param);
- self.check_attributes(
- generic_param.hir_id,
- generic_param.attrs,
- &generic_param.span,
- target,
- None,
- );
+ self.check_attributes(generic_param.hir_id, &generic_param.span, target, None);
intravisit::walk_generic_param(self, generic_param)
}
fn visit_trait_item(&mut self, trait_item: &'tcx TraitItem<'tcx>) {
let target = Target::from_trait_item(trait_item);
- self.check_attributes(trait_item.hir_id, &trait_item.attrs, &trait_item.span, target, None);
+ self.check_attributes(trait_item.hir_id(), &trait_item.span, target, None);
intravisit::walk_trait_item(self, trait_item)
}
- fn visit_struct_field(&mut self, struct_field: &'tcx hir::StructField<'tcx>) {
- self.check_attributes(
- struct_field.hir_id,
- &struct_field.attrs,
- &struct_field.span,
- Target::Field,
- None,
- );
- intravisit::walk_struct_field(self, struct_field);
+ fn visit_field_def(&mut self, struct_field: &'tcx hir::FieldDef<'tcx>) {
+ self.check_attributes(struct_field.hir_id, &struct_field.span, Target::Field, None);
+ intravisit::walk_field_def(self, struct_field);
}
fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
- self.check_attributes(arm.hir_id, &arm.attrs, &arm.span, Target::Arm, None);
+ self.check_attributes(arm.hir_id, &arm.span, Target::Arm, None);
intravisit::walk_arm(self, arm);
}
- fn visit_macro_def(&mut self, macro_def: &'tcx hir::MacroDef<'tcx>) {
- self.check_attributes(
- macro_def.hir_id,
- ¯o_def.attrs,
- ¯o_def.span,
- Target::MacroDef,
- None,
- );
- intravisit::walk_macro_def(self, macro_def);
- }
-
fn visit_foreign_item(&mut self, f_item: &'tcx ForeignItem<'tcx>) {
let target = Target::from_foreign_item(f_item);
self.check_attributes(
- f_item.hir_id,
- &f_item.attrs,
+ f_item.hir_id(),
&f_item.span,
target,
Some(ItemLike::ForeignItem(f_item)),
fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
let target = target_from_impl_item(self.tcx, impl_item);
- self.check_attributes(impl_item.hir_id, &impl_item.attrs, &impl_item.span, target, None);
+ self.check_attributes(impl_item.hir_id(), &impl_item.span, target, None);
intravisit::walk_impl_item(self, impl_item)
}
fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
// When checking statements ignore expressions, they will be checked later.
if let hir::StmtKind::Local(ref l) = stmt.kind {
- self.check_attributes(l.hir_id, &l.attrs, &stmt.span, Target::Statement, None);
+ self.check_attributes(l.hir_id, &stmt.span, Target::Statement, None);
}
intravisit::walk_stmt(self, stmt)
}
_ => Target::Expression,
};
- self.check_attributes(expr.hir_id, &expr.attrs, &expr.span, target, None);
+ self.check_attributes(expr.hir_id, &expr.span, target, None);
intravisit::walk_expr(self, expr)
}
generics: &'tcx hir::Generics<'tcx>,
item_id: HirId,
) {
- self.check_attributes(variant.id, variant.attrs, &variant.span, Target::Variant, None);
+ self.check_attributes(variant.id, &variant.span, Target::Variant, None);
intravisit::walk_variant(self, variant, generics, item_id)
}
+
+ fn visit_macro_def(&mut self, macro_def: &'tcx hir::MacroDef<'tcx>) {
+ self.check_attributes(macro_def.hir_id(), ¯o_def.span, Target::MacroDef, None);
+ intravisit::walk_macro_def(self, macro_def);
+ }
+
+ fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+ self.check_attributes(param.hir_id, ¶m.span, Target::Param, None);
+
+ intravisit::walk_param(self, param);
+ }
}
fn is_c_like_enum(item: &Item<'_>) -> bool {
tcx.hir().visit_exported_macros_in_krate(check_attr_visitor);
check_invalid_macro_level_attr(tcx, tcx.hir().krate().non_exported_macro_attrs);
if module_def_id.is_top_level_module() {
- check_attr_visitor.check_attributes(
- CRATE_HIR_ID,
- tcx.hir().krate_attrs(),
- &DUMMY_SP,
- Target::Mod,
- None,
- );
+ check_attr_visitor.check_attributes(CRATE_HIR_ID, &DUMMY_SP, Target::Mod, None);
check_invalid_crate_level_attr(tcx, tcx.hir().krate_attrs());
}
}