summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_passes
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_passes')
-rw-r--r--compiler/rustc_passes/Cargo.toml24
-rw-r--r--compiler/rustc_passes/src/check_attr.rs2217
-rw-r--r--compiler/rustc_passes/src/check_const.rs236
-rw-r--r--compiler/rustc_passes/src/dead.rs960
-rw-r--r--compiler/rustc_passes/src/debugger_visualizer.rs99
-rw-r--r--compiler/rustc_passes/src/diagnostic_items.rs113
-rw-r--r--compiler/rustc_passes/src/entry.rs231
-rw-r--r--compiler/rustc_passes/src/errors.rs645
-rw-r--r--compiler/rustc_passes/src/hir_id_validator.rs164
-rw-r--r--compiler/rustc_passes/src/hir_stats.rs344
-rw-r--r--compiler/rustc_passes/src/lang_items.rs278
-rw-r--r--compiler/rustc_passes/src/layout_test.rs132
-rw-r--r--compiler/rustc_passes/src/lib.rs59
-rw-r--r--compiler/rustc_passes/src/lib_features.rs138
-rw-r--r--compiler/rustc_passes/src/liveness.rs1687
-rw-r--r--compiler/rustc_passes/src/liveness/rwu_table.rs145
-rw-r--r--compiler/rustc_passes/src/loops.rs287
-rw-r--r--compiler/rustc_passes/src/naked_functions.rs335
-rw-r--r--compiler/rustc_passes/src/reachable.rs417
-rw-r--r--compiler/rustc_passes/src/stability.rs1063
-rw-r--r--compiler/rustc_passes/src/upvars.rs97
-rw-r--r--compiler/rustc_passes/src/weak_lang_items.rs91
22 files changed, 9762 insertions, 0 deletions
diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml
new file mode 100644
index 000000000..faa9c493d
--- /dev/null
+++ b/compiler/rustc_passes/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "rustc_passes"
+version = "0.0.0"
+edition = "2021"
+
+[dependencies]
+tracing = "0.1"
+itertools = "0.10.1"
+rustc_middle = { path = "../rustc_middle" }
+rustc_attr = { path = "../rustc_attr" }
+rustc_data_structures = { path = "../rustc_data_structures" }
+rustc_errors = { path = "../rustc_errors" }
+rustc_expand = { path = "../rustc_expand" }
+rustc_hir = { path = "../rustc_hir" }
+rustc_index = { path = "../rustc_index" }
+rustc_session = { path = "../rustc_session" }
+rustc_target = { path = "../rustc_target" }
+rustc_macros = { path = "../rustc_macros" }
+rustc_ast = { path = "../rustc_ast" }
+rustc_serialize = { path = "../rustc_serialize" }
+rustc_span = { path = "../rustc_span" }
+rustc_lexer = { path = "../rustc_lexer" }
+rustc_ast_pretty = { path = "../rustc_ast_pretty" }
+rustc_feature = { path = "../rustc_feature" }
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
new file mode 100644
index 000000000..a2ac329f2
--- /dev/null
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -0,0 +1,2217 @@
+//! This module implements some validity checks for attributes.
+//! In particular it verifies that `#[inline]` and `#[repr]` attributes are
+//! attached to items that actually support them and if there are
+//! conflicts between multiple such attributes attached to the same
+//! item.
+
+use crate::errors;
+use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::{fluent, struct_span_err, Applicability, MultiSpan};
+use rustc_expand::base::resolve_path;
+use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
+use rustc_hir as hir;
+use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{self, FnSig, ForeignItem, HirId, Item, ItemKind, TraitItem, CRATE_HIR_ID};
+use rustc_hir::{MethodKind, Target};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::lint::builtin::{
+ CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES,
+};
+use rustc_session::parse::feature_err;
+use rustc_span::symbol::{kw, sym, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use rustc_target::spec::abi::Abi;
+use std::collections::hash_map::Entry;
+
+pub(crate) fn target_from_impl_item<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ impl_item: &hir::ImplItem<'_>,
+) -> Target {
+ 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 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(),
+ _ => bug!("parent of an ImplItem must be an Impl"),
+ };
+ if containing_impl_is_for_trait {
+ Target::Method(MethodKind::Trait { body: true })
+ } else {
+ Target::Method(MethodKind::Inherent)
+ }
+ }
+ hir::ImplItemKind::TyAlias(..) => Target::AssocTy,
+ }
+}
+
+#[derive(Clone, Copy)]
+enum ItemLike<'tcx> {
+ Item(&'tcx Item<'tcx>),
+ ForeignItem,
+}
+
+struct CheckAttrVisitor<'tcx> {
+ tcx: TyCtxt<'tcx>,
+}
+
+impl CheckAttrVisitor<'_> {
+ /// Checks any attribute.
+ fn check_attributes(
+ &self,
+ hir_id: HirId,
+ span: Span,
+ target: Target,
+ item: Option<ItemLike<'_>>,
+ ) {
+ let mut doc_aliases = FxHashMap::default();
+ let mut is_valid = true;
+ let mut specified_inline = None;
+ let mut seen = FxHashMap::default();
+ let attrs = self.tcx.hir().attrs(hir_id);
+ for attr in attrs {
+ let attr_is_valid = match attr.name_or_empty() {
+ sym::inline => self.check_inline(hir_id, attr, span, target),
+ sym::no_coverage => self.check_no_coverage(hir_id, attr, span, target),
+ sym::non_exhaustive => self.check_non_exhaustive(hir_id, attr, span, target),
+ sym::marker => self.check_marker(hir_id, attr, span, target),
+ sym::rustc_must_implement_one_of => {
+ self.check_rustc_must_implement_one_of(attr, span, target)
+ }
+ sym::target_feature => self.check_target_feature(hir_id, attr, span, target),
+ sym::thread_local => self.check_thread_local(attr, span, target),
+ sym::track_caller => {
+ self.check_track_caller(hir_id, attr.span, attrs, span, target)
+ }
+ sym::doc => self.check_doc_attrs(
+ attr,
+ hir_id,
+ target,
+ &mut specified_inline,
+ &mut doc_aliases,
+ ),
+ sym::no_link => self.check_no_link(hir_id, &attr, span, target),
+ sym::export_name => self.check_export_name(hir_id, &attr, span, target),
+ sym::rustc_layout_scalar_valid_range_start
+ | sym::rustc_layout_scalar_valid_range_end => {
+ self.check_rustc_layout_scalar_valid_range(&attr, span, target)
+ }
+ sym::allow_internal_unstable => {
+ self.check_allow_internal_unstable(hir_id, &attr, span, target, &attrs)
+ }
+ sym::debugger_visualizer => self.check_debugger_visualizer(&attr, target),
+ sym::rustc_allow_const_fn_unstable => {
+ self.check_rustc_allow_const_fn_unstable(hir_id, &attr, span, target)
+ }
+ sym::rustc_std_internal_symbol => {
+ self.check_rustc_std_internal_symbol(&attr, span, target)
+ }
+ sym::naked => self.check_naked(hir_id, attr, span, target),
+ sym::rustc_legacy_const_generics => {
+ self.check_rustc_legacy_const_generics(&attr, span, target, item)
+ }
+ sym::rustc_lint_query_instability => {
+ self.check_rustc_lint_query_instability(&attr, span, target)
+ }
+ sym::rustc_lint_diagnostics => {
+ self.check_rustc_lint_diagnostics(&attr, span, target)
+ }
+ sym::rustc_lint_opt_ty => self.check_rustc_lint_opt_ty(&attr, span, target),
+ sym::rustc_lint_opt_deny_field_access => {
+ self.check_rustc_lint_opt_deny_field_access(&attr, span, target)
+ }
+ sym::rustc_clean
+ | sym::rustc_dirty
+ | sym::rustc_if_this_changed
+ | sym::rustc_then_this_would_need => self.check_rustc_dirty_clean(&attr),
+ sym::cmse_nonsecure_entry => self.check_cmse_nonsecure_entry(attr, span, target),
+ sym::const_trait => self.check_const_trait(attr, span, target),
+ sym::must_not_suspend => self.check_must_not_suspend(&attr, span, target),
+ sym::must_use => self.check_must_use(hir_id, &attr, span, target),
+ sym::rustc_pass_by_value => self.check_pass_by_value(&attr, span, target),
+ sym::rustc_allow_incoherent_impl => {
+ self.check_allow_incoherent_impl(&attr, span, target)
+ }
+ sym::rustc_has_incoherent_inherent_impls => {
+ self.check_has_incoherent_inherent_impls(&attr, span, target)
+ }
+ sym::rustc_const_unstable
+ | sym::rustc_const_stable
+ | sym::unstable
+ | sym::stable
+ | sym::rustc_allowed_through_unstable_modules
+ | sym::rustc_promotable => self.check_stability_promotable(&attr, span, target),
+ _ => true,
+ };
+ is_valid &= attr_is_valid;
+
+ // lint-only checks
+ match attr.name_or_empty() {
+ sym::cold => self.check_cold(hir_id, attr, span, target),
+ sym::link => self.check_link(hir_id, attr, span, target),
+ sym::link_name => self.check_link_name(hir_id, attr, span, target),
+ sym::link_section => self.check_link_section(hir_id, attr, span, target),
+ sym::no_mangle => self.check_no_mangle(hir_id, attr, span, target),
+ sym::deprecated => self.check_deprecated(hir_id, attr, span, target),
+ sym::macro_use | sym::macro_escape => self.check_macro_use(hir_id, attr, target),
+ sym::path => self.check_generic_attr(hir_id, attr, target, &[Target::Mod]),
+ sym::plugin_registrar => self.check_plugin_registrar(hir_id, attr, target),
+ sym::macro_export => self.check_macro_export(hir_id, attr, target),
+ sym::ignore | sym::should_panic | sym::proc_macro_derive => {
+ self.check_generic_attr(hir_id, attr, target, &[Target::Fn])
+ }
+ sym::automatically_derived => {
+ self.check_generic_attr(hir_id, attr, target, &[Target::Impl])
+ }
+ sym::no_implicit_prelude => {
+ self.check_generic_attr(hir_id, attr, target, &[Target::Mod])
+ }
+ _ => {}
+ }
+
+ let builtin = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
+
+ if hir_id != CRATE_HIR_ID {
+ if let Some(BuiltinAttribute { type_: AttributeType::CrateLevel, .. }) =
+ attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name))
+ {
+ match attr.style {
+ ast::AttrStyle::Outer => self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::OuterCrateLevelAttr,
+ ),
+ ast::AttrStyle::Inner => self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::InnerCrateLevelAttr,
+ ),
+ }
+ }
+ }
+
+ if let Some(BuiltinAttribute { duplicates, .. }) = builtin {
+ check_duplicates(self.tcx, attr, hir_id, *duplicates, &mut seen);
+ }
+
+ self.check_unused_attribute(hir_id, attr)
+ }
+
+ if !is_valid {
+ return;
+ }
+
+ // FIXME(@lcnr): this doesn't belong here.
+ if matches!(target, Target::Closure | Target::Fn | Target::Method(_) | Target::ForeignFn) {
+ self.tcx.ensure().codegen_fn_attrs(self.tcx.hir().local_def_id(hir_id));
+ }
+
+ self.check_repr(attrs, span, target, item, hir_id);
+ self.check_used(attrs, target);
+ }
+
+ fn inline_attr_str_error_with_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::IgnoredAttrWithMacro { sym },
+ );
+ }
+
+ fn inline_attr_str_error_without_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::IgnoredAttr { sym },
+ );
+ }
+
+ /// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid.
+ fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::Fn
+ | Target::Closure
+ | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
+ Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::IgnoredInlineAttrFnProto,
+ );
+ true
+ }
+ // FIXME(#65833): We permit associated consts to have an `#[inline]` attribute with
+ // just a lint, because we previously erroneously allowed it and some crates used it
+ // accidentally, to to be compatible with crates depending on them, we can't throw an
+ // error here.
+ Target::AssocConst => {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::IgnoredInlineAttrConstants,
+ );
+ true
+ }
+ // FIXME(#80564): Same for fields, arms, and macro defs
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "inline");
+ true
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::InlineNotFnOrClosure {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ /// Checks if a `#[no_coverage]` is applied directly to a function
+ fn check_no_coverage(
+ &self,
+ hir_id: HirId,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ // no_coverage on function is fine
+ Target::Fn
+ | Target::Closure
+ | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
+
+ // function prototypes can't be covered
+ Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::IgnoredNoCoverageFnProto,
+ );
+ true
+ }
+
+ Target::Mod | Target::ForeignMod | Target::Impl | Target::Trait => {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::IgnoredNoCoveragePropagate,
+ );
+ true
+ }
+
+ Target::Expression | Target::Statement | Target::Arm => {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::IgnoredNoCoverageFnDefn,
+ );
+ true
+ }
+
+ _ => {
+ self.tcx.sess.emit_err(errors::IgnoredNoCoverageNotCoverable {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ fn check_generic_attr(
+ &self,
+ hir_id: HirId,
+ attr: &Attribute,
+ target: Target,
+ allowed_targets: &[Target],
+ ) {
+ if !allowed_targets.iter().any(|t| t == &target) {
+ let name = attr.name_or_empty();
+ let mut i = allowed_targets.iter();
+ // Pluralize
+ let b = i.next().map_or_else(String::new, |t| t.to_string() + "s");
+ let supported_names = i.enumerate().fold(b, |mut b, (i, allowed_target)| {
+ if allowed_targets.len() > 2 && i == allowed_targets.len() - 2 {
+ b.push_str(", and ");
+ } else if allowed_targets.len() == 2 && i == allowed_targets.len() - 2 {
+ b.push_str(" and ");
+ } else {
+ b.push_str(", ");
+ }
+ // Pluralize
+ b.push_str(&(allowed_target.to_string() + "s"));
+ b
+ });
+ self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
+ lint.build(&format!("`#[{name}]` only has an effect on {}", supported_names))
+ .emit();
+ });
+ }
+ }
+
+ /// Checks if `#[naked]` is applied to a function definition.
+ fn check_naked(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::Fn
+ | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[allow_internal_unstable]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "naked");
+ true
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ /// Checks if `#[cmse_nonsecure_entry]` is applied to a function definition.
+ fn check_cmse_nonsecure_entry(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::Fn
+ | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
+ _ => {
+ self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ /// Checks if a `#[track_caller]` is applied to a non-naked function. Returns `true` if valid.
+ fn check_track_caller(
+ &self,
+ hir_id: HirId,
+ attr_span: Span,
+ attrs: &[Attribute],
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ _ if attrs.iter().any(|attr| attr.has_name(sym::naked)) => {
+ self.tcx.sess.emit_err(errors::NakedTrackedCaller { attr_span });
+ false
+ }
+ Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => true,
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[track_caller]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ for attr in attrs {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "track_caller");
+ }
+ true
+ }
+ _ => {
+ self.tcx
+ .sess
+ .emit_err(errors::TrackedCallerWrongLocation { attr_span, defn_span: span });
+ false
+ }
+ }
+ }
+
+ /// Checks if the `#[non_exhaustive]` attribute on an `item` is valid. Returns `true` if valid.
+ fn check_non_exhaustive(
+ &self,
+ hir_id: HirId,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ Target::Struct | Target::Enum | Target::Variant => true,
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[non_exhaustive]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "non_exhaustive");
+ true
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::NonExhaustiveWrongLocation {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ /// Checks if the `#[marker]` attribute on an `item` is valid. Returns `true` if valid.
+ fn check_marker(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::Trait => true,
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[marker]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "marker");
+ true
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToTrait {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ /// Checks if the `#[rustc_must_implement_one_of]` attribute on a `target` is valid. Returns `true` if valid.
+ fn check_rustc_must_implement_one_of(
+ &self,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ Target::Trait => true,
+ _ => {
+ self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToTrait {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ /// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid.
+ fn check_target_feature(
+ &self,
+ hir_id: HirId,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ Target::Fn
+ | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
+ // FIXME: #[target_feature] was previously erroneously allowed on statements and some
+ // crates used this, so only emit a warning.
+ Target::Statement => {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::TargetFeatureOnStatement,
+ );
+ true
+ }
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[target_feature]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "target_feature");
+ true
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ /// Checks if the `#[thread_local]` attribute on `item` is valid. Returns `true` if valid.
+ fn check_thread_local(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::ForeignStatic | Target::Static => true,
+ _ => {
+ self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToStatic {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ }
+ }
+ }
+
+ fn doc_attr_str_error(&self, meta: &NestedMetaItem, attr_name: &str) {
+ self.tcx.sess.emit_err(errors::DocExpectStr { attr_span: meta.span(), attr_name });
+ }
+
+ fn check_doc_alias_value(
+ &self,
+ meta: &NestedMetaItem,
+ doc_alias: Symbol,
+ hir_id: HirId,
+ target: Target,
+ is_list: bool,
+ aliases: &mut FxHashMap<String, Span>,
+ ) -> bool {
+ let tcx = self.tcx;
+ let span = meta.name_value_literal_span().unwrap_or_else(|| meta.span());
+ let attr_str =
+ &format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" });
+ if doc_alias == kw::Empty {
+ tcx.sess.emit_err(errors::DocAliasEmpty { span, attr_str });
+ return false;
+ }
+
+ let doc_alias_str = doc_alias.as_str();
+ if let Some(c) = doc_alias_str
+ .chars()
+ .find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
+ {
+ tcx.sess.emit_err(errors::DocAliasBadChar { span, attr_str, char_: c });
+ return false;
+ }
+ if doc_alias_str.starts_with(' ') || doc_alias_str.ends_with(' ') {
+ tcx.sess.emit_err(errors::DocAliasStartEnd { span, attr_str });
+ return false;
+ }
+
+ let span = meta.span();
+ if let Some(location) = match target {
+ Target::AssocTy => {
+ let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
+ let containing_item = self.tcx.hir().expect_item(parent_hir_id);
+ if Target::from_item(containing_item) == Target::Impl {
+ Some("type alias in implementation block")
+ } else {
+ None
+ }
+ }
+ Target::AssocConst => {
+ let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
+ let containing_item = self.tcx.hir().expect_item(parent_hir_id);
+ // We can't link to trait impl's consts.
+ let err = "associated constant in trait implementation block";
+ match containing_item.kind {
+ ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) => Some(err),
+ _ => None,
+ }
+ }
+ // we check the validity of params elsewhere
+ Target::Param => return false,
+ Target::Expression
+ | Target::Statement
+ | Target::Arm
+ | Target::ForeignMod
+ | Target::Closure
+ | Target::Impl => Some(target.name()),
+ Target::ExternCrate
+ | Target::Use
+ | Target::Static
+ | Target::Const
+ | Target::Fn
+ | Target::Mod
+ | Target::GlobalAsm
+ | Target::TyAlias
+ | Target::OpaqueTy
+ | Target::Enum
+ | Target::Variant
+ | Target::Struct
+ | Target::Field
+ | Target::Union
+ | Target::Trait
+ | Target::TraitAlias
+ | Target::Method(..)
+ | Target::ForeignFn
+ | Target::ForeignStatic
+ | Target::ForeignTy
+ | Target::GenericParam(..)
+ | Target::MacroDef => None,
+ } {
+ tcx.sess.emit_err(errors::DocAliasBadLocation { span, attr_str, location });
+ return false;
+ }
+ let item_name = self.tcx.hir().name(hir_id);
+ if item_name == doc_alias {
+ tcx.sess.emit_err(errors::DocAliasNotAnAlias { span, attr_str });
+ return false;
+ }
+ if let Err(entry) = aliases.try_insert(doc_alias_str.to_owned(), span) {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ span,
+ errors::DocAliasDuplicated { first_defn: *entry.entry.get() },
+ );
+ }
+ true
+ }
+
+ fn check_doc_alias(
+ &self,
+ meta: &NestedMetaItem,
+ hir_id: HirId,
+ target: Target,
+ aliases: &mut FxHashMap<String, Span>,
+ ) -> 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, hir_id, target, true, aliases) {
+ errors += 1;
+ }
+ }
+ _ => {
+ self.tcx
+ .sess
+ .emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
+ errors += 1;
+ }
+ },
+ None => {
+ self.tcx.sess.emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
+ errors += 1;
+ }
+ }
+ }
+ errors == 0
+ } else if let Some(doc_alias) = meta.value_str() {
+ self.check_doc_alias_value(meta, doc_alias, hir_id, target, false, aliases)
+ } else {
+ self.tcx.sess.emit_err(errors::DocAliasMalformed { span: meta.span() });
+ false
+ }
+ }
+
+ fn check_doc_keyword(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool {
+ let doc_keyword = meta.value_str().unwrap_or(kw::Empty);
+ if doc_keyword == kw::Empty {
+ self.doc_attr_str_error(meta, "keyword");
+ return false;
+ }
+ match self.tcx.hir().find(hir_id).and_then(|node| match node {
+ hir::Node::Item(item) => Some(&item.kind),
+ _ => None,
+ }) {
+ Some(ItemKind::Mod(ref module)) => {
+ if !module.item_ids.is_empty() {
+ self.tcx.sess.emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
+ return false;
+ }
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::DocKeywordNotMod { span: meta.span() });
+ return false;
+ }
+ }
+ if !rustc_lexer::is_ident(doc_keyword.as_str()) {
+ self.tcx.sess.emit_err(errors::DocKeywordInvalidIdent {
+ span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
+ doc_keyword,
+ });
+ return false;
+ }
+ true
+ }
+
+ fn check_doc_fake_variadic(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool {
+ match self.tcx.hir().find(hir_id).and_then(|node| match node {
+ hir::Node::Item(item) => Some(&item.kind),
+ _ => None,
+ }) {
+ Some(ItemKind::Impl(ref i)) => {
+ let is_valid = matches!(&i.self_ty.kind, hir::TyKind::Tup([_]))
+ || if let hir::TyKind::BareFn(bare_fn_ty) = &i.self_ty.kind {
+ bare_fn_ty.decl.inputs.len() == 1
+ } else {
+ false
+ };
+ if !is_valid {
+ self.tcx.sess.emit_err(errors::DocFakeVariadicNotValid { span: meta.span() });
+ return false;
+ }
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::DocKeywordOnlyImpl { span: meta.span() });
+ return false;
+ }
+ }
+ true
+ }
+
+ /// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. Returns `true` if valid.
+ ///
+ /// A doc inlining attribute is invalid if it is applied to a non-`use` item, or
+ /// if there are conflicting attributes for one item.
+ ///
+ /// `specified_inline` is used to keep track of whether we have
+ /// already seen an inlining attribute for this item.
+ /// If so, `specified_inline` holds the value and the span of
+ /// the first `inline`/`no_inline` attribute.
+ fn check_doc_inline(
+ &self,
+ attr: &Attribute,
+ meta: &NestedMetaItem,
+ hir_id: HirId,
+ target: Target,
+ specified_inline: &mut Option<(bool, Span)>,
+ ) -> bool {
+ if target == Target::Use || target == Target::ExternCrate {
+ let do_inline = meta.name_or_empty() == sym::inline;
+ if let Some((prev_inline, prev_span)) = *specified_inline {
+ if do_inline != prev_inline {
+ let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]);
+ spans.push_span_label(prev_span, fluent::passes::doc_inline_conflict_first);
+ spans.push_span_label(meta.span(), fluent::passes::doc_inline_conflict_second);
+ self.tcx.sess.emit_err(errors::DocKeywordConflict { spans });
+ return false;
+ }
+ true
+ } else {
+ *specified_inline = Some((do_inline, meta.span()));
+ true
+ }
+ } else {
+ self.tcx.emit_spanned_lint(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ meta.span(),
+ errors::DocInlineOnlyUse {
+ attr_span: meta.span(),
+ item_span: (attr.style == AttrStyle::Outer)
+ .then(|| self.tcx.hir().span(hir_id)),
+ },
+ );
+ false
+ }
+ }
+
+ /// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
+ fn check_attr_not_crate_level(
+ &self,
+ meta: &NestedMetaItem,
+ hir_id: HirId,
+ attr_name: &str,
+ ) -> bool {
+ if CRATE_HIR_ID == hir_id {
+ self.tcx.sess.emit_err(errors::DocAttrNotCrateLevel { span: meta.span(), attr_name });
+ return false;
+ }
+ true
+ }
+
+ /// Checks that an attribute is used at the crate level. Returns `true` if valid.
+ fn check_attr_crate_level(
+ &self,
+ attr: &Attribute,
+ meta: &NestedMetaItem,
+ hir_id: HirId,
+ ) -> bool {
+ if hir_id != CRATE_HIR_ID {
+ self.tcx.struct_span_lint_hir(INVALID_DOC_ATTRIBUTES, hir_id, meta.span(), |lint| {
+ let mut err = lint.build(fluent::passes::attr_crate_level);
+ if attr.style == AttrStyle::Outer
+ && self.tcx.hir().get_parent_item(hir_id) == CRATE_DEF_ID
+ {
+ if let Ok(mut src) = self.tcx.sess.source_map().span_to_snippet(attr.span) {
+ src.insert(1, '!');
+ err.span_suggestion_verbose(
+ attr.span,
+ fluent::passes::suggestion,
+ src,
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ err.span_help(attr.span, fluent::passes::help);
+ }
+ }
+ err.note(fluent::passes::note).emit();
+ });
+ return false;
+ }
+ true
+ }
+
+ /// Checks that `doc(test(...))` attribute contains only valid attributes. Returns `true` if
+ /// valid.
+ fn check_test_attr(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool {
+ let mut is_valid = true;
+ if let Some(metas) = meta.meta_item_list() {
+ for i_meta in metas {
+ match i_meta.name_or_empty() {
+ sym::attr | sym::no_crate_inject => {}
+ _ => {
+ self.tcx.emit_spanned_lint(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ i_meta.span(),
+ errors::DocTestUnknown {
+ path: rustc_ast_pretty::pprust::path_to_string(
+ &i_meta.meta_item().unwrap().path,
+ ),
+ },
+ );
+ is_valid = false;
+ }
+ }
+ }
+ } else {
+ self.tcx.emit_spanned_lint(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ meta.span(),
+ errors::DocTestTakesList,
+ );
+ is_valid = false;
+ }
+ is_valid
+ }
+
+ /// Runs various checks on `#[doc]` attributes. Returns `true` if valid.
+ ///
+ /// `specified_inline` should be initialized to `None` and kept for the scope
+ /// of one item. Read the documentation of [`check_doc_inline`] for more information.
+ ///
+ /// [`check_doc_inline`]: Self::check_doc_inline
+ fn check_doc_attrs(
+ &self,
+ attr: &Attribute,
+ hir_id: HirId,
+ target: Target,
+ specified_inline: &mut Option<(bool, Span)>,
+ aliases: &mut FxHashMap<String, Span>,
+ ) -> bool {
+ let mut is_valid = true;
+
+ if let Some(mi) = attr.meta() && let Some(list) = mi.meta_item_list() {
+ for meta in list {
+ if let Some(i_meta) = meta.meta_item() {
+ match i_meta.name_or_empty() {
+ sym::alias
+ if !self.check_attr_not_crate_level(meta, hir_id, "alias")
+ || !self.check_doc_alias(meta, hir_id, target, aliases) =>
+ {
+ is_valid = false
+ }
+
+ sym::keyword
+ if !self.check_attr_not_crate_level(meta, hir_id, "keyword")
+ || !self.check_doc_keyword(meta, hir_id) =>
+ {
+ is_valid = false
+ }
+
+ sym::fake_variadic
+ if !self.check_attr_not_crate_level(meta, hir_id, "fake_variadic")
+ || !self.check_doc_fake_variadic(meta, hir_id) =>
+ {
+ is_valid = false
+ }
+
+ sym::html_favicon_url
+ | sym::html_logo_url
+ | sym::html_playground_url
+ | sym::issue_tracker_base_url
+ | sym::html_root_url
+ | sym::html_no_source
+ | sym::test
+ if !self.check_attr_crate_level(attr, meta, hir_id) =>
+ {
+ is_valid = false;
+ }
+
+ sym::inline | sym::no_inline
+ if !self.check_doc_inline(
+ attr,
+ meta,
+ hir_id,
+ target,
+ specified_inline,
+ ) =>
+ {
+ is_valid = false;
+ }
+
+ // no_default_passes: deprecated
+ // passes: deprecated
+ // plugins: removed, but rustdoc warns about it itself
+ sym::alias
+ | sym::cfg
+ | sym::cfg_hide
+ | sym::hidden
+ | sym::html_favicon_url
+ | sym::html_logo_url
+ | sym::html_no_source
+ | sym::html_playground_url
+ | sym::html_root_url
+ | sym::inline
+ | sym::issue_tracker_base_url
+ | sym::keyword
+ | sym::masked
+ | sym::no_default_passes
+ | sym::no_inline
+ | sym::notable_trait
+ | sym::passes
+ | sym::plugins
+ | sym::fake_variadic => {}
+
+ sym::test => {
+ if !self.check_test_attr(meta, hir_id) {
+ is_valid = false;
+ }
+ }
+
+ sym::primitive => {
+ if !self.tcx.features().rustdoc_internals {
+ self.tcx.emit_spanned_lint(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ i_meta.span,
+ errors::DocPrimitive,
+ );
+ }
+ }
+
+ _ => {
+ let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path);
+ if i_meta.has_name(sym::spotlight) {
+ self.tcx.emit_spanned_lint(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ i_meta.span,
+ errors::DocTestUnknownSpotlight {
+ path,
+ span: i_meta.span
+ }
+ );
+ } else if i_meta.has_name(sym::include) &&
+ let Some(value) = i_meta.value_str() {
+ let applicability = if list.len() == 1 {
+ Applicability::MachineApplicable
+ } else {
+ Applicability::MaybeIncorrect
+ };
+ // If there are multiple attributes, the suggestion would suggest
+ // deleting all of them, which is incorrect.
+ self.tcx.emit_spanned_lint(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ i_meta.span,
+ errors::DocTestUnknownInclude {
+ path,
+ value: value.to_string(),
+ inner: (attr.style == AttrStyle::Inner)
+ .then_some("!")
+ .unwrap_or(""),
+ sugg: (attr.meta().unwrap().span, applicability),
+ }
+ );
+ } else {
+ self.tcx.emit_spanned_lint(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ i_meta.span,
+ errors::DocTestUnknownAny { path }
+ );
+ }
+ is_valid = false;
+ }
+ }
+ } else {
+ self.tcx.emit_spanned_lint(
+ INVALID_DOC_ATTRIBUTES,
+ hir_id,
+ meta.span(),
+ errors::DocInvalid,
+ );
+ is_valid = false;
+ }
+ }
+ }
+
+ is_valid
+ }
+
+ /// Warns against some misuses of `#[pass_by_value]`
+ fn check_pass_by_value(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::Struct | Target::Enum | Target::TyAlias => true,
+ _ => {
+ self.tcx.sess.emit_err(errors::PassByValue { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ fn check_allow_incoherent_impl(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::Method(MethodKind::Inherent) => true,
+ _ => {
+ self.tcx.sess.emit_err(errors::AllowIncoherentImpl { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ fn check_has_incoherent_inherent_impls(
+ &self,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ Target::Trait | Target::Struct | Target::Enum | Target::Union | Target::ForeignTy => {
+ true
+ }
+ _ => {
+ self.tcx
+ .sess
+ .emit_err(errors::HasIncoherentInherentImpl { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ /// Warns against some misuses of `#[must_use]`
+ fn check_must_use(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
+ let node = self.tcx.hir().get(hir_id);
+ if let Some(kind) = node.fn_kind() && let rustc_hir::IsAsync::Async = kind.asyncness() {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::MustUseAsync { span }
+ );
+ }
+
+ if !matches!(
+ target,
+ Target::Fn
+ | Target::Enum
+ | Target::Struct
+ | Target::Union
+ | Target::Method(_)
+ | Target::ForeignFn
+ // `impl Trait` in return position can trip
+ // `unused_must_use` if `Trait` is marked as
+ // `#[must_use]`
+ | Target::Trait
+ ) {
+ let article = match target {
+ Target::ExternCrate
+ | Target::OpaqueTy
+ | Target::Enum
+ | Target::Impl
+ | Target::Expression
+ | Target::Arm
+ | Target::AssocConst
+ | Target::AssocTy => "an",
+ _ => "a",
+ };
+
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::MustUseNoEffect { article, target },
+ );
+ }
+
+ // For now, its always valid
+ true
+ }
+
+ /// Checks if `#[must_not_suspend]` is applied to a function. Returns `true` if valid.
+ fn check_must_not_suspend(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::Struct | Target::Enum | Target::Union | Target::Trait => true,
+ _ => {
+ self.tcx.sess.emit_err(errors::MustNotSuspend { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ /// Checks if `#[cold]` is applied to a non-function. Returns `true` if valid.
+ fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+ match target {
+ Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => {}
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[cold]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "cold");
+ }
+ _ => {
+ // FIXME: #[cold] was previously allowed on non-functions and some crates used
+ // this, so only emit a warning.
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::Cold { span },
+ );
+ }
+ }
+ }
+
+ /// Checks if `#[link]` is applied to an item other than a foreign module.
+ fn check_link(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+ if target == Target::ForeignMod
+ && let hir::Node::Item(item) = self.tcx.hir().get(hir_id)
+ && let Item { kind: ItemKind::ForeignMod { abi, .. }, .. } = item
+ && !matches!(abi, Abi::Rust | Abi::RustIntrinsic | Abi::PlatformIntrinsic)
+ {
+ return;
+ }
+
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::Link { span: (target != Target::ForeignMod).then_some(span) },
+ );
+ }
+
+ /// Checks if `#[link_name]` is applied to an item other than a foreign function or static.
+ fn check_link_name(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+ match target {
+ Target::ForeignFn | Target::ForeignStatic => {}
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[link_name]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_name");
+ }
+ _ => {
+ // FIXME: #[cold] was previously allowed on non-functions/statics and some crates
+ // used this, so only emit a warning.
+ let attr_span = matches!(target, Target::ForeignMod).then_some(attr.span);
+ if let Some(s) = attr.value_str() {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::LinkName { span, attr_span, value: s.as_str() },
+ );
+ } else {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::LinkName { span, attr_span, value: "..." },
+ );
+ };
+ }
+ }
+ }
+
+ /// Checks if `#[no_link]` is applied to an `extern crate`. Returns `true` if valid.
+ fn check_no_link(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::ExternCrate => true,
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[no_link]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_link");
+ true
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::NoLink { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ fn is_impl_item(&self, hir_id: HirId) -> bool {
+ matches!(self.tcx.hir().get(hir_id), hir::Node::ImplItem(..))
+ }
+
+ /// Checks if `#[export_name]` is applied to a function or static. Returns `true` if valid.
+ fn check_export_name(
+ &self,
+ hir_id: HirId,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ Target::Static | Target::Fn => true,
+ Target::Method(..) if self.is_impl_item(hir_id) => true,
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[export_name]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "export_name");
+ true
+ }
+ _ => {
+ self.tcx.sess.emit_err(errors::ExportName { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ fn check_rustc_layout_scalar_valid_range(
+ &self,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ if target != Target::Struct {
+ self.tcx.sess.emit_err(errors::RustcLayoutScalarValidRangeNotStruct {
+ attr_span: attr.span,
+ span,
+ });
+ return false;
+ }
+
+ let Some(list) = attr.meta_item_list() else {
+ return false;
+ };
+
+ if matches!(&list[..], &[NestedMetaItem::Literal(Lit { kind: LitKind::Int(..), .. })]) {
+ true
+ } else {
+ self.tcx.sess.emit_err(errors::RustcLayoutScalarValidRangeArg { attr_span: attr.span });
+ 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);
+ if !is_function {
+ self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ return false;
+ }
+
+ let Some(list) = attr.meta_item_list() else {
+ // The attribute form is validated on AST.
+ return false;
+ };
+
+ let Some(ItemLike::Item(Item {
+ kind: ItemKind::Fn(FnSig { decl, .. }, generics, _),
+ ..
+ })) = item else {
+ bug!("should be a function item");
+ };
+
+ for param in generics.params {
+ match param.kind {
+ hir::GenericParamKind::Const { .. } => {}
+ _ => {
+ self.tcx.sess.emit_err(errors::RustcLegacyConstGenericsOnly {
+ attr_span: attr.span,
+ param_span: param.span,
+ });
+ return false;
+ }
+ }
+ }
+
+ if list.len() != generics.params.len() {
+ self.tcx.sess.emit_err(errors::RustcLegacyConstGenericsIndex {
+ attr_span: attr.span,
+ generics_span: generics.span,
+ });
+ 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.emit_err(errors::RustcLegacyConstGenericsIndexExceed {
+ span,
+ arg_count: arg_count as usize,
+ });
+ return false;
+ }
+ } else {
+ invalid_args.push(meta.span());
+ }
+ }
+
+ if !invalid_args.is_empty() {
+ self.tcx.sess.emit_err(errors::RustcLegacyConstGenericsIndexNegative { invalid_args });
+ false
+ } else {
+ true
+ }
+ }
+
+ /// Helper function for checking that the provided attribute is only applied to a function or
+ /// method.
+ fn check_applied_to_fn_or_method(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+ let is_function = matches!(target, Target::Fn | Target::Method(..));
+ if !is_function {
+ self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn {
+ attr_span: attr.span,
+ defn_span: span,
+ });
+ false
+ } else {
+ true
+ }
+ }
+
+ /// Checks that the `#[rustc_lint_query_instability]` attribute is only applied to a function
+ /// or method.
+ fn check_rustc_lint_query_instability(
+ &self,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ self.check_applied_to_fn_or_method(attr, span, target)
+ }
+
+ /// Checks that the `#[rustc_lint_diagnostics]` attribute is only applied to a function or
+ /// method.
+ fn check_rustc_lint_diagnostics(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+ self.check_applied_to_fn_or_method(attr, span, target)
+ }
+
+ /// Checks that the `#[rustc_lint_opt_ty]` attribute is only applied to a struct.
+ fn check_rustc_lint_opt_ty(&self, attr: &Attribute, span: Span, target: Target) -> bool {
+ match target {
+ Target::Struct => true,
+ _ => {
+ self.tcx.sess.emit_err(errors::RustcLintOptTy { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ /// Checks that the `#[rustc_lint_opt_deny_field_access]` attribute is only applied to a field.
+ fn check_rustc_lint_opt_deny_field_access(
+ &self,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ Target::Field => true,
+ _ => {
+ self.tcx
+ .sess
+ .emit_err(errors::RustcLintOptDenyFieldAccess { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ /// Checks that the dep-graph debugging attributes are only present when the query-dep-graph
+ /// option is passed to the compiler.
+ fn check_rustc_dirty_clean(&self, attr: &Attribute) -> bool {
+ if self.tcx.sess.opts.unstable_opts.query_dep_graph {
+ true
+ } else {
+ self.tcx.sess.emit_err(errors::RustcDirtyClean { span: attr.span });
+ false
+ }
+ }
+
+ /// 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 {
+ Target::Static | Target::Fn | Target::Method(..) => {}
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[link_section]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_section");
+ }
+ _ => {
+ // FIXME: #[link_section] was previously allowed on non-functions/statics and some
+ // crates used this, so only emit a warning.
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::LinkSection { span },
+ );
+ }
+ }
+ }
+
+ /// Checks if `#[no_mangle]` is applied to a function or static.
+ fn check_no_mangle(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+ match target {
+ Target::Static | Target::Fn => {}
+ Target::Method(..) if self.is_impl_item(hir_id) => {}
+ // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
+ // `#[no_mangle]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_mangle");
+ }
+ // FIXME: #[no_mangle] was previously allowed on non-functions/statics, this should be an error
+ // The error should specify that the item that is wrong is specifically a *foreign* fn/static
+ // otherwise the error seems odd
+ Target::ForeignFn | Target::ForeignStatic => {
+ let foreign_item_kind = match target {
+ Target::ForeignFn => "function",
+ Target::ForeignStatic => "static",
+ _ => unreachable!(),
+ };
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::NoMangleForeign { span, attr_span: attr.span, foreign_item_kind },
+ );
+ }
+ _ => {
+ // FIXME: #[no_mangle] was previously allowed on non-functions/statics and some
+ // crates used this, so only emit a warning.
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::NoMangle { span },
+ );
+ }
+ }
+ }
+
+ /// Checks if the `#[repr]` attributes on `item` are valid.
+ fn check_repr(
+ &self,
+ attrs: &[Attribute],
+ span: Span,
+ target: Target,
+ item: Option<ItemLike<'_>>,
+ hir_id: HirId,
+ ) {
+ // Extract the names of all repr hints, e.g., [foo, bar, align] for:
+ // ```
+ // #[repr(foo)]
+ // #[repr(bar, align(8))]
+ // ```
+ let hints: Vec<_> = attrs
+ .iter()
+ .filter(|attr| attr.has_name(sym::repr))
+ .filter_map(|attr| attr.meta_item_list())
+ .flatten()
+ .collect();
+
+ let mut int_reprs = 0;
+ let mut is_c = false;
+ let mut is_simd = false;
+ let mut is_transparent = false;
+
+ for hint in &hints {
+ if !hint.is_meta_item() {
+ self.tcx.sess.emit_err(errors::ReprIdent { span: hint.span() });
+ continue;
+ }
+
+ let (article, allowed_targets) = match hint.name_or_empty() {
+ sym::C => {
+ is_c = true;
+ match target {
+ Target::Struct | Target::Union | Target::Enum => continue,
+ _ => ("a", "struct, enum, or union"),
+ }
+ }
+ sym::align => {
+ if let (Target::Fn, false) = (target, self.tcx.features().fn_align) {
+ feature_err(
+ &self.tcx.sess.parse_sess,
+ sym::fn_align,
+ hint.span(),
+ "`repr(align)` attributes on functions are unstable",
+ )
+ .emit();
+ }
+
+ match target {
+ Target::Struct | Target::Union | Target::Enum | Target::Fn => continue,
+ _ => ("a", "struct, enum, function, or union"),
+ }
+ }
+ sym::packed => {
+ if target != Target::Struct && target != Target::Union {
+ ("a", "struct or union")
+ } else {
+ continue;
+ }
+ }
+ sym::simd => {
+ is_simd = true;
+ if target != Target::Struct {
+ ("a", "struct")
+ } else {
+ continue;
+ }
+ }
+ sym::transparent => {
+ is_transparent = true;
+ match target {
+ Target::Struct | Target::Union | Target::Enum => continue,
+ _ => ("a", "struct, enum, or union"),
+ }
+ }
+ sym::i8
+ | sym::u8
+ | sym::i16
+ | sym::u16
+ | sym::i32
+ | sym::u32
+ | sym::i64
+ | sym::u64
+ | sym::i128
+ | sym::u128
+ | sym::isize
+ | sym::usize => {
+ int_reprs += 1;
+ if target != Target::Enum {
+ ("an", "enum")
+ } else {
+ continue;
+ }
+ }
+ _ => {
+ struct_span_err!(
+ self.tcx.sess,
+ hint.span(),
+ E0552,
+ "unrecognized representation hint"
+ )
+ .emit();
+
+ continue;
+ }
+ };
+
+ struct_span_err!(
+ self.tcx.sess,
+ hint.span(),
+ E0517,
+ "{}",
+ &format!("attribute should be applied to {article} {allowed_targets}")
+ )
+ .span_label(span, &format!("not {article} {allowed_targets}"))
+ .emit();
+ }
+
+ // Just point at all repr hints if there are any incompatibilities.
+ // This is not ideal, but tracking precisely which ones are at fault is a huge hassle.
+ let hint_spans = hints.iter().map(|hint| hint.span());
+
+ // Error on repr(transparent, <anything else>).
+ if is_transparent && hints.len() > 1 {
+ let hint_spans: Vec<_> = hint_spans.clone().collect();
+ struct_span_err!(
+ self.tcx.sess,
+ hint_spans,
+ E0692,
+ "transparent {} cannot have other repr hints",
+ target
+ )
+ .emit();
+ }
+ // Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
+ if (int_reprs > 1)
+ || (is_simd && is_c)
+ || (int_reprs == 1
+ && is_c
+ && item.map_or(false, |item| {
+ if let ItemLike::Item(item) = item {
+ return is_c_like_enum(item);
+ }
+ return false;
+ }))
+ {
+ self.tcx.emit_spanned_lint(
+ CONFLICTING_REPR_HINTS,
+ hir_id,
+ hint_spans.collect::<Vec<Span>>(),
+ errors::ReprConflicting,
+ );
+ }
+ }
+
+ fn check_used(&self, attrs: &[Attribute], target: Target) {
+ let mut used_linker_span = None;
+ let mut used_compiler_span = None;
+ for attr in attrs.iter().filter(|attr| attr.has_name(sym::used)) {
+ if target != Target::Static {
+ self.tcx.sess.emit_err(errors::UsedStatic { span: attr.span });
+ }
+ let inner = attr.meta_item_list();
+ match inner.as_deref() {
+ Some([item]) if item.has_name(sym::linker) => {
+ if used_linker_span.is_none() {
+ used_linker_span = Some(attr.span);
+ }
+ }
+ Some([item]) if item.has_name(sym::compiler) => {
+ if used_compiler_span.is_none() {
+ used_compiler_span = Some(attr.span);
+ }
+ }
+ Some(_) => {
+ // This error case is handled in rustc_typeck::collect.
+ }
+ None => {
+ // Default case (compiler) when arg isn't defined.
+ if used_compiler_span.is_none() {
+ used_compiler_span = Some(attr.span);
+ }
+ }
+ }
+ }
+ if let (Some(linker_span), Some(compiler_span)) = (used_linker_span, used_compiler_span) {
+ self.tcx
+ .sess
+ .emit_err(errors::UsedCompilerLinker { spans: vec![linker_span, compiler_span] });
+ }
+ }
+
+ /// Outputs an error for `#[allow_internal_unstable]` which can only be applied to macros.
+ /// (Allows proc_macro functions)
+ fn check_allow_internal_unstable(
+ &self,
+ hir_id: HirId,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ attrs: &[Attribute],
+ ) -> bool {
+ debug!("Checking target: {:?}", target);
+ match target {
+ Target::Fn => {
+ for attr in attrs {
+ if self.tcx.sess.is_proc_macro_attr(attr) {
+ debug!("Is proc macro attr");
+ return true;
+ }
+ }
+ debug!("Is not proc macro attr");
+ false
+ }
+ Target::MacroDef => true,
+ // FIXME(#80564): We permit struct fields and match arms to have an
+ // `#[allow_internal_unstable]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm => {
+ self.inline_attr_str_error_without_macro_def(
+ hir_id,
+ attr,
+ "allow_internal_unstable",
+ );
+ true
+ }
+ _ => {
+ self.tcx
+ .sess
+ .emit_err(errors::AllowInternalUnstable { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ /// Checks if the items on the `#[debugger_visualizer]` attribute are valid.
+ fn check_debugger_visualizer(&self, attr: &Attribute, target: Target) -> bool {
+ match target {
+ Target::Mod => {}
+ _ => {
+ self.tcx.sess.emit_err(errors::DebugVisualizerPlacement { span: attr.span });
+ return false;
+ }
+ }
+
+ let Some(hints) = attr.meta_item_list() else {
+ self.tcx.sess.emit_err(errors::DebugVisualizerInvalid { span: attr.span });
+ return false;
+ };
+
+ let hint = match hints.len() {
+ 1 => &hints[0],
+ _ => {
+ self.tcx.sess.emit_err(errors::DebugVisualizerInvalid { span: attr.span });
+ return false;
+ }
+ };
+
+ let Some(meta_item) = hint.meta_item() else {
+ self.tcx.sess.emit_err(errors::DebugVisualizerInvalid { span: attr.span });
+ return false;
+ };
+
+ let visualizer_path = match (meta_item.name_or_empty(), meta_item.value_str()) {
+ (sym::natvis_file, Some(value)) => value,
+ (sym::gdb_script_file, Some(value)) => value,
+ (_, _) => {
+ self.tcx.sess.emit_err(errors::DebugVisualizerInvalid { span: meta_item.span });
+ return false;
+ }
+ };
+
+ let file =
+ match resolve_path(&self.tcx.sess.parse_sess, visualizer_path.as_str(), attr.span) {
+ Ok(file) => file,
+ Err(mut err) => {
+ err.emit();
+ return false;
+ }
+ };
+
+ match std::fs::File::open(&file) {
+ Ok(_) => true,
+ Err(err) => {
+ self.tcx
+ .sess
+ .struct_span_err(
+ meta_item.span,
+ &format!("couldn't read {}: {}", file.display(), err),
+ )
+ .emit();
+ false
+ }
+ }
+ }
+
+ /// Outputs an error for `#[allow_internal_unstable]` which can only be applied to macros.
+ /// (Allows proc_macro functions)
+ fn check_rustc_allow_const_fn_unstable(
+ &self,
+ hir_id: HirId,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ Target::Fn | Target::Method(_)
+ if self.tcx.is_const_fn_raw(self.tcx.hir().local_def_id(hir_id).to_def_id()) =>
+ {
+ true
+ }
+ // FIXME(#80564): We permit struct fields and match arms to have an
+ // `#[allow_internal_unstable]` attribute with just a lint, because we previously
+ // erroneously allowed it and some crates used it accidentally, to to be compatible
+ // with crates depending on them, we can't throw an error here.
+ Target::Field | Target::Arm | Target::MacroDef => {
+ self.inline_attr_str_error_with_macro_def(hir_id, attr, "allow_internal_unstable");
+ true
+ }
+ _ => {
+ self.tcx
+ .sess
+ .emit_err(errors::RustcAllowConstFnUnstable { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ fn check_rustc_std_internal_symbol(
+ &self,
+ attr: &Attribute,
+ span: Span,
+ target: Target,
+ ) -> bool {
+ match target {
+ Target::Fn | Target::Static => true,
+ _ => {
+ self.tcx
+ .sess
+ .emit_err(errors::RustcStdInternalSymbol { attr_span: attr.span, span });
+ false
+ }
+ }
+ }
+
+ /// `#[const_trait]` only applies to traits.
+ fn check_const_trait(&self, attr: &Attribute, _span: Span, target: Target) -> bool {
+ match target {
+ Target::Trait => true,
+ _ => {
+ self.tcx.sess.emit_err(errors::ConstTrait { attr_span: attr.span });
+ false
+ }
+ }
+ }
+
+ fn check_stability_promotable(&self, attr: &Attribute, _span: Span, target: Target) -> bool {
+ match target {
+ Target::Expression => {
+ self.tcx.sess.emit_err(errors::StabilityPromotable { attr_span: attr.span });
+ false
+ }
+ _ => true,
+ }
+ }
+
+ fn check_deprecated(&self, hir_id: HirId, attr: &Attribute, _span: Span, target: Target) {
+ match target {
+ Target::Closure | Target::Expression | Target::Statement | Target::Arm => {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::Deprecated,
+ );
+ }
+ _ => {}
+ }
+ }
+
+ fn check_macro_use(&self, hir_id: HirId, attr: &Attribute, target: Target) {
+ let name = attr.name_or_empty();
+ match target {
+ Target::ExternCrate | Target::Mod => {}
+ _ => {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::MacroUse { name },
+ );
+ }
+ }
+ }
+
+ fn check_macro_export(&self, hir_id: HirId, attr: &Attribute, target: Target) {
+ if target != Target::MacroDef {
+ self.tcx.emit_spanned_lint(UNUSED_ATTRIBUTES, hir_id, attr.span, errors::MacroExport);
+ }
+ }
+
+ fn check_plugin_registrar(&self, hir_id: HirId, attr: &Attribute, target: Target) {
+ if target != Target::Fn {
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::PluginRegistrar,
+ );
+ }
+ }
+
+ fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute) {
+ // Warn on useless empty attributes.
+ let note = if matches!(
+ attr.name_or_empty(),
+ sym::macro_use
+ | sym::allow
+ | sym::expect
+ | sym::warn
+ | sym::deny
+ | sym::forbid
+ | sym::feature
+ | sym::repr
+ | sym::target_feature
+ ) && attr.meta_item_list().map_or(false, |list| list.is_empty())
+ {
+ errors::UnusedNote::EmptyList { name: attr.name_or_empty() }
+ } else if matches!(
+ attr.name_or_empty(),
+ sym::allow | sym::warn | sym::deny | sym::forbid | sym::expect
+ ) && let Some(meta) = attr.meta_item_list()
+ && meta.len() == 1
+ && let Some(item) = meta[0].meta_item()
+ && let MetaItemKind::NameValue(_) = &item.kind
+ && item.path == sym::reason
+ {
+ errors::UnusedNote::NoLints { name: attr.name_or_empty() }
+ } else if attr.name_or_empty() == sym::default_method_body_is_const {
+ errors::UnusedNote::DefaultMethodBodyConst
+ } else {
+ return;
+ };
+
+ self.tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ attr.span,
+ errors::Unused { attr_span: attr.span, note },
+ );
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
+ // Historically we've run more checks on non-exported than exported macros,
+ // so this lets us continue to run them while maintaining backwards compatibility.
+ // In the long run, the checks should be harmonized.
+ if let ItemKind::Macro(ref macro_def, _) = item.kind {
+ let def_id = item.def_id.to_def_id();
+ if macro_def.macro_rules && !self.tcx.has_attr(def_id, sym::macro_export) {
+ check_non_exported_macro_for_invalid_attrs(self.tcx, item);
+ }
+ }
+
+ let target = Target::from_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.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.span, target, None);
+ intravisit::walk_trait_item(self, trait_item)
+ }
+
+ 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.span, Target::Arm, None);
+ intravisit::walk_arm(self, arm);
+ }
+
+ 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.span, target, Some(ItemLike::ForeignItem));
+ intravisit::walk_foreign_item(self, 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.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, stmt.span, Target::Statement, None);
+ }
+ intravisit::walk_stmt(self, stmt)
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ let target = match expr.kind {
+ hir::ExprKind::Closure { .. } => Target::Closure,
+ _ => Target::Expression,
+ };
+
+ self.check_attributes(expr.hir_id, expr.span, target, None);
+ intravisit::walk_expr(self, expr)
+ }
+
+ fn visit_variant(
+ &mut self,
+ variant: &'tcx hir::Variant<'tcx>,
+ generics: &'tcx hir::Generics<'tcx>,
+ item_id: HirId,
+ ) {
+ self.check_attributes(variant.id, variant.span, Target::Variant, None);
+ intravisit::walk_variant(self, variant, generics, item_id)
+ }
+
+ fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+ self.check_attributes(param.hir_id, param.span, Target::Param, None);
+
+ intravisit::walk_param(self, param);
+ }
+}
+
+fn is_c_like_enum(item: &Item<'_>) -> bool {
+ if let ItemKind::Enum(ref def, _) = item.kind {
+ for variant in def.variants {
+ match variant.data {
+ hir::VariantData::Unit(..) => { /* continue */ }
+ _ => return false,
+ }
+ }
+ true
+ } else {
+ false
+ }
+}
+
+// FIXME: Fix "Cannot determine resolution" error and remove built-in macros
+// from this check.
+fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
+ // Check for builtin attributes at the crate level
+ // which were unsuccessfully resolved due to cannot determine
+ // resolution for the attribute macro error.
+ const ATTRS_TO_CHECK: &[Symbol] = &[
+ sym::macro_export,
+ sym::repr,
+ sym::path,
+ sym::automatically_derived,
+ sym::start,
+ sym::rustc_main,
+ sym::derive,
+ sym::test,
+ sym::test_case,
+ sym::global_allocator,
+ sym::bench,
+ ];
+
+ for attr in attrs {
+ // This function should only be called with crate attributes
+ // which are inner attributes always but lets check to make sure
+ if attr.style == AttrStyle::Inner {
+ for attr_to_check in ATTRS_TO_CHECK {
+ if attr.has_name(*attr_to_check) {
+ let mut err = tcx.sess.struct_span_err(
+ attr.span,
+ &format!(
+ "`{}` attribute cannot be used at crate level",
+ attr_to_check.to_ident_string()
+ ),
+ );
+ // Only emit an error with a suggestion if we can create a
+ // string out of the attribute span
+ if let Ok(src) = tcx.sess.source_map().span_to_snippet(attr.span) {
+ let replacement = src.replace("#!", "#");
+ err.span_suggestion_verbose(
+ attr.span,
+ "perhaps you meant to use an outer attribute",
+ replacement,
+ rustc_errors::Applicability::MachineApplicable,
+ );
+ }
+ err.emit();
+ }
+ }
+ }
+ }
+}
+
+fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>) {
+ let attrs = tcx.hir().attrs(item.hir_id());
+
+ for attr in attrs {
+ if attr.has_name(sym::inline) {
+ tcx.sess.emit_err(errors::NonExportedMacroInvalidAttrs { attr_span: attr.span });
+ }
+ }
+}
+
+fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
+ let check_attr_visitor = &mut CheckAttrVisitor { tcx };
+ tcx.hir().visit_item_likes_in_module(module_def_id, check_attr_visitor);
+ if module_def_id.is_top_level_module() {
+ check_attr_visitor.check_attributes(CRATE_HIR_ID, DUMMY_SP, Target::Mod, None);
+ check_invalid_crate_level_attr(tcx, tcx.hir().krate_attrs());
+ }
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+ *providers = Providers { check_mod_attrs, ..*providers };
+}
+
+fn check_duplicates(
+ tcx: TyCtxt<'_>,
+ attr: &Attribute,
+ hir_id: HirId,
+ duplicates: AttributeDuplicates,
+ seen: &mut FxHashMap<Symbol, Span>,
+) {
+ use AttributeDuplicates::*;
+ if matches!(duplicates, WarnFollowingWordOnly) && !attr.is_word() {
+ return;
+ }
+ match duplicates {
+ DuplicatesOk => {}
+ WarnFollowing | FutureWarnFollowing | WarnFollowingWordOnly | FutureWarnPreceding => {
+ match seen.entry(attr.name_or_empty()) {
+ Entry::Occupied(mut entry) => {
+ let (this, other) = if matches!(duplicates, FutureWarnPreceding) {
+ let to_remove = entry.insert(attr.span);
+ (to_remove, attr.span)
+ } else {
+ (attr.span, *entry.get())
+ };
+ tcx.emit_spanned_lint(
+ UNUSED_ATTRIBUTES,
+ hir_id,
+ this,
+ errors::UnusedDuplicate {
+ this,
+ other,
+ warning: matches!(
+ duplicates,
+ FutureWarnFollowing | FutureWarnPreceding
+ )
+ .then_some(()),
+ },
+ );
+ }
+ Entry::Vacant(entry) => {
+ entry.insert(attr.span);
+ }
+ }
+ }
+ ErrorFollowing | ErrorPreceding => match seen.entry(attr.name_or_empty()) {
+ Entry::Occupied(mut entry) => {
+ let (this, other) = if matches!(duplicates, ErrorPreceding) {
+ let to_remove = entry.insert(attr.span);
+ (to_remove, attr.span)
+ } else {
+ (attr.span, *entry.get())
+ };
+ tcx.sess.emit_err(errors::UnusedMultiple {
+ this,
+ other,
+ name: attr.name_or_empty(),
+ });
+ }
+ Entry::Vacant(entry) => {
+ entry.insert(attr.span);
+ }
+ },
+ }
+}
diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs
new file mode 100644
index 000000000..70518284c
--- /dev/null
+++ b/compiler/rustc_passes/src/check_const.rs
@@ -0,0 +1,236 @@
+//! This pass checks HIR bodies that may be evaluated at compile-time (e.g., `const`, `static`,
+//! `const fn`) for structured control flow (e.g. `if`, `while`), which is forbidden in a const
+//! context.
+//!
+//! By the time the MIR const-checker runs, these high-level constructs have been lowered to
+//! control-flow primitives (e.g., `Goto`, `SwitchInt`), making it tough to properly attribute
+//! errors. We still look for those primitives in the MIR const-checker to ensure nothing slips
+//! through, but errors for structured control flow in a `const` should be emitted here.
+
+use rustc_attr as attr;
+use rustc_errors::struct_span_err;
+use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::parse::feature_err;
+use rustc_span::{sym, Span, Symbol};
+
+/// An expression that is not *always* legal in a const context.
+#[derive(Clone, Copy)]
+enum NonConstExpr {
+ Loop(hir::LoopSource),
+ Match(hir::MatchSource),
+}
+
+impl NonConstExpr {
+ fn name(self) -> String {
+ match self {
+ Self::Loop(src) => format!("`{}`", src.name()),
+ Self::Match(src) => format!("`{}`", src.name()),
+ }
+ }
+
+ fn required_feature_gates(self) -> Option<&'static [Symbol]> {
+ use hir::LoopSource::*;
+ use hir::MatchSource::*;
+
+ let gates: &[_] = match self {
+ Self::Match(AwaitDesugar) => {
+ return None;
+ }
+
+ Self::Loop(ForLoop) | Self::Match(ForLoopDesugar) => &[sym::const_for],
+
+ Self::Match(TryDesugar) => &[sym::const_try],
+
+ // All other expressions are allowed.
+ Self::Loop(Loop | While) | Self::Match(Normal) => &[],
+ };
+
+ Some(gates)
+ }
+}
+
+fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
+ let mut vis = CheckConstVisitor::new(tcx);
+ tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis);
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+ *providers = Providers { check_mod_const_bodies, ..*providers };
+}
+
+#[derive(Copy, Clone)]
+struct CheckConstVisitor<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ const_kind: Option<hir::ConstContext>,
+ def_id: Option<LocalDefId>,
+}
+
+impl<'tcx> CheckConstVisitor<'tcx> {
+ fn new(tcx: TyCtxt<'tcx>) -> Self {
+ CheckConstVisitor { tcx, const_kind: None, def_id: None }
+ }
+
+ /// Emits an error when an unsupported expression is found in a const context.
+ fn const_check_violated(&self, expr: NonConstExpr, span: Span) {
+ let Self { tcx, def_id, const_kind } = *self;
+
+ let features = tcx.features();
+ let required_gates = expr.required_feature_gates();
+
+ let is_feature_allowed = |feature_gate| {
+ // All features require that the corresponding gate be enabled,
+ // even if the function has `#[rustc_allow_const_fn_unstable(the_gate)]`.
+ if !tcx.features().enabled(feature_gate) {
+ return false;
+ }
+
+ // If `def_id` is `None`, we don't need to consider stability attributes.
+ let def_id = match def_id {
+ Some(x) => x,
+ None => return true,
+ };
+
+ // If the function belongs to a trait, then it must enable the const_trait_impl
+ // feature to use that trait function (with a const default body).
+ if tcx.trait_of_item(def_id.to_def_id()).is_some() {
+ return true;
+ }
+
+ // If this crate is not using stability attributes, or this function is not claiming to be a
+ // stable `const fn`, that is all that is required.
+ if !tcx.features().staged_api
+ || tcx.has_attr(def_id.to_def_id(), sym::rustc_const_unstable)
+ {
+ return true;
+ }
+
+ // However, we cannot allow stable `const fn`s to use unstable features without an explicit
+ // opt-in via `rustc_allow_const_fn_unstable`.
+ let attrs = tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(def_id));
+ attr::rustc_allow_const_fn_unstable(&tcx.sess, attrs).any(|name| name == feature_gate)
+ };
+
+ match required_gates {
+ // Don't emit an error if the user has enabled the requisite feature gates.
+ Some(gates) if gates.iter().copied().all(is_feature_allowed) => return,
+
+ // `-Zunleash-the-miri-inside-of-you` only works for expressions that don't have a
+ // corresponding feature gate. This encourages nightly users to use feature gates when
+ // possible.
+ None if tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you => {
+ tcx.sess.span_warn(span, "skipping const checks");
+ return;
+ }
+
+ _ => {}
+ }
+
+ let const_kind =
+ const_kind.expect("`const_check_violated` may only be called inside a const context");
+
+ let msg = format!("{} is not allowed in a `{}`", expr.name(), const_kind.keyword_name());
+
+ let required_gates = required_gates.unwrap_or(&[]);
+ let missing_gates: Vec<_> =
+ required_gates.iter().copied().filter(|&g| !features.enabled(g)).collect();
+
+ match missing_gates.as_slice() {
+ [] => {
+ struct_span_err!(tcx.sess, span, E0744, "{}", msg).emit();
+ }
+
+ [missing_primary, ref missing_secondary @ ..] => {
+ let mut err = feature_err(&tcx.sess.parse_sess, *missing_primary, span, &msg);
+
+ // If multiple feature gates would be required to enable this expression, include
+ // them as help messages. Don't emit a separate error for each missing feature gate.
+ //
+ // FIXME(ecstaticmorse): Maybe this could be incorporated into `feature_err`? This
+ // is a pretty narrow case, however.
+ if tcx.sess.is_nightly_build() {
+ for gate in missing_secondary {
+ let note = format!(
+ "add `#![feature({})]` to the crate attributes to enable",
+ gate,
+ );
+ err.help(&note);
+ }
+ }
+
+ err.emit();
+ }
+ }
+ }
+
+ /// Saves the parent `const_kind` before calling `f` and restores it afterwards.
+ fn recurse_into(
+ &mut self,
+ kind: Option<hir::ConstContext>,
+ def_id: Option<LocalDefId>,
+ f: impl FnOnce(&mut Self),
+ ) {
+ let parent_def_id = self.def_id;
+ let parent_kind = self.const_kind;
+ self.def_id = def_id;
+ self.const_kind = kind;
+ f(self);
+ self.def_id = parent_def_id;
+ self.const_kind = parent_kind;
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
+ intravisit::walk_item(self, item);
+ }
+
+ fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) {
+ let kind = Some(hir::ConstContext::Const);
+ self.recurse_into(kind, None, |this| intravisit::walk_anon_const(this, anon));
+ }
+
+ fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
+ let owner = self.tcx.hir().body_owner_def_id(body.id());
+ let kind = self.tcx.hir().body_const_context(owner);
+ self.recurse_into(kind, Some(owner), |this| intravisit::walk_body(this, body));
+ }
+
+ fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+ match &e.kind {
+ // Skip the following checks if we are not currently in a const context.
+ _ if self.const_kind.is_none() => {}
+
+ hir::ExprKind::Loop(_, _, source, _) => {
+ self.const_check_violated(NonConstExpr::Loop(*source), e.span);
+ }
+
+ hir::ExprKind::Match(_, _, source) => {
+ let non_const_expr = match source {
+ // These are handled by `ExprKind::Loop` above.
+ hir::MatchSource::ForLoopDesugar => None,
+
+ _ => Some(NonConstExpr::Match(*source)),
+ };
+
+ if let Some(expr) = non_const_expr {
+ self.const_check_violated(expr, e.span);
+ }
+ }
+
+ _ => {}
+ }
+
+ intravisit::walk_expr(self, e);
+ }
+}
diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs
new file mode 100644
index 000000000..1e2fbeb38
--- /dev/null
+++ b/compiler/rustc_passes/src/dead.rs
@@ -0,0 +1,960 @@
+// This implements the dead-code warning pass. It follows middle::reachable
+// closely. The idea is that all reachable symbols are live, codes called
+// from live codes are live, and everything else is dead.
+
+use itertools::Itertools;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::{pluralize, Applicability, MultiSpan};
+use rustc_hir as hir;
+use rustc_hir::def::{CtorOf, DefKind, Res};
+use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{Node, PatKind, TyKind};
+use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
+use rustc_middle::middle::privacy;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{self, DefIdTree, TyCtxt};
+use rustc_session::lint;
+use rustc_span::symbol::{sym, Symbol};
+use std::mem;
+
+// Any local node that may call something in its body block should be
+// explored. For example, if it's a live Node::Item that is a
+// function, then we should explore its block to check for codes that
+// may need to be marked as live.
+fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
+ matches!(
+ tcx.hir().find_by_def_id(def_id),
+ Some(
+ Node::Item(..)
+ | Node::ImplItem(..)
+ | Node::ForeignItem(..)
+ | Node::TraitItem(..)
+ | Node::Variant(..)
+ | Node::AnonConst(..)
+ )
+ )
+}
+
+struct MarkSymbolVisitor<'tcx> {
+ worklist: Vec<LocalDefId>,
+ tcx: TyCtxt<'tcx>,
+ maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>,
+ live_symbols: FxHashSet<LocalDefId>,
+ repr_has_repr_c: bool,
+ repr_has_repr_simd: bool,
+ in_pat: bool,
+ ignore_variant_stack: Vec<DefId>,
+ // maps from tuple struct constructors to tuple struct items
+ struct_constructors: FxHashMap<LocalDefId, LocalDefId>,
+ // maps from ADTs to ignored derived traits (e.g. Debug and Clone)
+ // and the span of their respective impl (i.e., part of the derive
+ // macro)
+ ignored_derived_traits: FxHashMap<LocalDefId, Vec<(DefId, DefId)>>,
+}
+
+impl<'tcx> MarkSymbolVisitor<'tcx> {
+ /// Gets the type-checking results for the current body.
+ /// As this will ICE if called outside bodies, only call when working with
+ /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
+ #[track_caller]
+ fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
+ self.maybe_typeck_results
+ .expect("`MarkSymbolVisitor::typeck_results` called outside of body")
+ }
+
+ fn check_def_id(&mut self, def_id: DefId) {
+ if let Some(def_id) = def_id.as_local() {
+ if should_explore(self.tcx, def_id) || self.struct_constructors.contains_key(&def_id) {
+ self.worklist.push(def_id);
+ }
+ self.live_symbols.insert(def_id);
+ }
+ }
+
+ fn insert_def_id(&mut self, def_id: DefId) {
+ if let Some(def_id) = def_id.as_local() {
+ debug_assert!(!should_explore(self.tcx, def_id));
+ self.live_symbols.insert(def_id);
+ }
+ }
+
+ fn handle_res(&mut self, res: Res) {
+ match res {
+ Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::TyAlias, def_id) => {
+ self.check_def_id(def_id);
+ }
+ _ if self.in_pat => {}
+ Res::PrimTy(..) | Res::SelfCtor(..) | Res::Local(..) => {}
+ Res::Def(DefKind::Ctor(CtorOf::Variant, ..), ctor_def_id) => {
+ let variant_id = self.tcx.parent(ctor_def_id);
+ let enum_id = self.tcx.parent(variant_id);
+ self.check_def_id(enum_id);
+ if !self.ignore_variant_stack.contains(&ctor_def_id) {
+ self.check_def_id(variant_id);
+ }
+ }
+ Res::Def(DefKind::Variant, variant_id) => {
+ let enum_id = self.tcx.parent(variant_id);
+ self.check_def_id(enum_id);
+ if !self.ignore_variant_stack.contains(&variant_id) {
+ self.check_def_id(variant_id);
+ }
+ }
+ Res::Def(_, def_id) => self.check_def_id(def_id),
+ Res::SelfTy { trait_: t, alias_to: i } => {
+ if let Some(t) = t {
+ self.check_def_id(t);
+ }
+ if let Some((i, _)) = i {
+ self.check_def_id(i);
+ }
+ }
+ Res::ToolMod | Res::NonMacroAttr(..) | Res::Err => {}
+ }
+ }
+
+ fn lookup_and_handle_method(&mut self, id: hir::HirId) {
+ if let Some(def_id) = self.typeck_results().type_dependent_def_id(id) {
+ self.check_def_id(def_id);
+ } else {
+ bug!("no type-dependent def for method");
+ }
+ }
+
+ fn handle_field_access(&mut self, lhs: &hir::Expr<'_>, hir_id: hir::HirId) {
+ match self.typeck_results().expr_ty_adjusted(lhs).kind() {
+ ty::Adt(def, _) => {
+ let index = self.tcx.field_index(hir_id, self.typeck_results());
+ self.insert_def_id(def.non_enum_variant().fields[index].did);
+ }
+ ty::Tuple(..) => {}
+ _ => span_bug!(lhs.span, "named field access on non-ADT"),
+ }
+ }
+
+ #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands.
+ fn handle_assign(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if self
+ .typeck_results()
+ .expr_adjustments(expr)
+ .iter()
+ .any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_)))
+ {
+ self.visit_expr(expr);
+ } else if let hir::ExprKind::Field(base, ..) = expr.kind {
+ // Ignore write to field
+ self.handle_assign(base);
+ } else {
+ self.visit_expr(expr);
+ }
+ }
+
+ #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands.
+ fn check_for_self_assign(&mut self, assign: &'tcx hir::Expr<'tcx>) {
+ fn check_for_self_assign_helper<'tcx>(
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+ lhs: &'tcx hir::Expr<'tcx>,
+ rhs: &'tcx hir::Expr<'tcx>,
+ ) -> bool {
+ match (&lhs.kind, &rhs.kind) {
+ (hir::ExprKind::Path(ref qpath_l), hir::ExprKind::Path(ref qpath_r)) => {
+ if let (Res::Local(id_l), Res::Local(id_r)) = (
+ typeck_results.qpath_res(qpath_l, lhs.hir_id),
+ typeck_results.qpath_res(qpath_r, rhs.hir_id),
+ ) {
+ if id_l == id_r {
+ return true;
+ }
+ }
+ return false;
+ }
+ (hir::ExprKind::Field(lhs_l, ident_l), hir::ExprKind::Field(lhs_r, ident_r)) => {
+ if ident_l == ident_r {
+ return check_for_self_assign_helper(typeck_results, lhs_l, lhs_r);
+ }
+ return false;
+ }
+ _ => {
+ return false;
+ }
+ }
+ }
+
+ if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind
+ && check_for_self_assign_helper(self.typeck_results(), lhs, rhs)
+ && !assign.span.from_expansion()
+ {
+ let is_field_assign = matches!(lhs.kind, hir::ExprKind::Field(..));
+ self.tcx.struct_span_lint_hir(
+ lint::builtin::DEAD_CODE,
+ assign.hir_id,
+ assign.span,
+ |lint| {
+ lint.build(&format!(
+ "useless assignment of {} of type `{}` to itself",
+ if is_field_assign { "field" } else { "variable" },
+ self.typeck_results().expr_ty(lhs),
+ ))
+ .emit();
+ },
+ )
+ }
+ }
+
+ fn handle_field_pattern_match(
+ &mut self,
+ lhs: &hir::Pat<'_>,
+ res: Res,
+ pats: &[hir::PatField<'_>],
+ ) {
+ let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
+ ty::Adt(adt, _) => adt.variant_of_res(res),
+ _ => span_bug!(lhs.span, "non-ADT in struct pattern"),
+ };
+ for pat in pats {
+ if let PatKind::Wild = pat.pat.kind {
+ continue;
+ }
+ let index = self.tcx.field_index(pat.hir_id, self.typeck_results());
+ self.insert_def_id(variant.fields[index].did);
+ }
+ }
+
+ fn handle_tuple_field_pattern_match(
+ &mut self,
+ lhs: &hir::Pat<'_>,
+ res: Res,
+ pats: &[hir::Pat<'_>],
+ dotdot: Option<usize>,
+ ) {
+ let variant = match self.typeck_results().node_type(lhs.hir_id).kind() {
+ ty::Adt(adt, _) => adt.variant_of_res(res),
+ _ => span_bug!(lhs.span, "non-ADT in tuple struct pattern"),
+ };
+ let first_n = pats.iter().enumerate().take(dotdot.unwrap_or(pats.len()));
+ let missing = variant.fields.len() - pats.len();
+ let last_n = pats
+ .iter()
+ .enumerate()
+ .skip(dotdot.unwrap_or(pats.len()))
+ .map(|(idx, pat)| (idx + missing, pat));
+ for (idx, pat) in first_n.chain(last_n) {
+ if let PatKind::Wild = pat.kind {
+ continue;
+ }
+ self.insert_def_id(variant.fields[idx].did);
+ }
+ }
+
+ fn mark_live_symbols(&mut self) {
+ let mut scanned = FxHashSet::default();
+ while let Some(id) = self.worklist.pop() {
+ if !scanned.insert(id) {
+ continue;
+ }
+
+ // in the case of tuple struct constructors we want to check the item, not the generated
+ // tuple struct constructor function
+ let id = self.struct_constructors.get(&id).copied().unwrap_or(id);
+
+ if let Some(node) = self.tcx.hir().find_by_def_id(id) {
+ self.live_symbols.insert(id);
+ self.visit_node(node);
+ }
+ }
+ }
+
+ /// Automatically generated items marked with `rustc_trivial_field_reads`
+ /// will be ignored for the purposes of dead code analysis (see PR #85200
+ /// for discussion).
+ fn should_ignore_item(&mut self, def_id: DefId) -> bool {
+ if let Some(impl_of) = self.tcx.impl_of_method(def_id) {
+ if !self.tcx.has_attr(impl_of, sym::automatically_derived) {
+ return false;
+ }
+
+ if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of)
+ && self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads)
+ {
+ let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap();
+ if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind()
+ && let Some(adt_def_id) = adt_def.did().as_local()
+ {
+ self.ignored_derived_traits
+ .entry(adt_def_id)
+ .or_default()
+ .push((trait_of, impl_of));
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ fn visit_node(&mut self, node: Node<'tcx>) {
+ if let Node::ImplItem(hir::ImplItem { def_id, .. }) = node
+ && self.should_ignore_item(def_id.to_def_id())
+ {
+ return;
+ }
+
+ let had_repr_c = self.repr_has_repr_c;
+ let had_repr_simd = self.repr_has_repr_simd;
+ self.repr_has_repr_c = false;
+ self.repr_has_repr_simd = false;
+ match node {
+ Node::Item(item) => match item.kind {
+ hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => {
+ let def = self.tcx.adt_def(item.def_id);
+ self.repr_has_repr_c = def.repr().c();
+ self.repr_has_repr_simd = def.repr().simd();
+
+ intravisit::walk_item(self, &item)
+ }
+ hir::ItemKind::ForeignMod { .. } => {}
+ _ => intravisit::walk_item(self, &item),
+ },
+ Node::TraitItem(trait_item) => {
+ intravisit::walk_trait_item(self, trait_item);
+ }
+ Node::ImplItem(impl_item) => {
+ let item = self.tcx.local_parent(impl_item.def_id);
+ if self.tcx.impl_trait_ref(item).is_none() {
+ //// If it's a type whose items are live, then it's live, too.
+ //// This is done to handle the case where, for example, the static
+ //// method of a private type is used, but the type itself is never
+ //// called directly.
+ let self_ty = self.tcx.type_of(item);
+ match *self_ty.kind() {
+ ty::Adt(def, _) => self.check_def_id(def.did()),
+ ty::Foreign(did) => self.check_def_id(did),
+ ty::Dynamic(data, ..) => {
+ if let Some(def_id) = data.principal_def_id() {
+ self.check_def_id(def_id)
+ }
+ }
+ _ => {}
+ }
+ }
+ intravisit::walk_impl_item(self, impl_item);
+ }
+ Node::ForeignItem(foreign_item) => {
+ intravisit::walk_foreign_item(self, &foreign_item);
+ }
+ _ => {}
+ }
+ self.repr_has_repr_simd = had_repr_simd;
+ self.repr_has_repr_c = had_repr_c;
+ }
+
+ fn mark_as_used_if_union(&mut self, adt: ty::AdtDef<'tcx>, fields: &[hir::ExprField<'_>]) {
+ if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did().is_local() {
+ for field in fields {
+ let index = self.tcx.field_index(field.hir_id, self.typeck_results());
+ self.insert_def_id(adt.non_enum_variant().fields[index].did);
+ }
+ }
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
+ fn visit_nested_body(&mut self, body: hir::BodyId) {
+ let old_maybe_typeck_results =
+ self.maybe_typeck_results.replace(self.tcx.typeck_body(body));
+ let body = self.tcx.hir().body(body);
+ self.visit_body(body);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn visit_variant_data(
+ &mut self,
+ def: &'tcx hir::VariantData<'tcx>,
+ _: Symbol,
+ _: &hir::Generics<'_>,
+ _: hir::HirId,
+ _: rustc_span::Span,
+ ) {
+ let tcx = self.tcx;
+ let has_repr_c = self.repr_has_repr_c;
+ let has_repr_simd = self.repr_has_repr_simd;
+ let live_fields = def.fields().iter().filter_map(|f| {
+ let def_id = tcx.hir().local_def_id(f.hir_id);
+ if has_repr_c || (f.is_positional() && has_repr_simd) {
+ return Some(def_id);
+ }
+ if !tcx.visibility(f.hir_id.owner).is_public() {
+ return None;
+ }
+ if tcx.visibility(def_id).is_public() { Some(def_id) } else { None }
+ });
+ self.live_symbols.extend(live_fields);
+
+ intravisit::walk_struct_def(self, def);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ match expr.kind {
+ hir::ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(..)) => {
+ let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
+ self.handle_res(res);
+ }
+ hir::ExprKind::MethodCall(..) => {
+ self.lookup_and_handle_method(expr.hir_id);
+ }
+ hir::ExprKind::Field(ref lhs, ..) => {
+ self.handle_field_access(&lhs, expr.hir_id);
+ }
+ hir::ExprKind::Struct(ref qpath, ref fields, _) => {
+ let res = self.typeck_results().qpath_res(qpath, expr.hir_id);
+ self.handle_res(res);
+ if let ty::Adt(adt, _) = self.typeck_results().expr_ty(expr).kind() {
+ self.mark_as_used_if_union(*adt, fields);
+ }
+ }
+ _ => (),
+ }
+
+ intravisit::walk_expr(self, expr);
+ }
+
+ fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
+ // Inside the body, ignore constructions of variants
+ // necessary for the pattern to match. Those construction sites
+ // can't be reached unless the variant is constructed elsewhere.
+ let len = self.ignore_variant_stack.len();
+ self.ignore_variant_stack.extend(arm.pat.necessary_variants());
+ intravisit::walk_arm(self, arm);
+ self.ignore_variant_stack.truncate(len);
+ }
+
+ fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
+ self.in_pat = true;
+ match pat.kind {
+ PatKind::Struct(ref path, ref fields, _) => {
+ let res = self.typeck_results().qpath_res(path, pat.hir_id);
+ self.handle_field_pattern_match(pat, res, fields);
+ }
+ PatKind::Path(ref qpath) => {
+ let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
+ self.handle_res(res);
+ }
+ PatKind::TupleStruct(ref qpath, ref fields, dotdot) => {
+ let res = self.typeck_results().qpath_res(qpath, pat.hir_id);
+ self.handle_tuple_field_pattern_match(pat, res, fields, dotdot);
+ }
+ _ => (),
+ }
+
+ intravisit::walk_pat(self, pat);
+ self.in_pat = false;
+ }
+
+ fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
+ self.handle_res(path.res);
+ intravisit::walk_path(self, path);
+ }
+
+ fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx>) {
+ if let TyKind::OpaqueDef(item_id, _) = ty.kind {
+ let item = self.tcx.hir().item(item_id);
+ intravisit::walk_item(self, item);
+ }
+ intravisit::walk_ty(self, ty);
+ }
+
+ fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) {
+ // When inline const blocks are used in pattern position, paths
+ // referenced by it should be considered as used.
+ let in_pat = mem::replace(&mut self.in_pat, false);
+
+ self.live_symbols.insert(self.tcx.hir().local_def_id(c.hir_id));
+ intravisit::walk_anon_const(self, c);
+
+ self.in_pat = in_pat;
+ }
+}
+
+fn has_allow_dead_code_or_lang_attr_helper(
+ tcx: TyCtxt<'_>,
+ id: hir::HirId,
+ lint: &'static lint::Lint,
+) -> bool {
+ let attrs = tcx.hir().attrs(id);
+ if tcx.sess.contains_name(attrs, sym::lang) {
+ return true;
+ }
+
+ // Stable attribute for #[lang = "panic_impl"]
+ if tcx.sess.contains_name(attrs, sym::panic_handler) {
+ return true;
+ }
+
+ // (To be) stable attribute for #[lang = "oom"]
+ if tcx.sess.contains_name(attrs, sym::alloc_error_handler) {
+ return true;
+ }
+
+ let def_id = tcx.hir().local_def_id(id);
+ if tcx.def_kind(def_id).has_codegen_attrs() {
+ let cg_attrs = tcx.codegen_fn_attrs(def_id);
+
+ // #[used], #[no_mangle], #[export_name], etc also keeps the item alive
+ // forcefully, e.g., for placing it in a specific section.
+ if cg_attrs.contains_extern_indicator()
+ || cg_attrs.flags.contains(CodegenFnAttrFlags::USED)
+ || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)
+ {
+ return true;
+ }
+ }
+
+ tcx.lint_level_at_node(lint, id).0 == lint::Allow
+}
+
+fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
+ has_allow_dead_code_or_lang_attr_helper(tcx, id, lint::builtin::DEAD_CODE)
+}
+
+// These check_* functions seeds items that
+// 1) We want to explicitly consider as live:
+// * Item annotated with #[allow(dead_code)]
+// - This is done so that if we want to suppress warnings for a
+// group of dead functions, we only have to annotate the "root".
+// For example, if both `f` and `g` are dead and `f` calls `g`,
+// then annotating `f` with `#[allow(dead_code)]` will suppress
+// warning for both `f` and `g`.
+// * Item annotated with #[lang=".."]
+// - This is because lang items are always callable from elsewhere.
+// or
+// 2) We are not sure to be live or not
+// * Implementations of traits and trait methods
+fn check_item<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ worklist: &mut Vec<LocalDefId>,
+ struct_constructors: &mut FxHashMap<LocalDefId, LocalDefId>,
+ id: hir::ItemId,
+) {
+ let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.hir_id());
+ if allow_dead_code {
+ worklist.push(id.def_id);
+ }
+
+ match tcx.def_kind(id.def_id) {
+ DefKind::Enum => {
+ let item = tcx.hir().item(id);
+ if let hir::ItemKind::Enum(ref enum_def, _) = item.kind {
+ let hir = tcx.hir();
+ if allow_dead_code {
+ worklist.extend(
+ enum_def.variants.iter().map(|variant| hir.local_def_id(variant.id)),
+ );
+ }
+
+ for variant in enum_def.variants {
+ if let Some(ctor_hir_id) = variant.data.ctor_hir_id() {
+ struct_constructors
+ .insert(hir.local_def_id(ctor_hir_id), hir.local_def_id(variant.id));
+ }
+ }
+ }
+ }
+ DefKind::Impl => {
+ let of_trait = tcx.impl_trait_ref(id.def_id);
+
+ if of_trait.is_some() {
+ worklist.push(id.def_id);
+ }
+
+ // get DefIds from another query
+ let local_def_ids = tcx
+ .associated_item_def_ids(id.def_id)
+ .iter()
+ .filter_map(|def_id| def_id.as_local());
+
+ // And we access the Map here to get HirId from LocalDefId
+ for id in local_def_ids {
+ if of_trait.is_some()
+ || has_allow_dead_code_or_lang_attr(tcx, tcx.hir().local_def_id_to_hir_id(id))
+ {
+ worklist.push(id);
+ }
+ }
+ }
+ DefKind::Struct => {
+ let item = tcx.hir().item(id);
+ if let hir::ItemKind::Struct(ref variant_data, _) = item.kind
+ && let Some(ctor_hir_id) = variant_data.ctor_hir_id()
+ {
+ struct_constructors.insert(tcx.hir().local_def_id(ctor_hir_id), item.def_id);
+ }
+ }
+ DefKind::GlobalAsm => {
+ // global_asm! is always live.
+ worklist.push(id.def_id);
+ }
+ _ => {}
+ }
+}
+
+fn check_trait_item<'tcx>(tcx: TyCtxt<'tcx>, worklist: &mut Vec<LocalDefId>, id: hir::TraitItemId) {
+ use hir::TraitItemKind::{Const, Fn};
+ if matches!(tcx.def_kind(id.def_id), DefKind::AssocConst | DefKind::AssocFn) {
+ let trait_item = tcx.hir().trait_item(id);
+ if matches!(trait_item.kind, Const(_, Some(_)) | Fn(_, hir::TraitFn::Provided(_)))
+ && has_allow_dead_code_or_lang_attr(tcx, trait_item.hir_id())
+ {
+ worklist.push(trait_item.def_id);
+ }
+ }
+}
+
+fn check_foreign_item<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ worklist: &mut Vec<LocalDefId>,
+ id: hir::ForeignItemId,
+) {
+ if matches!(tcx.def_kind(id.def_id), DefKind::Static(_) | DefKind::Fn)
+ && has_allow_dead_code_or_lang_attr(tcx, id.hir_id())
+ {
+ worklist.push(id.def_id);
+ }
+}
+
+fn create_and_seed_worklist<'tcx>(
+ tcx: TyCtxt<'tcx>,
+) -> (Vec<LocalDefId>, FxHashMap<LocalDefId, LocalDefId>) {
+ let access_levels = &tcx.privacy_access_levels(());
+ // see `MarkSymbolVisitor::struct_constructors`
+ let mut struct_constructors = Default::default();
+ let mut worklist = access_levels
+ .map
+ .iter()
+ .filter_map(
+ |(&id, &level)| {
+ if level >= privacy::AccessLevel::Reachable { Some(id) } else { None }
+ },
+ )
+ // Seed entry point
+ .chain(tcx.entry_fn(()).and_then(|(def_id, _)| def_id.as_local()))
+ .collect::<Vec<_>>();
+
+ let crate_items = tcx.hir_crate_items(());
+ for id in crate_items.items() {
+ check_item(tcx, &mut worklist, &mut struct_constructors, id);
+ }
+
+ for id in crate_items.trait_items() {
+ check_trait_item(tcx, &mut worklist, id);
+ }
+
+ for id in crate_items.foreign_items() {
+ check_foreign_item(tcx, &mut worklist, id);
+ }
+
+ (worklist, struct_constructors)
+}
+
+fn live_symbols_and_ignored_derived_traits<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ (): (),
+) -> (FxHashSet<LocalDefId>, FxHashMap<LocalDefId, Vec<(DefId, DefId)>>) {
+ let (worklist, struct_constructors) = create_and_seed_worklist(tcx);
+ let mut symbol_visitor = MarkSymbolVisitor {
+ worklist,
+ tcx,
+ maybe_typeck_results: None,
+ live_symbols: Default::default(),
+ repr_has_repr_c: false,
+ repr_has_repr_simd: false,
+ in_pat: false,
+ ignore_variant_stack: vec![],
+ struct_constructors,
+ ignored_derived_traits: FxHashMap::default(),
+ };
+ symbol_visitor.mark_live_symbols();
+ (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits)
+}
+
+struct DeadVariant {
+ def_id: LocalDefId,
+ name: Symbol,
+ level: lint::Level,
+}
+
+struct DeadVisitor<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ live_symbols: &'tcx FxHashSet<LocalDefId>,
+ ignored_derived_traits: &'tcx FxHashMap<LocalDefId, Vec<(DefId, DefId)>>,
+}
+
+enum ShouldWarnAboutField {
+ Yes(bool), // positional?
+ No,
+}
+
+impl<'tcx> DeadVisitor<'tcx> {
+ fn should_warn_about_field(&mut self, field: &ty::FieldDef) -> ShouldWarnAboutField {
+ if self.live_symbols.contains(&field.did.expect_local()) {
+ return ShouldWarnAboutField::No;
+ }
+ let field_type = self.tcx.type_of(field.did);
+ if field_type.is_phantom_data() {
+ return ShouldWarnAboutField::No;
+ }
+ let is_positional = field.name.as_str().starts_with(|c: char| c.is_ascii_digit());
+ if is_positional
+ && self
+ .tcx
+ .layout_of(self.tcx.param_env(field.did).and(field_type))
+ .map_or(true, |layout| layout.is_zst())
+ {
+ return ShouldWarnAboutField::No;
+ }
+ ShouldWarnAboutField::Yes(is_positional)
+ }
+
+ fn warn_multiple_dead_codes(
+ &self,
+ dead_codes: &[LocalDefId],
+ participle: &str,
+ parent_item: Option<LocalDefId>,
+ is_positional: bool,
+ ) {
+ if let Some(&first_id) = dead_codes.first() {
+ let tcx = self.tcx;
+ let names: Vec<_> = dead_codes
+ .iter()
+ .map(|&def_id| tcx.item_name(def_id.to_def_id()).to_string())
+ .collect();
+ let spans: Vec<_> = dead_codes
+ .iter()
+ .map(|&def_id| match tcx.def_ident_span(def_id) {
+ Some(s) => s.with_ctxt(tcx.def_span(def_id).ctxt()),
+ None => tcx.def_span(def_id),
+ })
+ .collect();
+
+ tcx.struct_span_lint_hir(
+ if is_positional {
+ lint::builtin::UNUSED_TUPLE_STRUCT_FIELDS
+ } else {
+ lint::builtin::DEAD_CODE
+ },
+ tcx.hir().local_def_id_to_hir_id(first_id),
+ MultiSpan::from_spans(spans.clone()),
+ |lint| {
+ let descr = tcx.def_kind(first_id).descr(first_id.to_def_id());
+ let span_len = dead_codes.len();
+ let names = match &names[..] {
+ _ if span_len > 6 => String::new(),
+ [name] => format!("`{name}` "),
+ [names @ .., last] => {
+ format!(
+ "{} and `{last}` ",
+ names.iter().map(|name| format!("`{name}`")).join(", ")
+ )
+ }
+ [] => unreachable!(),
+ };
+ let mut err = lint.build(&format!(
+ "{these}{descr}{s} {names}{are} never {participle}",
+ these = if span_len > 6 { "multiple " } else { "" },
+ s = pluralize!(span_len),
+ are = pluralize!("is", span_len),
+ ));
+
+ if is_positional {
+ err.multipart_suggestion(
+ &format!(
+ "consider changing the field{s} to be of unit type to \
+ suppress this warning while preserving the field \
+ numbering, or remove the field{s}",
+ s = pluralize!(span_len)
+ ),
+ spans.iter().map(|sp| (*sp, "()".to_string())).collect(),
+ // "HasPlaceholders" because applying this fix by itself isn't
+ // enough: All constructor calls have to be adjusted as well
+ Applicability::HasPlaceholders,
+ );
+ }
+
+ if let Some(parent_item) = parent_item {
+ let parent_descr = tcx.def_kind(parent_item).descr(parent_item.to_def_id());
+ err.span_label(
+ tcx.def_ident_span(parent_item).unwrap(),
+ format!("{descr}{s} in this {parent_descr}", s = pluralize!(span_len)),
+ );
+ }
+
+ let encl_def_id = parent_item.unwrap_or(first_id);
+ if let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id) {
+ let traits_str = ign_traits
+ .iter()
+ .map(|(trait_id, _)| format!("`{}`", self.tcx.item_name(*trait_id)))
+ .collect::<Vec<_>>()
+ .join(" and ");
+ let plural_s = pluralize!(ign_traits.len());
+ let article = if ign_traits.len() > 1 { "" } else { "a " };
+ let is_are = if ign_traits.len() > 1 { "these are" } else { "this is" };
+ let msg = format!(
+ "`{}` has {}derived impl{} for the trait{} {}, but {} \
+ intentionally ignored during dead code analysis",
+ self.tcx.item_name(encl_def_id.to_def_id()),
+ article,
+ plural_s,
+ plural_s,
+ traits_str,
+ is_are
+ );
+ err.note(&msg);
+ }
+ err.emit();
+ },
+ );
+ }
+ }
+
+ fn warn_dead_fields_and_variants(
+ &self,
+ def_id: LocalDefId,
+ participle: &str,
+ dead_codes: Vec<DeadVariant>,
+ is_positional: bool,
+ ) {
+ let mut dead_codes = dead_codes
+ .iter()
+ .filter(|v| !v.name.as_str().starts_with('_'))
+ .map(|v| v)
+ .collect::<Vec<&DeadVariant>>();
+ if dead_codes.is_empty() {
+ return;
+ }
+ dead_codes.sort_by_key(|v| v.level);
+ for (_, group) in &dead_codes.into_iter().group_by(|v| v.level) {
+ self.warn_multiple_dead_codes(
+ &group.map(|v| v.def_id).collect::<Vec<_>>(),
+ participle,
+ Some(def_id),
+ is_positional,
+ );
+ }
+ }
+
+ fn warn_dead_code(&mut self, id: LocalDefId, participle: &str) {
+ self.warn_multiple_dead_codes(&[id], participle, None, false);
+ }
+
+ fn check_definition(&mut self, def_id: LocalDefId) {
+ if self.live_symbols.contains(&def_id) {
+ return;
+ }
+ let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id);
+ if has_allow_dead_code_or_lang_attr(self.tcx, hir_id) {
+ return;
+ }
+ let Some(name) = self.tcx.opt_item_name(def_id.to_def_id()) else {
+ return
+ };
+ if name.as_str().starts_with('_') {
+ return;
+ }
+ match self.tcx.def_kind(def_id) {
+ DefKind::AssocConst
+ | DefKind::AssocFn
+ | DefKind::Fn
+ | DefKind::Static(_)
+ | DefKind::Const
+ | DefKind::TyAlias
+ | DefKind::Enum
+ | DefKind::Union
+ | DefKind::ForeignTy => self.warn_dead_code(def_id, "used"),
+ DefKind::Struct => self.warn_dead_code(def_id, "constructed"),
+ DefKind::Variant | DefKind::Field => bug!("should be handled specially"),
+ _ => {}
+ }
+ }
+}
+
+fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalDefId) {
+ let (live_symbols, ignored_derived_traits) = tcx.live_symbols_and_ignored_derived_traits(());
+ let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits };
+
+ let module_items = tcx.hir_module_items(module);
+
+ for item in module_items.items() {
+ if !live_symbols.contains(&item.def_id) {
+ let parent = tcx.local_parent(item.def_id);
+ if parent != module && !live_symbols.contains(&parent) {
+ // We already have diagnosed something.
+ continue;
+ }
+ visitor.check_definition(item.def_id);
+ continue;
+ }
+
+ let def_kind = tcx.def_kind(item.def_id);
+ if let DefKind::Struct | DefKind::Union | DefKind::Enum = def_kind {
+ let adt = tcx.adt_def(item.def_id);
+ let mut dead_variants = Vec::new();
+
+ for variant in adt.variants() {
+ let def_id = variant.def_id.expect_local();
+ if !live_symbols.contains(&def_id) {
+ // Record to group diagnostics.
+ let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
+ let level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0;
+ dead_variants.push(DeadVariant { def_id, name: variant.name, level });
+ continue;
+ }
+
+ let mut is_positional = false;
+ let dead_fields = variant
+ .fields
+ .iter()
+ .filter_map(|field| {
+ let def_id = field.did.expect_local();
+ let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
+ if let ShouldWarnAboutField::Yes(is_pos) =
+ visitor.should_warn_about_field(&field)
+ {
+ let level = tcx
+ .lint_level_at_node(
+ if is_pos {
+ is_positional = true;
+ lint::builtin::UNUSED_TUPLE_STRUCT_FIELDS
+ } else {
+ lint::builtin::DEAD_CODE
+ },
+ hir_id,
+ )
+ .0;
+ Some(DeadVariant { def_id, name: field.name, level })
+ } else {
+ None
+ }
+ })
+ .collect();
+ visitor.warn_dead_fields_and_variants(def_id, "read", dead_fields, is_positional)
+ }
+
+ visitor.warn_dead_fields_and_variants(item.def_id, "constructed", dead_variants, false);
+ }
+ }
+
+ for impl_item in module_items.impl_items() {
+ visitor.check_definition(impl_item.def_id);
+ }
+
+ for foreign_item in module_items.foreign_items() {
+ visitor.check_definition(foreign_item.def_id);
+ }
+
+ // We do not warn trait items.
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+ *providers =
+ Providers { live_symbols_and_ignored_derived_traits, check_mod_deathness, ..*providers };
+}
diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs
new file mode 100644
index 000000000..e08683fe2
--- /dev/null
+++ b/compiler/rustc_passes/src/debugger_visualizer.rs
@@ -0,0 +1,99 @@
+//! Detecting usage of the `#[debugger_visualizer]` attribute.
+
+use hir::CRATE_HIR_ID;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_expand::base::resolve_path;
+use rustc_hir as hir;
+use rustc_hir::def_id::CrateNum;
+use rustc_hir::HirId;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::def_id::LOCAL_CRATE;
+use rustc_span::{sym, DebuggerVisualizerFile, DebuggerVisualizerType};
+
+use std::sync::Arc;
+
+fn check_for_debugger_visualizer<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ hir_id: HirId,
+ debugger_visualizers: &mut FxHashSet<DebuggerVisualizerFile>,
+) {
+ let attrs = tcx.hir().attrs(hir_id);
+ for attr in attrs {
+ if attr.has_name(sym::debugger_visualizer) {
+ let Some(list) = attr.meta_item_list() else {
+ continue
+ };
+
+ let meta_item = match list.len() {
+ 1 => match list[0].meta_item() {
+ Some(meta_item) => meta_item,
+ _ => continue,
+ },
+ _ => continue,
+ };
+
+ let visualizer_type = match meta_item.name_or_empty() {
+ sym::natvis_file => DebuggerVisualizerType::Natvis,
+ sym::gdb_script_file => DebuggerVisualizerType::GdbPrettyPrinter,
+ _ => continue,
+ };
+
+ let file = match meta_item.value_str() {
+ Some(value) => {
+ match resolve_path(&tcx.sess.parse_sess, value.as_str(), attr.span) {
+ Ok(file) => file,
+ _ => continue,
+ }
+ }
+ None => continue,
+ };
+
+ match std::fs::read(&file) {
+ Ok(contents) => {
+ debugger_visualizers
+ .insert(DebuggerVisualizerFile::new(Arc::from(contents), visualizer_type));
+ }
+ Err(err) => {
+ tcx.sess
+ .struct_span_err(
+ meta_item.span,
+ &format!("couldn't read {}: {}", file.display(), err),
+ )
+ .emit();
+ }
+ }
+ }
+ }
+}
+
+/// Traverses and collects the debugger visualizers for a specific crate.
+fn debugger_visualizers<'tcx>(tcx: TyCtxt<'tcx>, cnum: CrateNum) -> Vec<DebuggerVisualizerFile> {
+ assert_eq!(cnum, LOCAL_CRATE);
+
+ // Initialize the collector.
+ let mut debugger_visualizers = FxHashSet::default();
+
+ // Collect debugger visualizers in this crate.
+ tcx.hir().for_each_module(|id| {
+ check_for_debugger_visualizer(
+ tcx,
+ tcx.hir().local_def_id_to_hir_id(id),
+ &mut debugger_visualizers,
+ )
+ });
+
+ // Collect debugger visualizers on the crate attributes.
+ check_for_debugger_visualizer(tcx, CRATE_HIR_ID, &mut debugger_visualizers);
+
+ // Extract out the found debugger_visualizer items.
+ let mut visualizers = debugger_visualizers.into_iter().collect::<Vec<_>>();
+
+ // Sort the visualizers so we always get a deterministic query result.
+ visualizers.sort();
+ visualizers
+}
+
+pub fn provide(providers: &mut Providers) {
+ providers.debugger_visualizers = debugger_visualizers;
+}
diff --git a/compiler/rustc_passes/src/diagnostic_items.rs b/compiler/rustc_passes/src/diagnostic_items.rs
new file mode 100644
index 000000000..e6b69d898
--- /dev/null
+++ b/compiler/rustc_passes/src/diagnostic_items.rs
@@ -0,0 +1,113 @@
+//! Detecting diagnostic items.
+//!
+//! Diagnostic items are items that are not language-inherent, but can reasonably be expected to
+//! exist for diagnostic purposes. This allows diagnostic authors to refer to specific items
+//! directly, without having to guess module paths and crates.
+//! Examples are:
+//!
+//! * Traits like `Debug`, that have no bearing on language semantics
+//!
+//! * Compiler internal types like `Ty` and `TyCtxt`
+
+use rustc_ast as ast;
+use rustc_hir::diagnostic_items::DiagnosticItems;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE};
+use rustc_span::symbol::{sym, Symbol};
+
+fn observe_item<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ diagnostic_items: &mut DiagnosticItems,
+ def_id: LocalDefId,
+) {
+ let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
+ let attrs = tcx.hir().attrs(hir_id);
+ if let Some(name) = extract(attrs) {
+ // insert into our table
+ collect_item(tcx, diagnostic_items, name, def_id.to_def_id());
+ }
+}
+
+fn collect_item(tcx: TyCtxt<'_>, items: &mut DiagnosticItems, name: Symbol, item_def_id: DefId) {
+ items.id_to_name.insert(item_def_id, name);
+ if let Some(original_def_id) = items.name_to_id.insert(name, item_def_id) {
+ if original_def_id != item_def_id {
+ let mut err = match tcx.hir().span_if_local(item_def_id) {
+ Some(span) => tcx
+ .sess
+ .struct_span_err(span, &format!("duplicate diagnostic item found: `{name}`.")),
+ None => tcx.sess.struct_err(&format!(
+ "duplicate diagnostic item in crate `{}`: `{}`.",
+ tcx.crate_name(item_def_id.krate),
+ name
+ )),
+ };
+ if let Some(span) = tcx.hir().span_if_local(original_def_id) {
+ err.span_note(span, "the diagnostic item is first defined here");
+ } else {
+ err.note(&format!(
+ "the diagnostic item is first defined in crate `{}`.",
+ tcx.crate_name(original_def_id.krate)
+ ));
+ }
+ err.emit();
+ }
+ }
+}
+
+/// Extract the first `rustc_diagnostic_item = "$name"` out of a list of attributes.
+fn extract(attrs: &[ast::Attribute]) -> Option<Symbol> {
+ attrs.iter().find_map(|attr| {
+ if attr.has_name(sym::rustc_diagnostic_item) { attr.value_str() } else { None }
+ })
+}
+
+/// Traverse and collect the diagnostic items in the current
+fn diagnostic_items<'tcx>(tcx: TyCtxt<'tcx>, cnum: CrateNum) -> DiagnosticItems {
+ assert_eq!(cnum, LOCAL_CRATE);
+
+ // Initialize the collector.
+ let mut diagnostic_items = DiagnosticItems::default();
+
+ // Collect diagnostic items in this crate.
+ let crate_items = tcx.hir_crate_items(());
+
+ for id in crate_items.items() {
+ observe_item(tcx, &mut diagnostic_items, id.def_id);
+ }
+
+ for id in crate_items.trait_items() {
+ observe_item(tcx, &mut diagnostic_items, id.def_id);
+ }
+
+ for id in crate_items.impl_items() {
+ observe_item(tcx, &mut diagnostic_items, id.def_id);
+ }
+
+ for id in crate_items.foreign_items() {
+ observe_item(tcx, &mut diagnostic_items, id.def_id);
+ }
+
+ diagnostic_items
+}
+
+/// Traverse and collect all the diagnostic items in all crates.
+fn all_diagnostic_items<'tcx>(tcx: TyCtxt<'tcx>, (): ()) -> DiagnosticItems {
+ // Initialize the collector.
+ let mut items = DiagnosticItems::default();
+
+ // Collect diagnostic items in other crates.
+ for &cnum in tcx.crates(()).iter().chain(std::iter::once(&LOCAL_CRATE)) {
+ for (&name, &def_id) in &tcx.diagnostic_items(cnum).name_to_id {
+ collect_item(tcx, &mut items, name, def_id);
+ }
+ }
+
+ items
+}
+
+pub fn provide(providers: &mut Providers) {
+ providers.diagnostic_items = diagnostic_items;
+ providers.all_diagnostic_items = all_diagnostic_items;
+}
diff --git a/compiler/rustc_passes/src/entry.rs b/compiler/rustc_passes/src/entry.rs
new file mode 100644
index 000000000..7381019a6
--- /dev/null
+++ b/compiler/rustc_passes/src/entry.rs
@@ -0,0 +1,231 @@
+use rustc_ast::{entry::EntryPointType, Attribute};
+use rustc_errors::struct_span_err;
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
+use rustc_hir::{ItemId, Node, CRATE_HIR_ID};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{DefIdTree, TyCtxt};
+use rustc_session::config::{CrateType, EntryFnType};
+use rustc_session::parse::feature_err;
+use rustc_span::symbol::sym;
+use rustc_span::{Span, Symbol, DUMMY_SP};
+
+struct EntryContext<'tcx> {
+ tcx: TyCtxt<'tcx>,
+
+ /// The function that has attribute named `main`.
+ attr_main_fn: Option<(LocalDefId, Span)>,
+
+ /// The function that has the attribute 'start' on it.
+ start_fn: Option<(LocalDefId, Span)>,
+
+ /// The functions that one might think are `main` but aren't, e.g.
+ /// main functions not defined at the top level. For diagnostics.
+ non_main_fns: Vec<Span>,
+}
+
+fn entry_fn(tcx: TyCtxt<'_>, (): ()) -> Option<(DefId, EntryFnType)> {
+ let any_exe = tcx.sess.crate_types().iter().any(|ty| *ty == CrateType::Executable);
+ if !any_exe {
+ // No need to find a main function.
+ return None;
+ }
+
+ // If the user wants no main function at all, then stop here.
+ if tcx.sess.contains_name(&tcx.hir().attrs(CRATE_HIR_ID), sym::no_main) {
+ return None;
+ }
+
+ let mut ctxt =
+ EntryContext { tcx, attr_main_fn: None, start_fn: None, non_main_fns: Vec::new() };
+
+ for id in tcx.hir().items() {
+ find_item(id, &mut ctxt);
+ }
+
+ configure_main(tcx, &ctxt)
+}
+
+// Beware, this is duplicated in `librustc_builtin_macros/test_harness.rs`
+// (with `ast::Item`), so make sure to keep them in sync.
+// A small optimization was added so that hir::Item is fetched only when needed.
+// An equivalent optimization was not applied to the duplicated code in test_harness.rs.
+fn entry_point_type(ctxt: &EntryContext<'_>, id: ItemId, at_root: bool) -> EntryPointType {
+ let attrs = ctxt.tcx.hir().attrs(id.hir_id());
+ if ctxt.tcx.sess.contains_name(attrs, sym::start) {
+ EntryPointType::Start
+ } else if ctxt.tcx.sess.contains_name(attrs, sym::rustc_main) {
+ EntryPointType::RustcMainAttr
+ } else {
+ if let Some(name) = ctxt.tcx.opt_item_name(id.def_id.to_def_id())
+ && name == sym::main {
+ if at_root {
+ // This is a top-level function so can be `main`.
+ EntryPointType::MainNamed
+ } else {
+ EntryPointType::OtherMain
+ }
+ } else {
+ EntryPointType::None
+ }
+ }
+}
+
+fn err_if_attr_found(ctxt: &EntryContext<'_>, attrs: &[Attribute], sym: Symbol) {
+ if let Some(attr) = ctxt.tcx.sess.find_by_name(attrs, sym) {
+ ctxt.tcx
+ .sess
+ .struct_span_err(
+ attr.span,
+ &format!("`{}` attribute can only be used on functions", sym),
+ )
+ .emit();
+ }
+}
+
+fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
+ let at_root = ctxt.tcx.opt_local_parent(id.def_id) == Some(CRATE_DEF_ID);
+
+ match entry_point_type(ctxt, id, at_root) {
+ EntryPointType::None => (),
+ _ if !matches!(ctxt.tcx.def_kind(id.def_id), DefKind::Fn) => {
+ let attrs = ctxt.tcx.hir().attrs(id.hir_id());
+ err_if_attr_found(ctxt, attrs, sym::start);
+ err_if_attr_found(ctxt, attrs, sym::rustc_main);
+ }
+ EntryPointType::MainNamed => (),
+ EntryPointType::OtherMain => {
+ ctxt.non_main_fns.push(ctxt.tcx.def_span(id.def_id));
+ }
+ EntryPointType::RustcMainAttr => {
+ if ctxt.attr_main_fn.is_none() {
+ ctxt.attr_main_fn = Some((id.def_id, ctxt.tcx.def_span(id.def_id.to_def_id())));
+ } else {
+ struct_span_err!(
+ ctxt.tcx.sess,
+ ctxt.tcx.def_span(id.def_id.to_def_id()),
+ E0137,
+ "multiple functions with a `#[rustc_main]` attribute"
+ )
+ .span_label(
+ ctxt.tcx.def_span(id.def_id.to_def_id()),
+ "additional `#[rustc_main]` function",
+ )
+ .span_label(ctxt.attr_main_fn.unwrap().1, "first `#[rustc_main]` function")
+ .emit();
+ }
+ }
+ EntryPointType::Start => {
+ if ctxt.start_fn.is_none() {
+ ctxt.start_fn = Some((id.def_id, ctxt.tcx.def_span(id.def_id.to_def_id())));
+ } else {
+ struct_span_err!(
+ ctxt.tcx.sess,
+ ctxt.tcx.def_span(id.def_id.to_def_id()),
+ E0138,
+ "multiple `start` functions"
+ )
+ .span_label(ctxt.start_fn.unwrap().1, "previous `#[start]` function here")
+ .span_label(ctxt.tcx.def_span(id.def_id.to_def_id()), "multiple `start` functions")
+ .emit();
+ }
+ }
+ }
+}
+
+fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId, EntryFnType)> {
+ if let Some((def_id, _)) = visitor.start_fn {
+ Some((def_id.to_def_id(), EntryFnType::Start))
+ } else if let Some((def_id, _)) = visitor.attr_main_fn {
+ Some((def_id.to_def_id(), EntryFnType::Main))
+ } else {
+ if let Some(main_def) = tcx.resolutions(()).main_def && let Some(def_id) = main_def.opt_fn_def_id() {
+ // non-local main imports are handled below
+ if let Some(def_id) = def_id.as_local() && matches!(tcx.hir().find_by_def_id(def_id), Some(Node::ForeignItem(_))) {
+ tcx.sess
+ .struct_span_err(
+ tcx.def_span(def_id),
+ "the `main` function cannot be declared in an `extern` block",
+ )
+ .emit();
+ return None;
+ }
+
+ if main_def.is_import && !tcx.features().imported_main {
+ let span = main_def.span;
+ feature_err(
+ &tcx.sess.parse_sess,
+ sym::imported_main,
+ span,
+ "using an imported function as entry point `main` is experimental",
+ )
+ .emit();
+ }
+ return Some((def_id, EntryFnType::Main));
+ }
+ no_main_err(tcx, visitor);
+ None
+ }
+}
+
+fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) {
+ let sp = tcx.def_span(CRATE_DEF_ID);
+ if *tcx.sess.parse_sess.reached_eof.borrow() {
+ // There's an unclosed brace that made the parser reach `Eof`, we shouldn't complain about
+ // the missing `fn main()` then as it might have been hidden inside an unclosed block.
+ tcx.sess.delay_span_bug(sp, "`main` not found, but expected unclosed brace error");
+ return;
+ }
+
+ // There is no main function.
+ let mut err = struct_span_err!(
+ tcx.sess,
+ DUMMY_SP,
+ E0601,
+ "`main` function not found in crate `{}`",
+ tcx.crate_name(LOCAL_CRATE)
+ );
+ let filename = &tcx.sess.local_crate_source_file;
+ let note = if !visitor.non_main_fns.is_empty() {
+ for &span in &visitor.non_main_fns {
+ err.span_note(span, "here is a function named `main`");
+ }
+ err.note("you have one or more functions named `main` not defined at the crate level");
+ err.help("consider moving the `main` function definitions");
+ // There were some functions named `main` though. Try to give the user a hint.
+ format!(
+ "the main function must be defined at the crate level{}",
+ filename.as_ref().map(|f| format!(" (in `{}`)", f.display())).unwrap_or_default()
+ )
+ } else if let Some(filename) = filename {
+ format!("consider adding a `main` function to `{}`", filename.display())
+ } else {
+ String::from("consider adding a `main` function at the crate level")
+ };
+ // The file may be empty, which leads to the diagnostic machinery not emitting this
+ // note. This is a relatively simple way to detect that case and emit a span-less
+ // note instead.
+ if tcx.sess.source_map().lookup_line(sp.hi()).is_ok() {
+ err.set_span(sp.shrink_to_hi());
+ err.span_label(sp.shrink_to_hi(), &note);
+ } else {
+ err.note(&note);
+ }
+
+ if let Some(main_def) = tcx.resolutions(()).main_def && main_def.opt_fn_def_id().is_none(){
+ // There is something at `crate::main`, but it is not a function definition.
+ err.span_label(main_def.span, "non-function item at `crate::main` is found");
+ }
+
+ if tcx.sess.teach(&err.get_code().unwrap()) {
+ err.note(
+ "If you don't know the basics of Rust, you can go look to the Rust Book \
+ to get started: https://doc.rust-lang.org/book/",
+ );
+ }
+ err.emit();
+}
+
+pub fn provide(providers: &mut Providers) {
+ *providers = Providers { entry_fn, ..*providers };
+}
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
new file mode 100644
index 000000000..5feb0e295
--- /dev/null
+++ b/compiler/rustc_passes/src/errors.rs
@@ -0,0 +1,645 @@
+use rustc_errors::{Applicability, MultiSpan};
+use rustc_macros::{LintDiagnostic, SessionDiagnostic, SessionSubdiagnostic};
+use rustc_span::{Span, Symbol};
+
+#[derive(LintDiagnostic)]
+#[lint(passes::outer_crate_level_attr)]
+pub struct OuterCrateLevelAttr;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::inner_crate_level_attr)]
+pub struct InnerCrateLevelAttr;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::ignored_attr_with_macro)]
+pub struct IgnoredAttrWithMacro<'a> {
+ pub sym: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::ignored_attr)]
+pub struct IgnoredAttr<'a> {
+ pub sym: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::inline_ignored_function_prototype)]
+pub struct IgnoredInlineAttrFnProto;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::inline_ignored_constants)]
+#[warn_]
+#[note]
+pub struct IgnoredInlineAttrConstants;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::inline_not_fn_or_closure, code = "E0518")]
+pub struct InlineNotFnOrClosure {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub defn_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::no_coverage_ignored_function_prototype)]
+pub struct IgnoredNoCoverageFnProto;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::no_coverage_propagate)]
+pub struct IgnoredNoCoveragePropagate;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::no_coverage_fn_defn)]
+pub struct IgnoredNoCoverageFnDefn;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::no_coverage_not_coverable, code = "E0788")]
+pub struct IgnoredNoCoverageNotCoverable {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_fn)]
+pub struct AttrShouldBeAppliedToFn {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::naked_tracked_caller, code = "E0736")]
+pub struct NakedTrackedCaller {
+ #[primary_span]
+ pub attr_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_fn, code = "E0739")]
+pub struct TrackedCallerWrongLocation {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_struct_enum, code = "E0701")]
+pub struct NonExhaustiveWrongLocation {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_trait)]
+pub struct AttrShouldBeAppliedToTrait {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub defn_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::target_feature_on_statement)]
+pub struct TargetFeatureOnStatement;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_static)]
+pub struct AttrShouldBeAppliedToStatic {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_expect_str)]
+pub struct DocExpectStr<'a> {
+ #[primary_span]
+ pub attr_span: Span,
+ pub attr_name: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_empty)]
+pub struct DocAliasEmpty<'a> {
+ #[primary_span]
+ pub span: Span,
+ pub attr_str: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_bad_char)]
+pub struct DocAliasBadChar<'a> {
+ #[primary_span]
+ pub span: Span,
+ pub attr_str: &'a str,
+ pub char_: char,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_start_end)]
+pub struct DocAliasStartEnd<'a> {
+ #[primary_span]
+ pub span: Span,
+ pub attr_str: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_bad_location)]
+pub struct DocAliasBadLocation<'a> {
+ #[primary_span]
+ pub span: Span,
+ pub attr_str: &'a str,
+ pub location: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_not_an_alias)]
+pub struct DocAliasNotAnAlias<'a> {
+ #[primary_span]
+ pub span: Span,
+ pub attr_str: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_alias_duplicated)]
+pub struct DocAliasDuplicated {
+ #[label]
+ pub first_defn: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_not_string_literal)]
+pub struct DocAliasNotStringLiteral {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_malformed)]
+pub struct DocAliasMalformed {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_keyword_empty_mod)]
+pub struct DocKeywordEmptyMod {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_keyword_not_mod)]
+pub struct DocKeywordNotMod {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_keyword_invalid_ident)]
+pub struct DocKeywordInvalidIdent {
+ #[primary_span]
+ pub span: Span,
+ pub doc_keyword: Symbol,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_fake_variadic_not_valid)]
+pub struct DocFakeVariadicNotValid {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_keyword_only_impl)]
+pub struct DocKeywordOnlyImpl {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_inline_conflict)]
+#[help]
+pub struct DocKeywordConflict {
+ #[primary_span]
+ pub spans: MultiSpan,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_inline_only_use)]
+#[note]
+pub struct DocInlineOnlyUse {
+ #[label]
+ pub attr_span: Span,
+ #[label(passes::not_a_use_item_label)]
+ pub item_span: Option<Span>,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_attr_not_crate_level)]
+pub struct DocAttrNotCrateLevel<'a> {
+ #[primary_span]
+ pub span: Span,
+ pub attr_name: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_unknown)]
+pub struct DocTestUnknown {
+ pub path: String,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_takes_list)]
+pub struct DocTestTakesList;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_primitive)]
+pub struct DocPrimitive;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_unknown_any)]
+pub struct DocTestUnknownAny {
+ pub path: String,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_unknown_spotlight)]
+#[note]
+#[note(passes::no_op_note)]
+pub struct DocTestUnknownSpotlight {
+ pub path: String,
+ #[suggestion_short(applicability = "machine-applicable", code = "notable_trait")]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_unknown_include)]
+pub struct DocTestUnknownInclude {
+ pub path: String,
+ pub value: String,
+ pub inner: &'static str,
+ #[suggestion(code = "#{inner}[doc = include_str!(\"{value}\")]")]
+ pub sugg: (Span, Applicability),
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_invalid)]
+pub struct DocInvalid;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::pass_by_value)]
+pub struct PassByValue {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::allow_incoherent_impl)]
+pub struct AllowIncoherentImpl {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::has_incoherent_inherent_impl)]
+pub struct HasIncoherentInherentImpl {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::must_use_async)]
+pub struct MustUseAsync {
+ #[label]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::must_use_no_effect)]
+pub struct MustUseNoEffect {
+ pub article: &'static str,
+ pub target: rustc_hir::Target,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::must_not_suspend)]
+pub struct MustNotSuspend {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::cold)]
+#[warn_]
+pub struct Cold {
+ #[label]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::link)]
+#[warn_]
+pub struct Link {
+ #[label]
+ pub span: Option<Span>,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::link_name)]
+#[warn_]
+pub struct LinkName<'a> {
+ #[help]
+ pub attr_span: Option<Span>,
+ #[label]
+ pub span: Span,
+ pub value: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::no_link)]
+pub struct NoLink {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::export_name)]
+pub struct ExportName {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_layout_scalar_valid_range_not_struct)]
+pub struct RustcLayoutScalarValidRangeNotStruct {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_layout_scalar_valid_range_arg)]
+pub struct RustcLayoutScalarValidRangeArg {
+ #[primary_span]
+ pub attr_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_legacy_const_generics_only)]
+pub struct RustcLegacyConstGenericsOnly {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub param_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_legacy_const_generics_index)]
+pub struct RustcLegacyConstGenericsIndex {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub generics_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_legacy_const_generics_index_exceed)]
+pub struct RustcLegacyConstGenericsIndexExceed {
+ #[primary_span]
+ #[label]
+ pub span: Span,
+ pub arg_count: usize,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_legacy_const_generics_index_negative)]
+pub struct RustcLegacyConstGenericsIndexNegative {
+ #[primary_span]
+ pub invalid_args: Vec<Span>,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_dirty_clean)]
+pub struct RustcDirtyClean {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::link_section)]
+#[warn_]
+pub struct LinkSection {
+ #[label]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::no_mangle_foreign)]
+#[warn_]
+#[note]
+pub struct NoMangleForeign {
+ #[label]
+ pub span: Span,
+ #[suggestion(applicability = "machine-applicable")]
+ pub attr_span: Span,
+ pub foreign_item_kind: &'static str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::no_mangle)]
+#[warn_]
+pub struct NoMangle {
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::repr_ident, code = "E0565")]
+pub struct ReprIdent {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::repr_conflicting, code = "E0566")]
+pub struct ReprConflicting;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::used_static)]
+pub struct UsedStatic {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::used_compiler_linker)]
+pub struct UsedCompilerLinker {
+ #[primary_span]
+ pub spans: Vec<Span>,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::allow_internal_unstable)]
+pub struct AllowInternalUnstable {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::debug_visualizer_placement)]
+pub struct DebugVisualizerPlacement {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::debug_visualizer_invalid)]
+#[note(passes::note_1)]
+#[note(passes::note_2)]
+#[note(passes::note_3)]
+pub struct DebugVisualizerInvalid {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_allow_const_fn_unstable)]
+pub struct RustcAllowConstFnUnstable {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_std_internal_symbol)]
+pub struct RustcStdInternalSymbol {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::const_trait)]
+pub struct ConstTrait {
+ #[primary_span]
+ pub attr_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::stability_promotable)]
+pub struct StabilityPromotable {
+ #[primary_span]
+ pub attr_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::deprecated)]
+pub struct Deprecated;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::macro_use)]
+pub struct MacroUse {
+ pub name: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::macro_export)]
+pub struct MacroExport;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::plugin_registrar)]
+pub struct PluginRegistrar;
+
+#[derive(SessionSubdiagnostic)]
+pub enum UnusedNote {
+ #[note(passes::unused_empty_lints_note)]
+ EmptyList { name: Symbol },
+ #[note(passes::unused_no_lints_note)]
+ NoLints { name: Symbol },
+ #[note(passes::unused_default_method_body_const_note)]
+ DefaultMethodBodyConst,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::unused)]
+pub struct Unused {
+ #[suggestion(applicability = "machine-applicable")]
+ pub attr_span: Span,
+ #[subdiagnostic]
+ pub note: UnusedNote,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::non_exported_macro_invalid_attrs, code = "E0518")]
+pub struct NonExportedMacroInvalidAttrs {
+ #[primary_span]
+ #[label]
+ pub attr_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::unused_duplicate)]
+pub struct UnusedDuplicate {
+ #[primary_span]
+ #[suggestion(code = "", applicability = "machine-applicable")]
+ pub this: Span,
+ #[note]
+ pub other: Span,
+ #[warn_]
+ pub warning: Option<()>,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::unused_multiple)]
+pub struct UnusedMultiple {
+ #[primary_span]
+ #[suggestion(code = "", applicability = "machine-applicable")]
+ pub this: Span,
+ #[note]
+ pub other: Span,
+ pub name: Symbol,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_lint_opt_ty)]
+pub struct RustcLintOptTy {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::rustc_lint_opt_deny_field_access)]
+pub struct RustcLintOptDenyFieldAccess {
+ #[primary_span]
+ pub attr_span: Span,
+ #[label]
+ pub span: Span,
+}
diff --git a/compiler/rustc_passes/src/hir_id_validator.rs b/compiler/rustc_passes/src/hir_id_validator.rs
new file mode 100644
index 000000000..212ea9e57
--- /dev/null
+++ b/compiler/rustc_passes/src/hir_id_validator.rs
@@ -0,0 +1,164 @@
+use rustc_data_structures::sync::Lock;
+use rustc_hir as hir;
+use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
+use rustc_hir::intravisit;
+use rustc_hir::{HirId, ItemLocalId};
+use rustc_index::bit_set::GrowableBitSet;
+use rustc_middle::hir::map::Map;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::TyCtxt;
+
+pub fn check_crate(tcx: TyCtxt<'_>) {
+ tcx.dep_graph.assert_ignored();
+
+ if tcx.sess.opts.unstable_opts.hir_stats {
+ crate::hir_stats::print_hir_stats(tcx);
+ }
+
+ #[cfg(debug_assertions)]
+ {
+ let errors = Lock::new(Vec::new());
+ let hir_map = tcx.hir();
+
+ hir_map.par_for_each_module(|module_id| {
+ let mut v = HirIdValidator {
+ hir_map,
+ owner: None,
+ hir_ids_seen: Default::default(),
+ errors: &errors,
+ };
+
+ tcx.hir().visit_item_likes_in_module(module_id, &mut v);
+ });
+
+ let errors = errors.into_inner();
+
+ if !errors.is_empty() {
+ let message = errors.iter().fold(String::new(), |s1, s2| s1 + "\n" + s2);
+ tcx.sess.delay_span_bug(rustc_span::DUMMY_SP, &message);
+ }
+ }
+}
+
+struct HirIdValidator<'a, 'hir> {
+ hir_map: Map<'hir>,
+ owner: Option<LocalDefId>,
+ hir_ids_seen: GrowableBitSet<ItemLocalId>,
+ errors: &'a Lock<Vec<String>>,
+}
+
+impl<'a, 'hir> HirIdValidator<'a, 'hir> {
+ fn new_visitor(&self, hir_map: Map<'hir>) -> HirIdValidator<'a, 'hir> {
+ HirIdValidator {
+ hir_map,
+ owner: None,
+ hir_ids_seen: Default::default(),
+ errors: self.errors,
+ }
+ }
+
+ #[cold]
+ #[inline(never)]
+ fn error(&self, f: impl FnOnce() -> String) {
+ self.errors.lock().push(f());
+ }
+
+ fn check<F: FnOnce(&mut HirIdValidator<'a, 'hir>)>(&mut self, owner: LocalDefId, walk: F) {
+ assert!(self.owner.is_none());
+ self.owner = Some(owner);
+ walk(self);
+
+ if owner == CRATE_DEF_ID {
+ return;
+ }
+
+ // There's always at least one entry for the owning item itself
+ let max = self
+ .hir_ids_seen
+ .iter()
+ .map(|local_id| local_id.as_usize())
+ .max()
+ .expect("owning item has no entry");
+
+ if max != self.hir_ids_seen.len() - 1 {
+ // Collect the missing ItemLocalIds
+ let missing: Vec<_> = (0..=max as u32)
+ .filter(|&i| !self.hir_ids_seen.contains(ItemLocalId::from_u32(i)))
+ .collect();
+
+ // Try to map those to something more useful
+ let mut missing_items = Vec::with_capacity(missing.len());
+
+ for local_id in missing {
+ let hir_id = HirId { owner, local_id: ItemLocalId::from_u32(local_id) };
+
+ trace!("missing hir id {:#?}", hir_id);
+
+ missing_items.push(format!(
+ "[local_id: {}, owner: {}]",
+ local_id,
+ self.hir_map.def_path(owner).to_string_no_crate_verbose()
+ ));
+ }
+ self.error(|| {
+ format!(
+ "ItemLocalIds not assigned densely in {}. \
+ Max ItemLocalId = {}, missing IDs = {:?}; seens IDs = {:?}",
+ self.hir_map.def_path(owner).to_string_no_crate_verbose(),
+ max,
+ missing_items,
+ self.hir_ids_seen
+ .iter()
+ .map(|local_id| HirId { owner, local_id })
+ .map(|h| format!("({:?} {})", h, self.hir_map.node_to_string(h)))
+ .collect::<Vec<_>>()
+ )
+ });
+ }
+ }
+}
+
+impl<'a, 'hir> intravisit::Visitor<'hir> for HirIdValidator<'a, 'hir> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.hir_map
+ }
+
+ fn visit_item(&mut self, i: &'hir hir::Item<'hir>) {
+ let mut inner_visitor = self.new_visitor(self.hir_map);
+ inner_visitor.check(i.def_id, |this| intravisit::walk_item(this, i));
+ }
+
+ fn visit_id(&mut self, hir_id: HirId) {
+ let owner = self.owner.expect("no owner");
+
+ if owner != hir_id.owner {
+ self.error(|| {
+ format!(
+ "HirIdValidator: The recorded owner of {} is {} instead of {}",
+ self.hir_map.node_to_string(hir_id),
+ self.hir_map.def_path(hir_id.owner).to_string_no_crate_verbose(),
+ self.hir_map.def_path(owner).to_string_no_crate_verbose()
+ )
+ });
+ }
+
+ self.hir_ids_seen.insert(hir_id.local_id);
+ }
+
+ fn visit_foreign_item(&mut self, i: &'hir hir::ForeignItem<'hir>) {
+ let mut inner_visitor = self.new_visitor(self.hir_map);
+ inner_visitor.check(i.def_id, |this| intravisit::walk_foreign_item(this, i));
+ }
+
+ fn visit_trait_item(&mut self, i: &'hir hir::TraitItem<'hir>) {
+ let mut inner_visitor = self.new_visitor(self.hir_map);
+ inner_visitor.check(i.def_id, |this| intravisit::walk_trait_item(this, i));
+ }
+
+ fn visit_impl_item(&mut self, i: &'hir hir::ImplItem<'hir>) {
+ let mut inner_visitor = self.new_visitor(self.hir_map);
+ inner_visitor.check(i.def_id, |this| intravisit::walk_impl_item(this, i));
+ }
+}
diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs
new file mode 100644
index 000000000..a3be827a7
--- /dev/null
+++ b/compiler/rustc_passes/src/hir_stats.rs
@@ -0,0 +1,344 @@
+// The visitors in this module collect sizes and counts of the most important
+// pieces of AST and HIR. The resulting numbers are good approximations but not
+// completely accurate (some things might be counted twice, others missed).
+
+use rustc_ast::visit as ast_visit;
+use rustc_ast::visit::BoundKind;
+use rustc_ast::{self as ast, AttrId, NodeId};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir as hir;
+use rustc_hir::intravisit as hir_visit;
+use rustc_hir::HirId;
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty::TyCtxt;
+use rustc_middle::util::common::to_readable_str;
+use rustc_span::Span;
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+enum Id {
+ Node(HirId),
+ Attr(AttrId),
+ None,
+}
+
+struct NodeData {
+ count: usize,
+ size: usize,
+}
+
+struct StatCollector<'k> {
+ krate: Option<Map<'k>>,
+ data: FxHashMap<&'static str, NodeData>,
+ seen: FxHashSet<Id>,
+}
+
+pub fn print_hir_stats(tcx: TyCtxt<'_>) {
+ let mut collector = StatCollector {
+ krate: Some(tcx.hir()),
+ data: FxHashMap::default(),
+ seen: FxHashSet::default(),
+ };
+ tcx.hir().walk_toplevel_module(&mut collector);
+ tcx.hir().walk_attributes(&mut collector);
+ collector.print("HIR STATS");
+}
+
+pub fn print_ast_stats(krate: &ast::Crate, title: &str) {
+ let mut collector =
+ StatCollector { krate: None, data: FxHashMap::default(), seen: FxHashSet::default() };
+ ast_visit::walk_crate(&mut collector, krate);
+ collector.print(title);
+}
+
+impl<'k> StatCollector<'k> {
+ fn record<T>(&mut self, label: &'static str, id: Id, node: &T) {
+ if id != Id::None && !self.seen.insert(id) {
+ return;
+ }
+
+ let entry = self.data.entry(label).or_insert(NodeData { count: 0, size: 0 });
+
+ entry.count += 1;
+ entry.size = std::mem::size_of_val(node);
+ }
+
+ fn print(&self, title: &str) {
+ let mut stats: Vec<_> = self.data.iter().collect();
+
+ stats.sort_by_key(|&(_, ref d)| d.count * d.size);
+
+ let mut total_size = 0;
+
+ eprintln!("\n{}\n", title);
+
+ eprintln!("{:<18}{:>18}{:>14}{:>14}", "Name", "Accumulated Size", "Count", "Item Size");
+ eprintln!("----------------------------------------------------------------");
+
+ for (label, data) in stats {
+ eprintln!(
+ "{:<18}{:>18}{:>14}{:>14}",
+ label,
+ to_readable_str(data.count * data.size),
+ to_readable_str(data.count),
+ to_readable_str(data.size)
+ );
+
+ total_size += data.count * data.size;
+ }
+ eprintln!("----------------------------------------------------------------");
+ eprintln!("{:<18}{:>18}\n", "Total", to_readable_str(total_size));
+ }
+}
+
+impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
+ fn visit_param(&mut self, param: &'v hir::Param<'v>) {
+ self.record("Param", Id::Node(param.hir_id), param);
+ hir_visit::walk_param(self, param)
+ }
+
+ fn visit_nested_item(&mut self, id: hir::ItemId) {
+ let nested_item = self.krate.unwrap().item(id);
+ self.visit_item(nested_item)
+ }
+
+ fn visit_nested_trait_item(&mut self, trait_item_id: hir::TraitItemId) {
+ let nested_trait_item = self.krate.unwrap().trait_item(trait_item_id);
+ self.visit_trait_item(nested_trait_item)
+ }
+
+ fn visit_nested_impl_item(&mut self, impl_item_id: hir::ImplItemId) {
+ let nested_impl_item = self.krate.unwrap().impl_item(impl_item_id);
+ self.visit_impl_item(nested_impl_item)
+ }
+
+ fn visit_nested_foreign_item(&mut self, id: hir::ForeignItemId) {
+ let nested_foreign_item = self.krate.unwrap().foreign_item(id);
+ self.visit_foreign_item(nested_foreign_item);
+ }
+
+ fn visit_nested_body(&mut self, body_id: hir::BodyId) {
+ let nested_body = self.krate.unwrap().body(body_id);
+ self.visit_body(nested_body)
+ }
+
+ fn visit_item(&mut self, i: &'v hir::Item<'v>) {
+ self.record("Item", Id::Node(i.hir_id()), i);
+ hir_visit::walk_item(self, i)
+ }
+
+ fn visit_foreign_item(&mut self, i: &'v hir::ForeignItem<'v>) {
+ self.record("ForeignItem", Id::Node(i.hir_id()), i);
+ hir_visit::walk_foreign_item(self, i)
+ }
+
+ fn visit_local(&mut self, l: &'v hir::Local<'v>) {
+ self.record("Local", Id::Node(l.hir_id), l);
+ hir_visit::walk_local(self, l)
+ }
+
+ fn visit_block(&mut self, b: &'v hir::Block<'v>) {
+ self.record("Block", Id::Node(b.hir_id), b);
+ hir_visit::walk_block(self, b)
+ }
+
+ fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
+ self.record("Stmt", Id::Node(s.hir_id), s);
+ hir_visit::walk_stmt(self, s)
+ }
+
+ fn visit_arm(&mut self, a: &'v hir::Arm<'v>) {
+ self.record("Arm", Id::Node(a.hir_id), a);
+ hir_visit::walk_arm(self, a)
+ }
+
+ fn visit_pat(&mut self, p: &'v hir::Pat<'v>) {
+ self.record("Pat", Id::Node(p.hir_id), p);
+ hir_visit::walk_pat(self, p)
+ }
+
+ fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
+ self.record("Expr", Id::Node(ex.hir_id), ex);
+ hir_visit::walk_expr(self, ex)
+ }
+
+ fn visit_ty(&mut self, t: &'v hir::Ty<'v>) {
+ self.record("Ty", Id::Node(t.hir_id), t);
+ hir_visit::walk_ty(self, t)
+ }
+
+ fn visit_fn(
+ &mut self,
+ fk: hir_visit::FnKind<'v>,
+ fd: &'v hir::FnDecl<'v>,
+ b: hir::BodyId,
+ s: Span,
+ id: hir::HirId,
+ ) {
+ self.record("FnDecl", Id::None, fd);
+ hir_visit::walk_fn(self, fk, fd, b, s, id)
+ }
+
+ fn visit_where_predicate(&mut self, predicate: &'v hir::WherePredicate<'v>) {
+ self.record("WherePredicate", Id::None, predicate);
+ hir_visit::walk_where_predicate(self, predicate)
+ }
+
+ fn visit_trait_item(&mut self, ti: &'v hir::TraitItem<'v>) {
+ self.record("TraitItem", Id::Node(ti.hir_id()), ti);
+ hir_visit::walk_trait_item(self, ti)
+ }
+
+ fn visit_impl_item(&mut self, ii: &'v hir::ImplItem<'v>) {
+ self.record("ImplItem", Id::Node(ii.hir_id()), ii);
+ hir_visit::walk_impl_item(self, ii)
+ }
+
+ fn visit_param_bound(&mut self, bounds: &'v hir::GenericBound<'v>) {
+ self.record("GenericBound", Id::None, bounds);
+ hir_visit::walk_param_bound(self, bounds)
+ }
+
+ fn visit_field_def(&mut self, s: &'v hir::FieldDef<'v>) {
+ self.record("FieldDef", Id::Node(s.hir_id), s);
+ hir_visit::walk_field_def(self, s)
+ }
+
+ fn visit_variant(
+ &mut self,
+ v: &'v hir::Variant<'v>,
+ g: &'v hir::Generics<'v>,
+ item_id: hir::HirId,
+ ) {
+ self.record("Variant", Id::None, v);
+ hir_visit::walk_variant(self, v, g, item_id)
+ }
+
+ fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
+ self.record("Lifetime", Id::Node(lifetime.hir_id), lifetime);
+ hir_visit::walk_lifetime(self, lifetime)
+ }
+
+ fn visit_qpath(&mut self, qpath: &'v hir::QPath<'v>, id: hir::HirId, span: Span) {
+ self.record("QPath", Id::None, qpath);
+ hir_visit::walk_qpath(self, qpath, id, span)
+ }
+
+ fn visit_path(&mut self, path: &'v hir::Path<'v>, _id: hir::HirId) {
+ self.record("Path", Id::None, path);
+ hir_visit::walk_path(self, path)
+ }
+
+ fn visit_path_segment(&mut self, path_span: Span, path_segment: &'v hir::PathSegment<'v>) {
+ self.record("PathSegment", Id::None, path_segment);
+ hir_visit::walk_path_segment(self, path_span, path_segment)
+ }
+
+ fn visit_assoc_type_binding(&mut self, type_binding: &'v hir::TypeBinding<'v>) {
+ self.record("TypeBinding", Id::Node(type_binding.hir_id), type_binding);
+ hir_visit::walk_assoc_type_binding(self, type_binding)
+ }
+
+ fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
+ self.record("Attribute", Id::Attr(attr.id), attr);
+ }
+}
+
+impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
+ fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
+ self.record("ForeignItem", Id::None, i);
+ ast_visit::walk_foreign_item(self, i)
+ }
+
+ fn visit_item(&mut self, i: &'v ast::Item) {
+ self.record("Item", Id::None, i);
+ ast_visit::walk_item(self, i)
+ }
+
+ fn visit_local(&mut self, l: &'v ast::Local) {
+ self.record("Local", Id::None, l);
+ ast_visit::walk_local(self, l)
+ }
+
+ fn visit_block(&mut self, b: &'v ast::Block) {
+ self.record("Block", Id::None, b);
+ ast_visit::walk_block(self, b)
+ }
+
+ fn visit_stmt(&mut self, s: &'v ast::Stmt) {
+ self.record("Stmt", Id::None, s);
+ ast_visit::walk_stmt(self, s)
+ }
+
+ fn visit_arm(&mut self, a: &'v ast::Arm) {
+ self.record("Arm", Id::None, a);
+ ast_visit::walk_arm(self, a)
+ }
+
+ fn visit_pat(&mut self, p: &'v ast::Pat) {
+ self.record("Pat", Id::None, p);
+ ast_visit::walk_pat(self, p)
+ }
+
+ fn visit_expr(&mut self, ex: &'v ast::Expr) {
+ self.record("Expr", Id::None, ex);
+ ast_visit::walk_expr(self, ex)
+ }
+
+ fn visit_ty(&mut self, t: &'v ast::Ty) {
+ self.record("Ty", Id::None, t);
+ ast_visit::walk_ty(self, t)
+ }
+
+ fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, s: Span, _: NodeId) {
+ self.record("FnDecl", Id::None, fk.decl());
+ ast_visit::walk_fn(self, fk, s)
+ }
+
+ fn visit_assoc_item(&mut self, item: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
+ let label = match ctxt {
+ ast_visit::AssocCtxt::Trait => "TraitItem",
+ ast_visit::AssocCtxt::Impl => "ImplItem",
+ };
+ self.record(label, Id::None, item);
+ ast_visit::walk_assoc_item(self, item, ctxt);
+ }
+
+ fn visit_param_bound(&mut self, bounds: &'v ast::GenericBound, _ctxt: BoundKind) {
+ self.record("GenericBound", Id::None, bounds);
+ ast_visit::walk_param_bound(self, bounds)
+ }
+
+ fn visit_field_def(&mut self, s: &'v ast::FieldDef) {
+ self.record("FieldDef", Id::None, s);
+ ast_visit::walk_field_def(self, s)
+ }
+
+ fn visit_variant(&mut self, v: &'v ast::Variant) {
+ self.record("Variant", Id::None, v);
+ ast_visit::walk_variant(self, v)
+ }
+
+ fn visit_lifetime(&mut self, lifetime: &'v ast::Lifetime, _: ast_visit::LifetimeCtxt) {
+ self.record("Lifetime", Id::None, lifetime);
+ ast_visit::walk_lifetime(self, lifetime)
+ }
+
+ fn visit_mac_call(&mut self, mac: &'v ast::MacCall) {
+ self.record("MacCall", Id::None, mac);
+ ast_visit::walk_mac(self, mac)
+ }
+
+ fn visit_path_segment(&mut self, path_span: Span, path_segment: &'v ast::PathSegment) {
+ self.record("PathSegment", Id::None, path_segment);
+ ast_visit::walk_path_segment(self, path_span, path_segment)
+ }
+
+ fn visit_assoc_constraint(&mut self, constraint: &'v ast::AssocConstraint) {
+ self.record("AssocConstraint", Id::None, constraint);
+ ast_visit::walk_assoc_constraint(self, constraint)
+ }
+
+ fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
+ self.record("Attribute", Id::None, attr);
+ }
+}
diff --git a/compiler/rustc_passes/src/lang_items.rs b/compiler/rustc_passes/src/lang_items.rs
new file mode 100644
index 000000000..79900a90a
--- /dev/null
+++ b/compiler/rustc_passes/src/lang_items.rs
@@ -0,0 +1,278 @@
+//! Detecting language items.
+//!
+//! Language items are items that represent concepts intrinsic to the language
+//! itself. Examples are:
+//!
+//! * Traits that specify "kinds"; e.g., `Sync`, `Send`.
+//! * Traits that represent operators; e.g., `Add`, `Sub`, `Index`.
+//! * Functions called by the compiler itself.
+
+use crate::check_attr::target_from_impl_item;
+use crate::weak_lang_items;
+
+use rustc_errors::{pluralize, struct_span_err};
+use rustc_hir as hir;
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::DefId;
+use rustc_hir::lang_items::{extract, GenericRequirement, ITEM_REFS};
+use rustc_hir::{HirId, LangItem, LanguageItems, Target};
+use rustc_middle::ty::TyCtxt;
+use rustc_session::cstore::ExternCrate;
+use rustc_span::Span;
+
+use rustc_middle::ty::query::Providers;
+
+struct LanguageItemCollector<'tcx> {
+ items: LanguageItems,
+ tcx: TyCtxt<'tcx>,
+}
+
+impl<'tcx> LanguageItemCollector<'tcx> {
+ fn new(tcx: TyCtxt<'tcx>) -> LanguageItemCollector<'tcx> {
+ LanguageItemCollector { tcx, items: LanguageItems::new() }
+ }
+
+ fn check_for_lang(&mut self, actual_target: Target, hir_id: HirId) {
+ let attrs = self.tcx.hir().attrs(hir_id);
+ if let Some((value, span)) = extract(&attrs) {
+ match ITEM_REFS.get(&value).cloned() {
+ // Known lang item with attribute on correct target.
+ Some((item_index, expected_target)) if actual_target == expected_target => {
+ self.collect_item_extended(item_index, hir_id, span);
+ }
+ // Known lang item with attribute on incorrect target.
+ Some((_, expected_target)) => {
+ struct_span_err!(
+ self.tcx.sess,
+ span,
+ E0718,
+ "`{}` language item must be applied to a {}",
+ value,
+ expected_target,
+ )
+ .span_label(
+ span,
+ format!(
+ "attribute should be applied to a {}, not a {}",
+ expected_target, actual_target,
+ ),
+ )
+ .emit();
+ }
+ // Unknown lang item.
+ _ => {
+ struct_span_err!(
+ self.tcx.sess,
+ span,
+ E0522,
+ "definition of an unknown language item: `{}`",
+ value
+ )
+ .span_label(span, format!("definition of unknown language item `{}`", value))
+ .emit();
+ }
+ }
+ }
+ }
+
+ fn collect_item(&mut self, item_index: usize, item_def_id: DefId) {
+ // Check for duplicates.
+ if let Some(original_def_id) = self.items.items[item_index] {
+ if original_def_id != item_def_id {
+ let lang_item = LangItem::from_u32(item_index as u32).unwrap();
+ let name = lang_item.name();
+ let mut err = match self.tcx.hir().span_if_local(item_def_id) {
+ Some(span) => struct_span_err!(
+ self.tcx.sess,
+ span,
+ E0152,
+ "found duplicate lang item `{}`",
+ name
+ ),
+ None => match self.tcx.extern_crate(item_def_id) {
+ Some(ExternCrate { dependency_of, .. }) => {
+ self.tcx.sess.struct_err(&format!(
+ "duplicate lang item in crate `{}` (which `{}` depends on): `{}`.",
+ self.tcx.crate_name(item_def_id.krate),
+ self.tcx.crate_name(*dependency_of),
+ name
+ ))
+ }
+ _ => self.tcx.sess.struct_err(&format!(
+ "duplicate lang item in crate `{}`: `{}`.",
+ self.tcx.crate_name(item_def_id.krate),
+ name
+ )),
+ },
+ };
+ if let Some(span) = self.tcx.hir().span_if_local(original_def_id) {
+ err.span_note(span, "the lang item is first defined here");
+ } else {
+ match self.tcx.extern_crate(original_def_id) {
+ Some(ExternCrate { dependency_of, .. }) => {
+ err.note(&format!(
+ "the lang item is first defined in crate `{}` (which `{}` depends on)",
+ self.tcx.crate_name(original_def_id.krate),
+ self.tcx.crate_name(*dependency_of)
+ ));
+ }
+ _ => {
+ err.note(&format!(
+ "the lang item is first defined in crate `{}`.",
+ self.tcx.crate_name(original_def_id.krate)
+ ));
+ }
+ }
+ let mut note_def = |which, def_id: DefId| {
+ let crate_name = self.tcx.crate_name(def_id.krate);
+ let note = if def_id.is_local() {
+ format!("{} definition in the local crate (`{}`)", which, crate_name)
+ } else {
+ let paths: Vec<_> = self
+ .tcx
+ .crate_extern_paths(def_id.krate)
+ .iter()
+ .map(|p| p.display().to_string())
+ .collect();
+ format!(
+ "{} definition in `{}` loaded from {}",
+ which,
+ crate_name,
+ paths.join(", ")
+ )
+ };
+ err.note(&note);
+ };
+ note_def("first", original_def_id);
+ note_def("second", item_def_id);
+ }
+ err.emit();
+ }
+ }
+
+ // Matched.
+ self.items.items[item_index] = Some(item_def_id);
+ if let Some(group) = LangItem::from_u32(item_index as u32).unwrap().group() {
+ self.items.groups[group as usize].push(item_def_id);
+ }
+ }
+
+ // Like collect_item() above, but also checks whether the lang item is declared
+ // with the right number of generic arguments.
+ fn collect_item_extended(&mut self, item_index: usize, hir_id: HirId, span: Span) {
+ let item_def_id = self.tcx.hir().local_def_id(hir_id).to_def_id();
+ let lang_item = LangItem::from_u32(item_index as u32).unwrap();
+ let name = lang_item.name();
+
+ // Now check whether the lang_item has the expected number of generic
+ // arguments. Generally speaking, binary and indexing operations have
+ // one (for the RHS/index), unary operations have none, the closure
+ // traits have one for the argument list, generators have one for the
+ // resume argument, and ordering/equality relations have one for the RHS
+ // Some other types like Box and various functions like drop_in_place
+ // have minimum requirements.
+
+ if let hir::Node::Item(hir::Item { kind, span: item_span, .. }) = self.tcx.hir().get(hir_id)
+ {
+ let (actual_num, generics_span) = match kind.generics() {
+ Some(generics) => (generics.params.len(), generics.span),
+ None => (0, *item_span),
+ };
+
+ let required = match lang_item.required_generics() {
+ GenericRequirement::Exact(num) if num != actual_num => {
+ Some((format!("{}", num), pluralize!(num)))
+ }
+ GenericRequirement::Minimum(num) if actual_num < num => {
+ Some((format!("at least {}", num), pluralize!(num)))
+ }
+ // If the number matches, or there is no requirement, handle it normally
+ _ => None,
+ };
+
+ if let Some((range_str, pluralized)) = required {
+ // We are issuing E0718 "incorrect target" here, because while the
+ // item kind of the target is correct, the target is still wrong
+ // because of the wrong number of generic arguments.
+ struct_span_err!(
+ self.tcx.sess,
+ span,
+ E0718,
+ "`{}` language item must be applied to a {} with {} generic argument{}",
+ name,
+ kind.descr(),
+ range_str,
+ pluralized,
+ )
+ .span_label(
+ generics_span,
+ format!(
+ "this {} has {} generic argument{}",
+ kind.descr(),
+ actual_num,
+ pluralize!(actual_num),
+ ),
+ )
+ .emit();
+
+ // return early to not collect the lang item
+ return;
+ }
+ }
+
+ self.collect_item(item_index, item_def_id);
+ }
+}
+
+/// Traverses and collects all the lang items in all crates.
+fn get_lang_items(tcx: TyCtxt<'_>, (): ()) -> LanguageItems {
+ // Initialize the collector.
+ let mut collector = LanguageItemCollector::new(tcx);
+
+ // Collect lang items in other crates.
+ for &cnum in tcx.crates(()).iter() {
+ for &(def_id, item_index) in tcx.defined_lang_items(cnum).iter() {
+ collector.collect_item(item_index, def_id);
+ }
+ }
+
+ // Collect lang items in this crate.
+ let crate_items = tcx.hir_crate_items(());
+
+ for id in crate_items.items() {
+ collector.check_for_lang(Target::from_def_kind(tcx.def_kind(id.def_id)), id.hir_id());
+
+ if matches!(tcx.def_kind(id.def_id), DefKind::Enum) {
+ let item = tcx.hir().item(id);
+ if let hir::ItemKind::Enum(def, ..) = &item.kind {
+ for variant in def.variants {
+ collector.check_for_lang(Target::Variant, variant.id);
+ }
+ }
+ }
+ }
+
+ // FIXME: avoid calling trait_item() when possible
+ for id in crate_items.trait_items() {
+ let item = tcx.hir().trait_item(id);
+ collector.check_for_lang(Target::from_trait_item(item), item.hir_id())
+ }
+
+ // FIXME: avoid calling impl_item() when possible
+ for id in crate_items.impl_items() {
+ let item = tcx.hir().impl_item(id);
+ collector.check_for_lang(target_from_impl_item(tcx, item), item.hir_id())
+ }
+
+ // Extract out the found lang items.
+ let LanguageItemCollector { mut items, .. } = collector;
+
+ // Find all required but not-yet-defined lang items.
+ weak_lang_items::check_crate(tcx, &mut items);
+
+ items
+}
+
+pub fn provide(providers: &mut Providers) {
+ providers.get_lang_items = get_lang_items;
+}
diff --git a/compiler/rustc_passes/src/layout_test.rs b/compiler/rustc_passes/src/layout_test.rs
new file mode 100644
index 000000000..fd03f6571
--- /dev/null
+++ b/compiler/rustc_passes/src/layout_test.rs
@@ -0,0 +1,132 @@
+use rustc_ast::Attribute;
+use rustc_hir::def::DefKind;
+use rustc_hir::def_id::LocalDefId;
+use rustc_middle::ty::layout::{HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout};
+use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use rustc_target::abi::{HasDataLayout, TargetDataLayout};
+
+pub fn test_layout(tcx: TyCtxt<'_>) {
+ if tcx.features().rustc_attrs {
+ // if the `rustc_attrs` feature is not enabled, don't bother testing layout
+ for id in tcx.hir().items() {
+ if matches!(
+ tcx.def_kind(id.def_id),
+ DefKind::TyAlias | DefKind::Enum | DefKind::Struct | DefKind::Union
+ ) {
+ for attr in tcx.get_attrs(id.def_id.to_def_id(), sym::rustc_layout) {
+ dump_layout_of(tcx, id.def_id, attr);
+ }
+ }
+ }
+ }
+}
+
+fn dump_layout_of<'tcx>(tcx: TyCtxt<'tcx>, item_def_id: LocalDefId, attr: &Attribute) {
+ let tcx = tcx;
+ let param_env = tcx.param_env(item_def_id);
+ let ty = tcx.type_of(item_def_id);
+ match tcx.layout_of(param_env.and(ty)) {
+ Ok(ty_layout) => {
+ // Check out the `#[rustc_layout(..)]` attribute to tell what to dump.
+ // The `..` are the names of fields to dump.
+ let meta_items = attr.meta_item_list().unwrap_or_default();
+ for meta_item in meta_items {
+ match meta_item.name_or_empty() {
+ sym::abi => {
+ tcx.sess.span_err(
+ tcx.def_span(item_def_id.to_def_id()),
+ &format!("abi: {:?}", ty_layout.abi),
+ );
+ }
+
+ sym::align => {
+ tcx.sess.span_err(
+ tcx.def_span(item_def_id.to_def_id()),
+ &format!("align: {:?}", ty_layout.align),
+ );
+ }
+
+ sym::size => {
+ tcx.sess.span_err(
+ tcx.def_span(item_def_id.to_def_id()),
+ &format!("size: {:?}", ty_layout.size),
+ );
+ }
+
+ sym::homogeneous_aggregate => {
+ tcx.sess.span_err(
+ tcx.def_span(item_def_id.to_def_id()),
+ &format!(
+ "homogeneous_aggregate: {:?}",
+ ty_layout.homogeneous_aggregate(&UnwrapLayoutCx { tcx, param_env }),
+ ),
+ );
+ }
+
+ sym::debug => {
+ let normalized_ty = tcx.normalize_erasing_regions(
+ param_env.with_reveal_all_normalized(tcx),
+ ty,
+ );
+ tcx.sess.span_err(
+ tcx.def_span(item_def_id.to_def_id()),
+ &format!("layout_of({:?}) = {:#?}", normalized_ty, *ty_layout),
+ );
+ }
+
+ name => {
+ tcx.sess.span_err(
+ meta_item.span(),
+ &format!("unrecognized field name `{}`", name),
+ );
+ }
+ }
+ }
+ }
+
+ Err(layout_error) => {
+ tcx.sess.span_err(
+ tcx.def_span(item_def_id.to_def_id()),
+ &format!("layout error: {:?}", layout_error),
+ );
+ }
+ }
+}
+
+struct UnwrapLayoutCx<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ param_env: ParamEnv<'tcx>,
+}
+
+impl<'tcx> LayoutOfHelpers<'tcx> for UnwrapLayoutCx<'tcx> {
+ type LayoutOfResult = TyAndLayout<'tcx>;
+
+ fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! {
+ span_bug!(
+ span,
+ "`#[rustc_layout(..)]` test resulted in `layout_of({}) = Err({})`",
+ ty,
+ err
+ );
+ }
+}
+
+impl<'tcx> HasTyCtxt<'tcx> for UnwrapLayoutCx<'tcx> {
+ fn tcx(&self) -> TyCtxt<'tcx> {
+ self.tcx
+ }
+}
+
+impl<'tcx> HasParamEnv<'tcx> for UnwrapLayoutCx<'tcx> {
+ fn param_env(&self) -> ParamEnv<'tcx> {
+ self.param_env
+ }
+}
+
+impl<'tcx> HasDataLayout for UnwrapLayoutCx<'tcx> {
+ fn data_layout(&self) -> &TargetDataLayout {
+ self.tcx.data_layout()
+ }
+}
diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs
new file mode 100644
index 000000000..7b2f83958
--- /dev/null
+++ b/compiler/rustc_passes/src/lib.rs
@@ -0,0 +1,59 @@
+//! Various checks
+//!
+//! # Note
+//!
+//! This API is completely unstable and subject to change.
+
+#![allow(rustc::potential_query_instability)]
+#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(iter_intersperse)]
+#![feature(let_chains)]
+#![feature(let_else)]
+#![feature(map_try_insert)]
+#![feature(min_specialization)]
+#![feature(try_blocks)]
+#![recursion_limit = "256"]
+
+#[macro_use]
+extern crate rustc_middle;
+#[macro_use]
+extern crate tracing;
+
+use rustc_middle::ty::query::Providers;
+
+mod check_attr;
+mod check_const;
+pub mod dead;
+mod debugger_visualizer;
+mod diagnostic_items;
+pub mod entry;
+mod errors;
+pub mod hir_id_validator;
+pub mod hir_stats;
+mod lang_items;
+pub mod layout_test;
+mod lib_features;
+mod liveness;
+pub mod loops;
+mod naked_functions;
+mod reachable;
+pub mod stability;
+mod upvars;
+mod weak_lang_items;
+
+pub fn provide(providers: &mut Providers) {
+ check_attr::provide(providers);
+ check_const::provide(providers);
+ dead::provide(providers);
+ debugger_visualizer::provide(providers);
+ diagnostic_items::provide(providers);
+ entry::provide(providers);
+ lang_items::provide(providers);
+ lib_features::provide(providers);
+ loops::provide(providers);
+ naked_functions::provide(providers);
+ liveness::provide(providers);
+ reachable::provide(providers);
+ stability::provide(providers);
+ upvars::provide(providers);
+}
diff --git a/compiler/rustc_passes/src/lib_features.rs b/compiler/rustc_passes/src/lib_features.rs
new file mode 100644
index 000000000..e05994f13
--- /dev/null
+++ b/compiler/rustc_passes/src/lib_features.rs
@@ -0,0 +1,138 @@
+//! Detecting lib features (i.e., features that are not lang features).
+//!
+//! These are declared using stability attributes (e.g., `#[stable (..)]` and `#[unstable (..)]`),
+//! but are not declared in one single location (unlike lang features), which means we need to
+//! collect them instead.
+
+use rustc_ast::{Attribute, MetaItemKind};
+use rustc_errors::struct_span_err;
+use rustc_hir::intravisit::Visitor;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::middle::lib_features::LibFeatures;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::symbol::Symbol;
+use rustc_span::{sym, Span};
+
+fn new_lib_features() -> LibFeatures {
+ LibFeatures { stable: Default::default(), unstable: Default::default() }
+}
+
+pub struct LibFeatureCollector<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ lib_features: LibFeatures,
+}
+
+impl<'tcx> LibFeatureCollector<'tcx> {
+ fn new(tcx: TyCtxt<'tcx>) -> LibFeatureCollector<'tcx> {
+ LibFeatureCollector { tcx, lib_features: new_lib_features() }
+ }
+
+ fn extract(&self, attr: &Attribute) -> Option<(Symbol, Option<Symbol>, Span)> {
+ let stab_attrs =
+ [sym::stable, sym::unstable, sym::rustc_const_stable, sym::rustc_const_unstable];
+
+ // Find a stability attribute: one of #[stable(…)], #[unstable(…)],
+ // #[rustc_const_stable(…)], or #[rustc_const_unstable(…)].
+ if let Some(stab_attr) = stab_attrs.iter().find(|stab_attr| attr.has_name(**stab_attr)) {
+ let meta_kind = attr.meta_kind();
+ if let Some(MetaItemKind::List(ref metas)) = meta_kind {
+ let mut feature = None;
+ let mut since = None;
+ for meta in metas {
+ if let Some(mi) = meta.meta_item() {
+ // Find the `feature = ".."` meta-item.
+ match (mi.name_or_empty(), mi.value_str()) {
+ (sym::feature, val) => feature = val,
+ (sym::since, val) => since = val,
+ _ => {}
+ }
+ }
+ }
+ if let Some(feature) = feature {
+ // This additional check for stability is to make sure we
+ // don't emit additional, irrelevant errors for malformed
+ // attributes.
+ let is_unstable =
+ matches!(*stab_attr, sym::unstable | sym::rustc_const_unstable);
+ if since.is_some() || is_unstable {
+ return Some((feature, since, attr.span));
+ }
+ }
+ // We need to iterate over the other attributes, because
+ // `rustc_const_unstable` is not mutually exclusive with
+ // the other stability attributes, so we can't just `break`
+ // here.
+ }
+ }
+
+ None
+ }
+
+ fn collect_feature(&mut self, feature: Symbol, since: Option<Symbol>, span: Span) {
+ let already_in_stable = self.lib_features.stable.contains_key(&feature);
+ let already_in_unstable = self.lib_features.unstable.contains_key(&feature);
+
+ match (since, already_in_stable, already_in_unstable) {
+ (Some(since), _, false) => {
+ if let Some((prev_since, _)) = self.lib_features.stable.get(&feature) {
+ if *prev_since != since {
+ self.span_feature_error(
+ span,
+ &format!(
+ "feature `{}` is declared stable since {}, \
+ but was previously declared stable since {}",
+ feature, since, prev_since,
+ ),
+ );
+ return;
+ }
+ }
+
+ self.lib_features.stable.insert(feature, (since, span));
+ }
+ (None, false, _) => {
+ self.lib_features.unstable.insert(feature, span);
+ }
+ (Some(_), _, true) | (None, true, _) => {
+ self.span_feature_error(
+ span,
+ &format!(
+ "feature `{}` is declared {}, but was previously declared {}",
+ feature,
+ if since.is_some() { "stable" } else { "unstable" },
+ if since.is_none() { "stable" } else { "unstable" },
+ ),
+ );
+ }
+ }
+ }
+
+ fn span_feature_error(&self, span: Span, msg: &str) {
+ struct_span_err!(self.tcx.sess, span, E0711, "{}", &msg).emit();
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for LibFeatureCollector<'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_attribute(&mut self, attr: &'tcx Attribute) {
+ if let Some((feature, stable, span)) = self.extract(attr) {
+ self.collect_feature(feature, stable, span);
+ }
+ }
+}
+
+fn lib_features(tcx: TyCtxt<'_>, (): ()) -> LibFeatures {
+ let mut collector = LibFeatureCollector::new(tcx);
+ tcx.hir().walk_attributes(&mut collector);
+ collector.lib_features
+}
+
+pub fn provide(providers: &mut Providers) {
+ providers.lib_features = lib_features;
+}
diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
new file mode 100644
index 000000000..461dd52b9
--- /dev/null
+++ b/compiler/rustc_passes/src/liveness.rs
@@ -0,0 +1,1687 @@
+//! A classic liveness analysis based on dataflow over the AST. Computes,
+//! for each local variable in a function, whether that variable is live
+//! at a given point. Program execution points are identified by their
+//! IDs.
+//!
+//! # Basic idea
+//!
+//! The basic model is that each local variable is assigned an index. We
+//! represent sets of local variables using a vector indexed by this
+//! index. The value in the vector is either 0, indicating the variable
+//! is dead, or the ID of an expression that uses the variable.
+//!
+//! We conceptually walk over the AST in reverse execution order. If we
+//! find a use of a variable, we add it to the set of live variables. If
+//! we find an assignment to a variable, we remove it from the set of live
+//! variables. When we have to merge two flows, we take the union of
+//! those two flows -- if the variable is live on both paths, we simply
+//! pick one ID. In the event of loops, we continue doing this until a
+//! fixed point is reached.
+//!
+//! ## Checking initialization
+//!
+//! At the function entry point, all variables must be dead. If this is
+//! not the case, we can report an error using the ID found in the set of
+//! live variables, which identifies a use of the variable which is not
+//! dominated by an assignment.
+//!
+//! ## Checking moves
+//!
+//! After each explicit move, the variable must be dead.
+//!
+//! ## Computing last uses
+//!
+//! Any use of the variable where the variable is dead afterwards is a
+//! last use.
+//!
+//! # Implementation details
+//!
+//! The actual implementation contains two (nested) walks over the AST.
+//! The outer walk has the job of building up the ir_maps instance for the
+//! enclosing function. On the way down the tree, it identifies those AST
+//! nodes and variable IDs that will be needed for the liveness analysis
+//! and assigns them contiguous IDs. The liveness ID for an AST node is
+//! called a `live_node` (it's a newtype'd `u32`) and the ID for a variable
+//! is called a `variable` (another newtype'd `u32`).
+//!
+//! On the way back up the tree, as we are about to exit from a function
+//! declaration we allocate a `liveness` instance. Now that we know
+//! precisely how many nodes and variables we need, we can allocate all
+//! the various arrays that we will need to precisely the right size. We then
+//! perform the actual propagation on the `liveness` instance.
+//!
+//! This propagation is encoded in the various `propagate_through_*()`
+//! methods. It effectively does a reverse walk of the AST; whenever we
+//! reach a loop node, we iterate until a fixed point is reached.
+//!
+//! ## The `RWU` struct
+//!
+//! At each live node `N`, we track three pieces of information for each
+//! variable `V` (these are encapsulated in the `RWU` struct):
+//!
+//! - `reader`: the `LiveNode` ID of some node which will read the value
+//! that `V` holds on entry to `N`. Formally: a node `M` such
+//! that there exists a path `P` from `N` to `M` where `P` does not
+//! write `V`. If the `reader` is `None`, then the current
+//! value will never be read (the variable is dead, essentially).
+//!
+//! - `writer`: the `LiveNode` ID of some node which will write the
+//! variable `V` and which is reachable from `N`. Formally: a node `M`
+//! such that there exists a path `P` from `N` to `M` and `M` writes
+//! `V`. If the `writer` is `None`, then there is no writer
+//! of `V` that follows `N`.
+//!
+//! - `used`: a boolean value indicating whether `V` is *used*. We
+//! distinguish a *read* from a *use* in that a *use* is some read that
+//! is not just used to generate a new value. For example, `x += 1` is
+//! a read but not a use. This is used to generate better warnings.
+//!
+//! ## Special nodes and variables
+//!
+//! We generate various special nodes for various, well, special purposes.
+//! These are described in the `Liveness` struct.
+
+use self::LiveNodeKind::*;
+use self::VarKind::*;
+
+use rustc_ast::InlineAsmOptions;
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::*;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet};
+use rustc_index::vec::IndexVec;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{self, DefIdTree, RootVariableMinCaptureList, Ty, TyCtxt};
+use rustc_session::lint;
+use rustc_span::symbol::{kw, sym, Symbol};
+use rustc_span::Span;
+
+use std::collections::VecDeque;
+use std::io;
+use std::io::prelude::*;
+use std::rc::Rc;
+
+mod rwu_table;
+
+rustc_index::newtype_index! {
+ pub struct Variable {
+ DEBUG_FORMAT = "v({})",
+ }
+}
+
+rustc_index::newtype_index! {
+ pub struct LiveNode {
+ DEBUG_FORMAT = "ln({})",
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+enum LiveNodeKind {
+ UpvarNode(Span),
+ ExprNode(Span, HirId),
+ VarDefNode(Span, HirId),
+ ClosureNode,
+ ExitNode,
+}
+
+fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String {
+ let sm = tcx.sess.source_map();
+ match lnk {
+ UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_diagnostic_string(s)),
+ ExprNode(s, _) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)),
+ VarDefNode(s, _) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)),
+ ClosureNode => "Closure node".to_owned(),
+ ExitNode => "Exit node".to_owned(),
+ }
+}
+
+fn check_mod_liveness(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
+ tcx.hir().visit_item_likes_in_module(module_def_id, &mut IrMaps::new(tcx));
+}
+
+pub fn provide(providers: &mut Providers) {
+ *providers = Providers { check_mod_liveness, ..*providers };
+}
+
+// ______________________________________________________________________
+// Creating ir_maps
+//
+// This is the first pass and the one that drives the main
+// computation. It walks up and down the IR once. On the way down,
+// we count for each function the number of variables as well as
+// liveness nodes. A liveness node is basically an expression or
+// capture clause that does something of interest: either it has
+// interesting control flow or it uses/defines a local variable.
+//
+// On the way back up, at each function node we create liveness sets
+// (we now know precisely how big to make our various vectors and so
+// forth) and then do the data-flow propagation to compute the set
+// of live variables at each program point.
+//
+// Finally, we run back over the IR one last time and, using the
+// computed liveness, check various safety conditions. For example,
+// there must be no live nodes at the definition site for a variable
+// unless it has an initializer. Similarly, each non-mutable local
+// variable must not be assigned if there is some successor
+// assignment. And so forth.
+
+struct CaptureInfo {
+ ln: LiveNode,
+ var_hid: HirId,
+}
+
+#[derive(Copy, Clone, Debug)]
+struct LocalInfo {
+ id: HirId,
+ name: Symbol,
+ is_shorthand: bool,
+}
+
+#[derive(Copy, Clone, Debug)]
+enum VarKind {
+ Param(HirId, Symbol),
+ Local(LocalInfo),
+ Upvar(HirId, Symbol),
+}
+
+struct IrMaps<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ live_node_map: HirIdMap<LiveNode>,
+ variable_map: HirIdMap<Variable>,
+ capture_info_map: HirIdMap<Rc<Vec<CaptureInfo>>>,
+ var_kinds: IndexVec<Variable, VarKind>,
+ lnks: IndexVec<LiveNode, LiveNodeKind>,
+}
+
+impl<'tcx> IrMaps<'tcx> {
+ fn new(tcx: TyCtxt<'tcx>) -> IrMaps<'tcx> {
+ IrMaps {
+ tcx,
+ live_node_map: HirIdMap::default(),
+ variable_map: HirIdMap::default(),
+ capture_info_map: Default::default(),
+ var_kinds: IndexVec::new(),
+ lnks: IndexVec::new(),
+ }
+ }
+
+ fn add_live_node(&mut self, lnk: LiveNodeKind) -> LiveNode {
+ let ln = self.lnks.push(lnk);
+
+ debug!("{:?} is of kind {}", ln, live_node_kind_to_string(lnk, self.tcx));
+
+ ln
+ }
+
+ fn add_live_node_for_node(&mut self, hir_id: HirId, lnk: LiveNodeKind) {
+ let ln = self.add_live_node(lnk);
+ self.live_node_map.insert(hir_id, ln);
+
+ debug!("{:?} is node {:?}", ln, hir_id);
+ }
+
+ fn add_variable(&mut self, vk: VarKind) -> Variable {
+ let v = self.var_kinds.push(vk);
+
+ match vk {
+ Local(LocalInfo { id: node_id, .. }) | Param(node_id, _) | Upvar(node_id, _) => {
+ self.variable_map.insert(node_id, v);
+ }
+ }
+
+ debug!("{:?} is {:?}", v, vk);
+
+ v
+ }
+
+ fn variable(&self, hir_id: HirId, span: Span) -> Variable {
+ match self.variable_map.get(&hir_id) {
+ Some(&var) => var,
+ None => {
+ span_bug!(span, "no variable registered for id {:?}", hir_id);
+ }
+ }
+ }
+
+ fn variable_name(&self, var: Variable) -> Symbol {
+ match self.var_kinds[var] {
+ Local(LocalInfo { name, .. }) | Param(_, name) | Upvar(_, name) => name,
+ }
+ }
+
+ fn variable_is_shorthand(&self, var: Variable) -> bool {
+ match self.var_kinds[var] {
+ Local(LocalInfo { is_shorthand, .. }) => is_shorthand,
+ Param(..) | Upvar(..) => false,
+ }
+ }
+
+ fn set_captures(&mut self, hir_id: HirId, cs: Vec<CaptureInfo>) {
+ self.capture_info_map.insert(hir_id, Rc::new(cs));
+ }
+
+ fn collect_shorthand_field_ids(&self, pat: &hir::Pat<'tcx>) -> HirIdSet {
+ // For struct patterns, take note of which fields used shorthand
+ // (`x` rather than `x: x`).
+ let mut shorthand_field_ids = HirIdSet::default();
+ let mut pats = VecDeque::new();
+ pats.push_back(pat);
+
+ while let Some(pat) = pats.pop_front() {
+ use rustc_hir::PatKind::*;
+ match &pat.kind {
+ Binding(.., inner_pat) => {
+ pats.extend(inner_pat.iter());
+ }
+ Struct(_, fields, _) => {
+ let (short, not_short): (Vec<_>, _) =
+ fields.iter().partition(|f| f.is_shorthand);
+ shorthand_field_ids.extend(short.iter().map(|f| f.pat.hir_id));
+ pats.extend(not_short.iter().map(|f| f.pat));
+ }
+ Ref(inner_pat, _) | Box(inner_pat) => {
+ pats.push_back(inner_pat);
+ }
+ TupleStruct(_, inner_pats, _) | Tuple(inner_pats, _) | Or(inner_pats) => {
+ pats.extend(inner_pats.iter());
+ }
+ Slice(pre_pats, inner_pat, post_pats) => {
+ pats.extend(pre_pats.iter());
+ pats.extend(inner_pat.iter());
+ pats.extend(post_pats.iter());
+ }
+ _ => {}
+ }
+ }
+
+ shorthand_field_ids
+ }
+
+ fn add_from_pat(&mut self, pat: &hir::Pat<'tcx>) {
+ let shorthand_field_ids = self.collect_shorthand_field_ids(pat);
+
+ pat.each_binding(|_, hir_id, _, ident| {
+ self.add_live_node_for_node(hir_id, VarDefNode(ident.span, hir_id));
+ self.add_variable(Local(LocalInfo {
+ id: hir_id,
+ name: ident.name,
+ is_shorthand: shorthand_field_ids.contains(&hir_id),
+ }));
+ });
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
+ debug!("visit_body {:?}", body.id());
+
+ // swap in a new set of IR maps for this body
+ let mut maps = IrMaps::new(self.tcx);
+ let hir_id = maps.tcx.hir().body_owner(body.id());
+ let local_def_id = maps.tcx.hir().local_def_id(hir_id);
+ let def_id = local_def_id.to_def_id();
+
+ // Don't run unused pass for #[derive()]
+ let parent = self.tcx.local_parent(local_def_id);
+ if let DefKind::Impl = self.tcx.def_kind(parent)
+ && self.tcx.has_attr(parent.to_def_id(), sym::automatically_derived)
+ {
+ return;
+ }
+
+ // Don't run unused pass for #[naked]
+ if self.tcx.has_attr(def_id, sym::naked) {
+ return;
+ }
+
+ if let Some(upvars) = maps.tcx.upvars_mentioned(def_id) {
+ for &var_hir_id in upvars.keys() {
+ let var_name = maps.tcx.hir().name(var_hir_id);
+ maps.add_variable(Upvar(var_hir_id, var_name));
+ }
+ }
+
+ // gather up the various local variables, significant expressions,
+ // and so forth:
+ intravisit::walk_body(&mut maps, body);
+
+ // compute liveness
+ let mut lsets = Liveness::new(&mut maps, local_def_id);
+ let entry_ln = lsets.compute(&body, hir_id);
+ lsets.log_liveness(entry_ln, body.id().hir_id);
+
+ // check for various error conditions
+ lsets.visit_body(body);
+ lsets.warn_about_unused_upvars(entry_ln);
+ lsets.warn_about_unused_args(body, entry_ln);
+ }
+
+ fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
+ self.add_from_pat(&local.pat);
+ if local.els.is_some() {
+ self.add_live_node_for_node(local.hir_id, ExprNode(local.span, local.hir_id));
+ }
+ intravisit::walk_local(self, local);
+ }
+
+ fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
+ self.add_from_pat(&arm.pat);
+ if let Some(hir::Guard::IfLet(ref let_expr)) = arm.guard {
+ self.add_from_pat(let_expr.pat);
+ }
+ intravisit::walk_arm(self, arm);
+ }
+
+ fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+ let shorthand_field_ids = self.collect_shorthand_field_ids(param.pat);
+ param.pat.each_binding(|_bm, hir_id, _x, ident| {
+ let var = match param.pat.kind {
+ rustc_hir::PatKind::Struct(..) => Local(LocalInfo {
+ id: hir_id,
+ name: ident.name,
+ is_shorthand: shorthand_field_ids.contains(&hir_id),
+ }),
+ _ => Param(hir_id, ident.name),
+ };
+ self.add_variable(var);
+ });
+ intravisit::walk_param(self, param);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ match expr.kind {
+ // live nodes required for uses or definitions of variables:
+ hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => {
+ debug!("expr {}: path that leads to {:?}", expr.hir_id, path.res);
+ if let Res::Local(_var_hir_id) = path.res {
+ self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
+ }
+ intravisit::walk_expr(self, expr);
+ }
+ hir::ExprKind::Closure { .. } => {
+ // Interesting control flow (for loops can contain labeled
+ // breaks or continues)
+ self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
+
+ // Make a live_node for each mentioned variable, with the span
+ // being the location that the variable is used. This results
+ // in better error messages than just pointing at the closure
+ // construction site.
+ let mut call_caps = Vec::new();
+ let closure_def_id = self.tcx.hir().local_def_id(expr.hir_id);
+ if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
+ call_caps.extend(upvars.keys().map(|var_id| {
+ let upvar = upvars[var_id];
+ let upvar_ln = self.add_live_node(UpvarNode(upvar.span));
+ CaptureInfo { ln: upvar_ln, var_hid: *var_id }
+ }));
+ }
+ self.set_captures(expr.hir_id, call_caps);
+ intravisit::walk_expr(self, expr);
+ }
+
+ hir::ExprKind::Let(let_expr) => {
+ self.add_from_pat(let_expr.pat);
+ intravisit::walk_expr(self, expr);
+ }
+
+ // live nodes required for interesting control flow:
+ hir::ExprKind::If(..)
+ | hir::ExprKind::Match(..)
+ | hir::ExprKind::Loop(..)
+ | hir::ExprKind::Yield(..) => {
+ self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
+ intravisit::walk_expr(self, expr);
+ }
+ hir::ExprKind::Binary(op, ..) if op.node.is_lazy() => {
+ self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id));
+ intravisit::walk_expr(self, expr);
+ }
+
+ // otherwise, live nodes are not required:
+ hir::ExprKind::Index(..)
+ | hir::ExprKind::Field(..)
+ | hir::ExprKind::Array(..)
+ | hir::ExprKind::Call(..)
+ | hir::ExprKind::MethodCall(..)
+ | hir::ExprKind::Tup(..)
+ | hir::ExprKind::Binary(..)
+ | hir::ExprKind::AddrOf(..)
+ | hir::ExprKind::Cast(..)
+ | hir::ExprKind::DropTemps(..)
+ | hir::ExprKind::Unary(..)
+ | hir::ExprKind::Break(..)
+ | hir::ExprKind::Continue(_)
+ | hir::ExprKind::Lit(_)
+ | hir::ExprKind::ConstBlock(..)
+ | hir::ExprKind::Ret(..)
+ | hir::ExprKind::Block(..)
+ | hir::ExprKind::Assign(..)
+ | hir::ExprKind::AssignOp(..)
+ | hir::ExprKind::Struct(..)
+ | hir::ExprKind::Repeat(..)
+ | hir::ExprKind::InlineAsm(..)
+ | hir::ExprKind::Box(..)
+ | hir::ExprKind::Type(..)
+ | hir::ExprKind::Err
+ | hir::ExprKind::Path(hir::QPath::TypeRelative(..))
+ | hir::ExprKind::Path(hir::QPath::LangItem(..)) => {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+ }
+}
+
+// ______________________________________________________________________
+// Computing liveness sets
+//
+// Actually we compute just a bit more than just liveness, but we use
+// the same basic propagation framework in all cases.
+
+const ACC_READ: u32 = 1;
+const ACC_WRITE: u32 = 2;
+const ACC_USE: u32 = 4;
+
+struct Liveness<'a, 'tcx> {
+ ir: &'a mut IrMaps<'tcx>,
+ typeck_results: &'a ty::TypeckResults<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ closure_min_captures: Option<&'tcx RootVariableMinCaptureList<'tcx>>,
+ successors: IndexVec<LiveNode, Option<LiveNode>>,
+ rwu_table: rwu_table::RWUTable,
+
+ /// A live node representing a point of execution before closure entry &
+ /// after closure exit. Used to calculate liveness of captured variables
+ /// through calls to the same closure. Used for Fn & FnMut closures only.
+ closure_ln: LiveNode,
+ /// A live node representing every 'exit' from the function, whether it be
+ /// by explicit return, panic, or other means.
+ exit_ln: LiveNode,
+
+ // mappings from loop node ID to LiveNode
+ // ("break" label should map to loop node ID,
+ // it probably doesn't now)
+ break_ln: HirIdMap<LiveNode>,
+ cont_ln: HirIdMap<LiveNode>,
+}
+
+impl<'a, 'tcx> Liveness<'a, 'tcx> {
+ fn new(ir: &'a mut IrMaps<'tcx>, body_owner: LocalDefId) -> Liveness<'a, 'tcx> {
+ let typeck_results = ir.tcx.typeck(body_owner);
+ let param_env = ir.tcx.param_env(body_owner);
+ let closure_min_captures = typeck_results.closure_min_captures.get(&body_owner);
+ let closure_ln = ir.add_live_node(ClosureNode);
+ let exit_ln = ir.add_live_node(ExitNode);
+
+ let num_live_nodes = ir.lnks.len();
+ let num_vars = ir.var_kinds.len();
+
+ Liveness {
+ ir,
+ typeck_results,
+ param_env,
+ closure_min_captures,
+ successors: IndexVec::from_elem_n(None, num_live_nodes),
+ rwu_table: rwu_table::RWUTable::new(num_live_nodes, num_vars),
+ closure_ln,
+ exit_ln,
+ break_ln: Default::default(),
+ cont_ln: Default::default(),
+ }
+ }
+
+ fn live_node(&self, hir_id: HirId, span: Span) -> LiveNode {
+ match self.ir.live_node_map.get(&hir_id) {
+ Some(&ln) => ln,
+ None => {
+ // This must be a mismatch between the ir_map construction
+ // above and the propagation code below; the two sets of
+ // code have to agree about which AST nodes are worth
+ // creating liveness nodes for.
+ span_bug!(span, "no live node registered for node {:?}", hir_id);
+ }
+ }
+ }
+
+ fn variable(&self, hir_id: HirId, span: Span) -> Variable {
+ self.ir.variable(hir_id, span)
+ }
+
+ fn define_bindings_in_pat(&mut self, pat: &hir::Pat<'_>, mut succ: LiveNode) -> LiveNode {
+ // In an or-pattern, only consider the first pattern; any later patterns
+ // must have the same bindings, and we also consider the first pattern
+ // to be the "authoritative" set of ids.
+ pat.each_binding_or_first(&mut |_, hir_id, pat_sp, ident| {
+ let ln = self.live_node(hir_id, pat_sp);
+ let var = self.variable(hir_id, ident.span);
+ self.init_from_succ(ln, succ);
+ self.define(ln, var);
+ succ = ln;
+ });
+ succ
+ }
+
+ fn live_on_entry(&self, ln: LiveNode, var: Variable) -> bool {
+ self.rwu_table.get_reader(ln, var)
+ }
+
+ // Is this variable live on entry to any of its successor nodes?
+ fn live_on_exit(&self, ln: LiveNode, var: Variable) -> bool {
+ let successor = self.successors[ln].unwrap();
+ self.live_on_entry(successor, var)
+ }
+
+ fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool {
+ self.rwu_table.get_used(ln, var)
+ }
+
+ fn assigned_on_entry(&self, ln: LiveNode, var: Variable) -> bool {
+ self.rwu_table.get_writer(ln, var)
+ }
+
+ fn assigned_on_exit(&self, ln: LiveNode, var: Variable) -> bool {
+ let successor = self.successors[ln].unwrap();
+ self.assigned_on_entry(successor, var)
+ }
+
+ fn write_vars<F>(&self, wr: &mut dyn Write, mut test: F) -> io::Result<()>
+ where
+ F: FnMut(Variable) -> bool,
+ {
+ for var_idx in 0..self.ir.var_kinds.len() {
+ let var = Variable::from(var_idx);
+ if test(var) {
+ write!(wr, " {:?}", var)?;
+ }
+ }
+ Ok(())
+ }
+
+ #[allow(unused_must_use)]
+ fn ln_str(&self, ln: LiveNode) -> String {
+ let mut wr = Vec::new();
+ {
+ let wr = &mut wr as &mut dyn Write;
+ write!(wr, "[{:?} of kind {:?} reads", ln, self.ir.lnks[ln]);
+ self.write_vars(wr, |var| self.rwu_table.get_reader(ln, var));
+ write!(wr, " writes");
+ self.write_vars(wr, |var| self.rwu_table.get_writer(ln, var));
+ write!(wr, " uses");
+ self.write_vars(wr, |var| self.rwu_table.get_used(ln, var));
+
+ write!(wr, " precedes {:?}]", self.successors[ln]);
+ }
+ String::from_utf8(wr).unwrap()
+ }
+
+ fn log_liveness(&self, entry_ln: LiveNode, hir_id: hir::HirId) {
+ // hack to skip the loop unless debug! is enabled:
+ debug!(
+ "^^ liveness computation results for body {} (entry={:?})",
+ {
+ for ln_idx in 0..self.ir.lnks.len() {
+ debug!("{:?}", self.ln_str(LiveNode::from(ln_idx)));
+ }
+ hir_id
+ },
+ entry_ln
+ );
+ }
+
+ fn init_empty(&mut self, ln: LiveNode, succ_ln: LiveNode) {
+ self.successors[ln] = Some(succ_ln);
+
+ // It is not necessary to initialize the RWUs here because they are all
+ // empty when created, and the sets only grow during iterations.
+ }
+
+ fn init_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) {
+ // more efficient version of init_empty() / merge_from_succ()
+ self.successors[ln] = Some(succ_ln);
+ self.rwu_table.copy(ln, succ_ln);
+ debug!("init_from_succ(ln={}, succ={})", self.ln_str(ln), self.ln_str(succ_ln));
+ }
+
+ fn merge_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) -> bool {
+ if ln == succ_ln {
+ return false;
+ }
+
+ let changed = self.rwu_table.union(ln, succ_ln);
+ debug!("merge_from_succ(ln={:?}, succ={}, changed={})", ln, self.ln_str(succ_ln), changed);
+ changed
+ }
+
+ // Indicates that a local variable was *defined*; we know that no
+ // uses of the variable can precede the definition (resolve checks
+ // this) so we just clear out all the data.
+ fn define(&mut self, writer: LiveNode, var: Variable) {
+ let used = self.rwu_table.get_used(writer, var);
+ self.rwu_table.set(writer, var, rwu_table::RWU { reader: false, writer: false, used });
+ debug!("{:?} defines {:?}: {}", writer, var, self.ln_str(writer));
+ }
+
+ // Either read, write, or both depending on the acc bitset
+ fn acc(&mut self, ln: LiveNode, var: Variable, acc: u32) {
+ debug!("{:?} accesses[{:x}] {:?}: {}", ln, acc, var, self.ln_str(ln));
+
+ let mut rwu = self.rwu_table.get(ln, var);
+
+ if (acc & ACC_WRITE) != 0 {
+ rwu.reader = false;
+ rwu.writer = true;
+ }
+
+ // Important: if we both read/write, must do read second
+ // or else the write will override.
+ if (acc & ACC_READ) != 0 {
+ rwu.reader = true;
+ }
+
+ if (acc & ACC_USE) != 0 {
+ rwu.used = true;
+ }
+
+ self.rwu_table.set(ln, var, rwu);
+ }
+
+ fn compute(&mut self, body: &hir::Body<'_>, hir_id: HirId) -> LiveNode {
+ debug!("compute: for body {:?}", body.id().hir_id);
+
+ // # Liveness of captured variables
+ //
+ // When computing the liveness for captured variables we take into
+ // account how variable is captured (ByRef vs ByValue) and what is the
+ // closure kind (Generator / FnOnce vs Fn / FnMut).
+ //
+ // Variables captured by reference are assumed to be used on the exit
+ // from the closure.
+ //
+ // In FnOnce closures, variables captured by value are known to be dead
+ // on exit since it is impossible to call the closure again.
+ //
+ // In Fn / FnMut closures, variables captured by value are live on exit
+ // if they are live on the entry to the closure, since only the closure
+ // itself can access them on subsequent calls.
+
+ if let Some(closure_min_captures) = self.closure_min_captures {
+ // Mark upvars captured by reference as used after closure exits.
+ for (&var_hir_id, min_capture_list) in closure_min_captures {
+ for captured_place in min_capture_list {
+ match captured_place.info.capture_kind {
+ ty::UpvarCapture::ByRef(_) => {
+ let var = self.variable(
+ var_hir_id,
+ captured_place.get_capture_kind_span(self.ir.tcx),
+ );
+ self.acc(self.exit_ln, var, ACC_READ | ACC_USE);
+ }
+ ty::UpvarCapture::ByValue => {}
+ }
+ }
+ }
+ }
+
+ let succ = self.propagate_through_expr(&body.value, self.exit_ln);
+
+ if self.closure_min_captures.is_none() {
+ // Either not a closure, or closure without any captured variables.
+ // No need to determine liveness of captured variables, since there
+ // are none.
+ return succ;
+ }
+
+ let ty = self.typeck_results.node_type(hir_id);
+ match ty.kind() {
+ ty::Closure(_def_id, substs) => match substs.as_closure().kind() {
+ ty::ClosureKind::Fn => {}
+ ty::ClosureKind::FnMut => {}
+ ty::ClosureKind::FnOnce => return succ,
+ },
+ ty::Generator(..) => return succ,
+ _ => {
+ span_bug!(
+ body.value.span,
+ "{} has upvars so it should have a closure type: {:?}",
+ hir_id,
+ ty
+ );
+ }
+ };
+
+ // Propagate through calls to the closure.
+ loop {
+ self.init_from_succ(self.closure_ln, succ);
+ for param in body.params {
+ param.pat.each_binding(|_bm, hir_id, _x, ident| {
+ let var = self.variable(hir_id, ident.span);
+ self.define(self.closure_ln, var);
+ })
+ }
+
+ if !self.merge_from_succ(self.exit_ln, self.closure_ln) {
+ break;
+ }
+ assert_eq!(succ, self.propagate_through_expr(&body.value, self.exit_ln));
+ }
+
+ succ
+ }
+
+ fn propagate_through_block(&mut self, blk: &hir::Block<'_>, succ: LiveNode) -> LiveNode {
+ if blk.targeted_by_break {
+ self.break_ln.insert(blk.hir_id, succ);
+ }
+ let succ = self.propagate_through_opt_expr(blk.expr, succ);
+ blk.stmts.iter().rev().fold(succ, |succ, stmt| self.propagate_through_stmt(stmt, succ))
+ }
+
+ fn propagate_through_stmt(&mut self, stmt: &hir::Stmt<'_>, succ: LiveNode) -> LiveNode {
+ match stmt.kind {
+ hir::StmtKind::Local(ref local) => {
+ // Note: we mark the variable as defined regardless of whether
+ // there is an initializer. Initially I had thought to only mark
+ // the live variable as defined if it was initialized, and then we
+ // could check for uninit variables just by scanning what is live
+ // at the start of the function. But that doesn't work so well for
+ // immutable variables defined in a loop:
+ // loop { let x; x = 5; }
+ // because the "assignment" loops back around and generates an error.
+ //
+ // So now we just check that variables defined w/o an
+ // initializer are not live at the point of their
+ // initialization, which is mildly more complex than checking
+ // once at the func header but otherwise equivalent.
+
+ if let Some(els) = local.els {
+ // Eventually, `let pat: ty = init else { els };` is mostly equivalent to
+ // `let (bindings, ...) = match init { pat => (bindings, ...), _ => els };`
+ // except that extended lifetime applies at the `init` location.
+ //
+ // (e)
+ // |
+ // v
+ // (expr)
+ // / \
+ // | |
+ // v v
+ // bindings els
+ // |
+ // v
+ // ( succ )
+ //
+ if let Some(init) = local.init {
+ let else_ln = self.propagate_through_block(els, succ);
+ let ln = self.live_node(local.hir_id, local.span);
+ self.init_from_succ(ln, succ);
+ self.merge_from_succ(ln, else_ln);
+ let succ = self.propagate_through_expr(init, ln);
+ self.define_bindings_in_pat(&local.pat, succ)
+ } else {
+ span_bug!(
+ stmt.span,
+ "variable is uninitialized but an unexpected else branch is found"
+ )
+ }
+ } else {
+ let succ = self.propagate_through_opt_expr(local.init, succ);
+ self.define_bindings_in_pat(&local.pat, succ)
+ }
+ }
+ hir::StmtKind::Item(..) => succ,
+ hir::StmtKind::Expr(ref expr) | hir::StmtKind::Semi(ref expr) => {
+ self.propagate_through_expr(&expr, succ)
+ }
+ }
+ }
+
+ fn propagate_through_exprs(&mut self, exprs: &[Expr<'_>], succ: LiveNode) -> LiveNode {
+ exprs.iter().rev().fold(succ, |succ, expr| self.propagate_through_expr(&expr, succ))
+ }
+
+ fn propagate_through_opt_expr(
+ &mut self,
+ opt_expr: Option<&Expr<'_>>,
+ succ: LiveNode,
+ ) -> LiveNode {
+ opt_expr.map_or(succ, |expr| self.propagate_through_expr(expr, succ))
+ }
+
+ fn propagate_through_expr(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
+ debug!("propagate_through_expr: {:?}", expr);
+
+ match expr.kind {
+ // Interesting cases with control flow or which gen/kill
+ hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => {
+ self.access_path(expr.hir_id, path, succ, ACC_READ | ACC_USE)
+ }
+
+ hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(&e, succ),
+
+ hir::ExprKind::Closure { .. } => {
+ debug!("{:?} is an ExprKind::Closure", expr);
+
+ // the construction of a closure itself is not important,
+ // but we have to consider the closed over variables.
+ let caps = self
+ .ir
+ .capture_info_map
+ .get(&expr.hir_id)
+ .cloned()
+ .unwrap_or_else(|| span_bug!(expr.span, "no registered caps"));
+
+ caps.iter().rev().fold(succ, |succ, cap| {
+ self.init_from_succ(cap.ln, succ);
+ let var = self.variable(cap.var_hid, expr.span);
+ self.acc(cap.ln, var, ACC_READ | ACC_USE);
+ cap.ln
+ })
+ }
+
+ hir::ExprKind::Let(let_expr) => {
+ let succ = self.propagate_through_expr(let_expr.init, succ);
+ self.define_bindings_in_pat(let_expr.pat, succ)
+ }
+
+ // Note that labels have been resolved, so we don't need to look
+ // at the label ident
+ hir::ExprKind::Loop(ref blk, ..) => self.propagate_through_loop(expr, &blk, succ),
+
+ hir::ExprKind::Yield(ref e, ..) => {
+ let yield_ln = self.live_node(expr.hir_id, expr.span);
+ self.init_from_succ(yield_ln, succ);
+ self.merge_from_succ(yield_ln, self.exit_ln);
+ self.propagate_through_expr(e, yield_ln)
+ }
+
+ hir::ExprKind::If(ref cond, ref then, ref else_opt) => {
+ //
+ // (cond)
+ // |
+ // v
+ // (expr)
+ // / \
+ // | |
+ // v v
+ // (then)(els)
+ // | |
+ // v v
+ // ( succ )
+ //
+ let else_ln =
+ self.propagate_through_opt_expr(else_opt.as_ref().map(|e| &**e), succ);
+ let then_ln = self.propagate_through_expr(&then, succ);
+ let ln = self.live_node(expr.hir_id, expr.span);
+ self.init_from_succ(ln, else_ln);
+ self.merge_from_succ(ln, then_ln);
+ self.propagate_through_expr(&cond, ln)
+ }
+
+ hir::ExprKind::Match(ref e, arms, _) => {
+ //
+ // (e)
+ // |
+ // v
+ // (expr)
+ // / | \
+ // | | |
+ // v v v
+ // (..arms..)
+ // | | |
+ // v v v
+ // ( succ )
+ //
+ //
+ let ln = self.live_node(expr.hir_id, expr.span);
+ self.init_empty(ln, succ);
+ for arm in arms {
+ let body_succ = self.propagate_through_expr(&arm.body, succ);
+
+ let guard_succ = arm.guard.as_ref().map_or(body_succ, |g| match g {
+ hir::Guard::If(e) => self.propagate_through_expr(e, body_succ),
+ hir::Guard::IfLet(let_expr) => {
+ let let_bind = self.define_bindings_in_pat(let_expr.pat, body_succ);
+ self.propagate_through_expr(let_expr.init, let_bind)
+ }
+ });
+ let arm_succ = self.define_bindings_in_pat(&arm.pat, guard_succ);
+ self.merge_from_succ(ln, arm_succ);
+ }
+ self.propagate_through_expr(&e, ln)
+ }
+
+ hir::ExprKind::Ret(ref o_e) => {
+ // Ignore succ and subst exit_ln.
+ self.propagate_through_opt_expr(o_e.as_ref().map(|e| &**e), self.exit_ln)
+ }
+
+ hir::ExprKind::Break(label, ref opt_expr) => {
+ // Find which label this break jumps to
+ let target = match label.target_id {
+ Ok(hir_id) => self.break_ln.get(&hir_id),
+ Err(err) => span_bug!(expr.span, "loop scope error: {}", err),
+ }
+ .cloned();
+
+ // Now that we know the label we're going to,
+ // look it up in the break loop nodes table
+
+ match target {
+ Some(b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b),
+ None => span_bug!(expr.span, "`break` to unknown label"),
+ }
+ }
+
+ hir::ExprKind::Continue(label) => {
+ // Find which label this expr continues to
+ let sc = label
+ .target_id
+ .unwrap_or_else(|err| span_bug!(expr.span, "loop scope error: {}", err));
+
+ // Now that we know the label we're going to,
+ // look it up in the continue loop nodes table
+ self.cont_ln
+ .get(&sc)
+ .cloned()
+ .unwrap_or_else(|| span_bug!(expr.span, "continue to unknown label"))
+ }
+
+ hir::ExprKind::Assign(ref l, ref r, _) => {
+ // see comment on places in
+ // propagate_through_place_components()
+ let succ = self.write_place(&l, succ, ACC_WRITE);
+ let succ = self.propagate_through_place_components(&l, succ);
+ self.propagate_through_expr(&r, succ)
+ }
+
+ hir::ExprKind::AssignOp(_, ref l, ref r) => {
+ // an overloaded assign op is like a method call
+ if self.typeck_results.is_method_call(expr) {
+ let succ = self.propagate_through_expr(&l, succ);
+ self.propagate_through_expr(&r, succ)
+ } else {
+ // see comment on places in
+ // propagate_through_place_components()
+ let succ = self.write_place(&l, succ, ACC_WRITE | ACC_READ);
+ let succ = self.propagate_through_expr(&r, succ);
+ self.propagate_through_place_components(&l, succ)
+ }
+ }
+
+ // Uninteresting cases: just propagate in rev exec order
+ hir::ExprKind::Array(ref exprs) => self.propagate_through_exprs(exprs, succ),
+
+ hir::ExprKind::Struct(_, ref fields, ref with_expr) => {
+ let succ = self.propagate_through_opt_expr(with_expr.as_ref().map(|e| &**e), succ);
+ fields
+ .iter()
+ .rev()
+ .fold(succ, |succ, field| self.propagate_through_expr(&field.expr, succ))
+ }
+
+ hir::ExprKind::Call(ref f, ref args) => {
+ let succ = self.check_is_ty_uninhabited(expr, succ);
+ let succ = self.propagate_through_exprs(args, succ);
+ self.propagate_through_expr(&f, succ)
+ }
+
+ hir::ExprKind::MethodCall(.., ref args, _) => {
+ let succ = self.check_is_ty_uninhabited(expr, succ);
+ self.propagate_through_exprs(args, succ)
+ }
+
+ hir::ExprKind::Tup(ref exprs) => self.propagate_through_exprs(exprs, succ),
+
+ hir::ExprKind::Binary(op, ref l, ref r) if op.node.is_lazy() => {
+ let r_succ = self.propagate_through_expr(&r, succ);
+
+ let ln = self.live_node(expr.hir_id, expr.span);
+ self.init_from_succ(ln, succ);
+ self.merge_from_succ(ln, r_succ);
+
+ self.propagate_through_expr(&l, ln)
+ }
+
+ hir::ExprKind::Index(ref l, ref r) | hir::ExprKind::Binary(_, ref l, ref r) => {
+ let r_succ = self.propagate_through_expr(&r, succ);
+ self.propagate_through_expr(&l, r_succ)
+ }
+
+ hir::ExprKind::Box(ref e)
+ | hir::ExprKind::AddrOf(_, _, ref e)
+ | hir::ExprKind::Cast(ref e, _)
+ | hir::ExprKind::Type(ref e, _)
+ | hir::ExprKind::DropTemps(ref e)
+ | hir::ExprKind::Unary(_, ref e)
+ | hir::ExprKind::Repeat(ref e, _) => self.propagate_through_expr(&e, succ),
+
+ hir::ExprKind::InlineAsm(ref asm) => {
+ // Handle non-returning asm
+ let mut succ = if asm.options.contains(InlineAsmOptions::NORETURN) {
+ self.exit_ln
+ } else {
+ succ
+ };
+
+ // Do a first pass for writing outputs only
+ for (op, _op_sp) in asm.operands.iter().rev() {
+ match op {
+ hir::InlineAsmOperand::In { .. }
+ | hir::InlineAsmOperand::Const { .. }
+ | hir::InlineAsmOperand::SymFn { .. }
+ | hir::InlineAsmOperand::SymStatic { .. } => {}
+ hir::InlineAsmOperand::Out { expr, .. } => {
+ if let Some(expr) = expr {
+ succ = self.write_place(expr, succ, ACC_WRITE);
+ }
+ }
+ hir::InlineAsmOperand::InOut { expr, .. } => {
+ succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE | ACC_USE);
+ }
+ hir::InlineAsmOperand::SplitInOut { out_expr, .. } => {
+ if let Some(expr) = out_expr {
+ succ = self.write_place(expr, succ, ACC_WRITE);
+ }
+ }
+ }
+ }
+
+ // Then do a second pass for inputs
+ let mut succ = succ;
+ for (op, _op_sp) in asm.operands.iter().rev() {
+ match op {
+ hir::InlineAsmOperand::In { expr, .. } => {
+ succ = self.propagate_through_expr(expr, succ)
+ }
+ hir::InlineAsmOperand::Out { expr, .. } => {
+ if let Some(expr) = expr {
+ succ = self.propagate_through_place_components(expr, succ);
+ }
+ }
+ hir::InlineAsmOperand::InOut { expr, .. } => {
+ succ = self.propagate_through_place_components(expr, succ);
+ }
+ hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
+ if let Some(expr) = out_expr {
+ succ = self.propagate_through_place_components(expr, succ);
+ }
+ succ = self.propagate_through_expr(in_expr, succ);
+ }
+ hir::InlineAsmOperand::Const { .. }
+ | hir::InlineAsmOperand::SymFn { .. }
+ | hir::InlineAsmOperand::SymStatic { .. } => {}
+ }
+ }
+ succ
+ }
+
+ hir::ExprKind::Lit(..)
+ | hir::ExprKind::ConstBlock(..)
+ | hir::ExprKind::Err
+ | hir::ExprKind::Path(hir::QPath::TypeRelative(..))
+ | hir::ExprKind::Path(hir::QPath::LangItem(..)) => succ,
+
+ // Note that labels have been resolved, so we don't need to look
+ // at the label ident
+ hir::ExprKind::Block(ref blk, _) => self.propagate_through_block(&blk, succ),
+ }
+ }
+
+ fn propagate_through_place_components(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
+ // # Places
+ //
+ // In general, the full flow graph structure for an
+ // assignment/move/etc can be handled in one of two ways,
+ // depending on whether what is being assigned is a "tracked
+ // value" or not. A tracked value is basically a local
+ // variable or argument.
+ //
+ // The two kinds of graphs are:
+ //
+ // Tracked place Untracked place
+ // ----------------------++-----------------------
+ // ||
+ // | || |
+ // v || v
+ // (rvalue) || (rvalue)
+ // | || |
+ // v || v
+ // (write of place) || (place components)
+ // | || |
+ // v || v
+ // (succ) || (succ)
+ // ||
+ // ----------------------++-----------------------
+ //
+ // I will cover the two cases in turn:
+ //
+ // # Tracked places
+ //
+ // A tracked place is a local variable/argument `x`. In
+ // these cases, the link_node where the write occurs is linked
+ // to node id of `x`. The `write_place()` routine generates
+ // the contents of this node. There are no subcomponents to
+ // consider.
+ //
+ // # Non-tracked places
+ //
+ // These are places like `x[5]` or `x.f`. In that case, we
+ // basically ignore the value which is written to but generate
+ // reads for the components---`x` in these two examples. The
+ // components reads are generated by
+ // `propagate_through_place_components()` (this fn).
+ //
+ // # Illegal places
+ //
+ // It is still possible to observe assignments to non-places;
+ // these errors are detected in the later pass borrowck. We
+ // just ignore such cases and treat them as reads.
+
+ match expr.kind {
+ hir::ExprKind::Path(_) => succ,
+ hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(&e, succ),
+ _ => self.propagate_through_expr(expr, succ),
+ }
+ }
+
+ // see comment on propagate_through_place()
+ fn write_place(&mut self, expr: &Expr<'_>, succ: LiveNode, acc: u32) -> LiveNode {
+ match expr.kind {
+ hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => {
+ self.access_path(expr.hir_id, path, succ, acc)
+ }
+
+ // We do not track other places, so just propagate through
+ // to their subcomponents. Also, it may happen that
+ // non-places occur here, because those are detected in the
+ // later pass borrowck.
+ _ => succ,
+ }
+ }
+
+ fn access_var(
+ &mut self,
+ hir_id: HirId,
+ var_hid: HirId,
+ succ: LiveNode,
+ acc: u32,
+ span: Span,
+ ) -> LiveNode {
+ let ln = self.live_node(hir_id, span);
+ if acc != 0 {
+ self.init_from_succ(ln, succ);
+ let var = self.variable(var_hid, span);
+ self.acc(ln, var, acc);
+ }
+ ln
+ }
+
+ fn access_path(
+ &mut self,
+ hir_id: HirId,
+ path: &hir::Path<'_>,
+ succ: LiveNode,
+ acc: u32,
+ ) -> LiveNode {
+ match path.res {
+ Res::Local(hid) => self.access_var(hir_id, hid, succ, acc, path.span),
+ _ => succ,
+ }
+ }
+
+ fn propagate_through_loop(
+ &mut self,
+ expr: &Expr<'_>,
+ body: &hir::Block<'_>,
+ succ: LiveNode,
+ ) -> LiveNode {
+ /*
+ We model control flow like this:
+
+ (expr) <-+
+ | |
+ v |
+ (body) --+
+
+ Note that a `continue` expression targeting the `loop` will have a successor of `expr`.
+ Meanwhile, a `break` expression will have a successor of `succ`.
+ */
+
+ // first iteration:
+ let ln = self.live_node(expr.hir_id, expr.span);
+ self.init_empty(ln, succ);
+ debug!("propagate_through_loop: using id for loop body {} {:?}", expr.hir_id, body);
+
+ self.break_ln.insert(expr.hir_id, succ);
+
+ self.cont_ln.insert(expr.hir_id, ln);
+
+ let body_ln = self.propagate_through_block(body, ln);
+
+ // repeat until fixed point is reached:
+ while self.merge_from_succ(ln, body_ln) {
+ assert_eq!(body_ln, self.propagate_through_block(body, ln));
+ }
+
+ ln
+ }
+
+ fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode {
+ let ty = self.typeck_results.expr_ty(expr);
+ let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id();
+ if self.ir.tcx.is_ty_uninhabited_from(m, ty, self.param_env) {
+ match self.ir.lnks[succ] {
+ LiveNodeKind::ExprNode(succ_span, succ_id) => {
+ self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression");
+ }
+ LiveNodeKind::VarDefNode(succ_span, succ_id) => {
+ self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition");
+ }
+ _ => {}
+ };
+ self.exit_ln
+ } else {
+ succ
+ }
+ }
+
+ fn warn_about_unreachable(
+ &mut self,
+ orig_span: Span,
+ orig_ty: Ty<'tcx>,
+ expr_span: Span,
+ expr_id: HirId,
+ descr: &str,
+ ) {
+ if !orig_ty.is_never() {
+ // Unreachable code warnings are already emitted during type checking.
+ // However, during type checking, full type information is being
+ // calculated but not yet available, so the check for diverging
+ // expressions due to uninhabited result types is pretty crude and
+ // only checks whether ty.is_never(). Here, we have full type
+ // information available and can issue warnings for less obviously
+ // uninhabited types (e.g. empty enums). The check above is used so
+ // that we do not emit the same warning twice if the uninhabited type
+ // is indeed `!`.
+
+ self.ir.tcx.struct_span_lint_hir(
+ lint::builtin::UNREACHABLE_CODE,
+ expr_id,
+ expr_span,
+ |lint| {
+ let msg = format!("unreachable {}", descr);
+ lint.build(&msg)
+ .span_label(expr_span, &msg)
+ .span_label(orig_span, "any code following this expression is unreachable")
+ .span_note(
+ orig_span,
+ &format!(
+ "this expression has type `{}`, which is uninhabited",
+ orig_ty
+ ),
+ )
+ .emit();
+ },
+ );
+ }
+ }
+}
+
+// _______________________________________________________________________
+// Checking for error conditions
+
+impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
+ fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
+ self.check_unused_vars_in_pat(&local.pat, None, |spans, hir_id, ln, var| {
+ if local.init.is_some() {
+ self.warn_about_dead_assign(spans, hir_id, ln, var);
+ }
+ });
+
+ intravisit::walk_local(self, local);
+ }
+
+ fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
+ check_expr(self, ex);
+ intravisit::walk_expr(self, ex);
+ }
+
+ fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
+ self.check_unused_vars_in_pat(&arm.pat, None, |_, _, _, _| {});
+ intravisit::walk_arm(self, arm);
+ }
+}
+
+fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
+ match expr.kind {
+ hir::ExprKind::Assign(ref l, ..) => {
+ this.check_place(&l);
+ }
+
+ hir::ExprKind::AssignOp(_, ref l, _) => {
+ if !this.typeck_results.is_method_call(expr) {
+ this.check_place(&l);
+ }
+ }
+
+ hir::ExprKind::InlineAsm(ref asm) => {
+ for (op, _op_sp) in asm.operands {
+ match op {
+ hir::InlineAsmOperand::Out { expr, .. } => {
+ if let Some(expr) = expr {
+ this.check_place(expr);
+ }
+ }
+ hir::InlineAsmOperand::InOut { expr, .. } => {
+ this.check_place(expr);
+ }
+ hir::InlineAsmOperand::SplitInOut { out_expr, .. } => {
+ if let Some(out_expr) = out_expr {
+ this.check_place(out_expr);
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
+ hir::ExprKind::Let(let_expr) => {
+ this.check_unused_vars_in_pat(let_expr.pat, None, |_, _, _, _| {});
+ }
+
+ // no correctness conditions related to liveness
+ hir::ExprKind::Call(..)
+ | hir::ExprKind::MethodCall(..)
+ | hir::ExprKind::Match(..)
+ | hir::ExprKind::Loop(..)
+ | hir::ExprKind::Index(..)
+ | hir::ExprKind::Field(..)
+ | hir::ExprKind::Array(..)
+ | hir::ExprKind::Tup(..)
+ | hir::ExprKind::Binary(..)
+ | hir::ExprKind::Cast(..)
+ | hir::ExprKind::If(..)
+ | hir::ExprKind::DropTemps(..)
+ | hir::ExprKind::Unary(..)
+ | hir::ExprKind::Ret(..)
+ | hir::ExprKind::Break(..)
+ | hir::ExprKind::Continue(..)
+ | hir::ExprKind::Lit(_)
+ | hir::ExprKind::ConstBlock(..)
+ | hir::ExprKind::Block(..)
+ | hir::ExprKind::AddrOf(..)
+ | hir::ExprKind::Struct(..)
+ | hir::ExprKind::Repeat(..)
+ | hir::ExprKind::Closure { .. }
+ | hir::ExprKind::Path(_)
+ | hir::ExprKind::Yield(..)
+ | hir::ExprKind::Box(..)
+ | hir::ExprKind::Type(..)
+ | hir::ExprKind::Err => {}
+ }
+}
+
+impl<'tcx> Liveness<'_, 'tcx> {
+ fn check_place(&mut self, expr: &'tcx Expr<'tcx>) {
+ match expr.kind {
+ hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => {
+ if let Res::Local(var_hid) = path.res {
+ // Assignment to an immutable variable or argument: only legal
+ // if there is no later assignment. If this local is actually
+ // mutable, then check for a reassignment to flag the mutability
+ // as being used.
+ let ln = self.live_node(expr.hir_id, expr.span);
+ let var = self.variable(var_hid, expr.span);
+ self.warn_about_dead_assign(vec![expr.span], expr.hir_id, ln, var);
+ }
+ }
+ _ => {
+ // For other kinds of places, no checks are required,
+ // and any embedded expressions are actually rvalues
+ intravisit::walk_expr(self, expr);
+ }
+ }
+ }
+
+ fn should_warn(&self, var: Variable) -> Option<String> {
+ let name = self.ir.variable_name(var);
+ if name == kw::Empty {
+ return None;
+ }
+ let name = name.as_str();
+ if name.as_bytes()[0] == b'_' {
+ return None;
+ }
+ Some(name.to_owned())
+ }
+
+ fn warn_about_unused_upvars(&self, entry_ln: LiveNode) {
+ let Some(closure_min_captures) = self.closure_min_captures else {
+ return;
+ };
+
+ // If closure_min_captures is Some(), upvars must be Some() too.
+ for (&var_hir_id, min_capture_list) in closure_min_captures {
+ for captured_place in min_capture_list {
+ match captured_place.info.capture_kind {
+ ty::UpvarCapture::ByValue => {}
+ ty::UpvarCapture::ByRef(..) => continue,
+ };
+ let span = captured_place.get_capture_kind_span(self.ir.tcx);
+ let var = self.variable(var_hir_id, span);
+ if self.used_on_entry(entry_ln, var) {
+ if !self.live_on_entry(entry_ln, var) {
+ if let Some(name) = self.should_warn(var) {
+ self.ir.tcx.struct_span_lint_hir(
+ lint::builtin::UNUSED_ASSIGNMENTS,
+ var_hir_id,
+ vec![span],
+ |lint| {
+ lint.build(&format!(
+ "value captured by `{}` is never read",
+ name
+ ))
+ .help("did you mean to capture by reference instead?")
+ .emit();
+ },
+ );
+ }
+ }
+ } else {
+ if let Some(name) = self.should_warn(var) {
+ self.ir.tcx.struct_span_lint_hir(
+ lint::builtin::UNUSED_VARIABLES,
+ var_hir_id,
+ vec![span],
+ |lint| {
+ lint.build(&format!("unused variable: `{}`", name))
+ .help("did you mean to capture by reference instead?")
+ .emit();
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+
+ fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) {
+ for p in body.params {
+ self.check_unused_vars_in_pat(&p.pat, Some(entry_ln), |spans, hir_id, ln, var| {
+ if !self.live_on_entry(ln, var) {
+ self.report_unused_assign(hir_id, spans, var, |name| {
+ format!("value passed to `{}` is never read", name)
+ });
+ }
+ });
+ }
+ }
+
+ fn check_unused_vars_in_pat(
+ &self,
+ pat: &hir::Pat<'_>,
+ entry_ln: Option<LiveNode>,
+ on_used_on_entry: impl Fn(Vec<Span>, HirId, LiveNode, Variable),
+ ) {
+ // In an or-pattern, only consider the variable; any later patterns must have the same
+ // bindings, and we also consider the first pattern to be the "authoritative" set of ids.
+ // However, we should take the ids and spans of variables with the same name from the later
+ // patterns so the suggestions to prefix with underscores will apply to those too.
+ let mut vars: FxIndexMap<Symbol, (LiveNode, Variable, Vec<(HirId, Span, Span)>)> =
+ <_>::default();
+
+ pat.each_binding(|_, hir_id, pat_sp, ident| {
+ let ln = entry_ln.unwrap_or_else(|| self.live_node(hir_id, pat_sp));
+ let var = self.variable(hir_id, ident.span);
+ let id_and_sp = (hir_id, pat_sp, ident.span);
+ vars.entry(self.ir.variable_name(var))
+ .and_modify(|(.., hir_ids_and_spans)| hir_ids_and_spans.push(id_and_sp))
+ .or_insert_with(|| (ln, var, vec![id_and_sp]));
+ });
+
+ for (_, (ln, var, hir_ids_and_spans)) in vars {
+ if self.used_on_entry(ln, var) {
+ let id = hir_ids_and_spans[0].0;
+ let spans =
+ hir_ids_and_spans.into_iter().map(|(_, _, ident_span)| ident_span).collect();
+ on_used_on_entry(spans, id, ln, var);
+ } else {
+ self.report_unused(hir_ids_and_spans, ln, var);
+ }
+ }
+ }
+
+ fn report_unused(
+ &self,
+ hir_ids_and_spans: Vec<(HirId, Span, Span)>,
+ ln: LiveNode,
+ var: Variable,
+ ) {
+ let first_hir_id = hir_ids_and_spans[0].0;
+
+ if let Some(name) = self.should_warn(var).filter(|name| name != "self") {
+ // annoying: for parameters in funcs like `fn(x: i32)
+ // {ret}`, there is only one node, so asking about
+ // assigned_on_exit() is not meaningful.
+ let is_assigned =
+ if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) };
+
+ if is_assigned {
+ self.ir.tcx.struct_span_lint_hir(
+ lint::builtin::UNUSED_VARIABLES,
+ first_hir_id,
+ hir_ids_and_spans
+ .into_iter()
+ .map(|(_, _, ident_span)| ident_span)
+ .collect::<Vec<_>>(),
+ |lint| {
+ lint.build(&format!("variable `{}` is assigned to, but never used", name))
+ .note(&format!("consider using `_{}` instead", name))
+ .emit();
+ },
+ )
+ } else {
+ let (shorthands, non_shorthands): (Vec<_>, Vec<_>) =
+ hir_ids_and_spans.iter().copied().partition(|(hir_id, _, ident_span)| {
+ let var = self.variable(*hir_id, *ident_span);
+ self.ir.variable_is_shorthand(var)
+ });
+
+ // If we have both shorthand and non-shorthand, prefer the "try ignoring
+ // the field" message, and suggest `_` for the non-shorthands. If we only
+ // have non-shorthand, then prefix with an underscore instead.
+ if !shorthands.is_empty() {
+ let shorthands = shorthands
+ .into_iter()
+ .map(|(_, pat_span, _)| (pat_span, format!("{}: _", name)))
+ .chain(
+ non_shorthands
+ .into_iter()
+ .map(|(_, pat_span, _)| (pat_span, "_".to_string())),
+ )
+ .collect::<Vec<_>>();
+
+ self.ir.tcx.struct_span_lint_hir(
+ lint::builtin::UNUSED_VARIABLES,
+ first_hir_id,
+ hir_ids_and_spans
+ .iter()
+ .map(|(_, pat_span, _)| *pat_span)
+ .collect::<Vec<_>>(),
+ |lint| {
+ let mut err = lint.build(&format!("unused variable: `{}`", name));
+ err.multipart_suggestion(
+ "try ignoring the field",
+ shorthands,
+ Applicability::MachineApplicable,
+ );
+ err.emit();
+ },
+ );
+ } else {
+ let non_shorthands = non_shorthands
+ .into_iter()
+ .map(|(_, _, ident_span)| (ident_span, format!("_{}", name)))
+ .collect::<Vec<_>>();
+
+ self.ir.tcx.struct_span_lint_hir(
+ lint::builtin::UNUSED_VARIABLES,
+ first_hir_id,
+ hir_ids_and_spans
+ .iter()
+ .map(|(_, _, ident_span)| *ident_span)
+ .collect::<Vec<_>>(),
+ |lint| {
+ let mut err = lint.build(&format!("unused variable: `{}`", name));
+ err.multipart_suggestion(
+ "if this is intentional, prefix it with an underscore",
+ non_shorthands,
+ Applicability::MachineApplicable,
+ );
+ err.emit();
+ },
+ );
+ }
+ }
+ }
+ }
+
+ fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) {
+ if !self.live_on_exit(ln, var) {
+ self.report_unused_assign(hir_id, spans, var, |name| {
+ format!("value assigned to `{}` is never read", name)
+ });
+ }
+ }
+
+ fn report_unused_assign(
+ &self,
+ hir_id: HirId,
+ spans: Vec<Span>,
+ var: Variable,
+ message: impl Fn(&str) -> String,
+ ) {
+ if let Some(name) = self.should_warn(var) {
+ self.ir.tcx.struct_span_lint_hir(
+ lint::builtin::UNUSED_ASSIGNMENTS,
+ hir_id,
+ spans,
+ |lint| {
+ lint.build(&message(&name))
+ .help("maybe it is overwritten before being read?")
+ .emit();
+ },
+ )
+ }
+ }
+}
diff --git a/compiler/rustc_passes/src/liveness/rwu_table.rs b/compiler/rustc_passes/src/liveness/rwu_table.rs
new file mode 100644
index 000000000..6d5983f53
--- /dev/null
+++ b/compiler/rustc_passes/src/liveness/rwu_table.rs
@@ -0,0 +1,145 @@
+use crate::liveness::{LiveNode, Variable};
+use std::iter;
+
+#[derive(Clone, Copy)]
+pub(super) struct RWU {
+ pub(super) reader: bool,
+ pub(super) writer: bool,
+ pub(super) used: bool,
+}
+
+/// Conceptually, this is like a `Vec<Vec<RWU>>`. But the number of
+/// RWU`s can get very large, so it uses a more compact representation.
+pub(super) struct RWUTable {
+ /// Total number of live nodes.
+ live_nodes: usize,
+ /// Total number of variables.
+ vars: usize,
+
+ /// A compressed representation of `RWU`s.
+ ///
+ /// Each word represents 2 different `RWU`s packed together. Each packed RWU
+ /// is stored in 4 bits: a reader bit, a writer bit, a used bit and a
+ /// padding bit.
+ ///
+ /// The data for each live node is contiguous and starts at a word boundary,
+ /// so there might be an unused space left.
+ words: Vec<u8>,
+ /// Number of words per each live node.
+ live_node_words: usize,
+}
+
+impl RWUTable {
+ const RWU_READER: u8 = 0b0001;
+ const RWU_WRITER: u8 = 0b0010;
+ const RWU_USED: u8 = 0b0100;
+ const RWU_MASK: u8 = 0b1111;
+
+ /// Size of packed RWU in bits.
+ const RWU_BITS: usize = 4;
+ /// Size of a word in bits.
+ const WORD_BITS: usize = std::mem::size_of::<u8>() * 8;
+ /// Number of packed RWUs that fit into a single word.
+ const WORD_RWU_COUNT: usize = Self::WORD_BITS / Self::RWU_BITS;
+
+ pub(super) fn new(live_nodes: usize, vars: usize) -> RWUTable {
+ let live_node_words = (vars + Self::WORD_RWU_COUNT - 1) / Self::WORD_RWU_COUNT;
+ Self { live_nodes, vars, live_node_words, words: vec![0u8; live_node_words * live_nodes] }
+ }
+
+ fn word_and_shift(&self, ln: LiveNode, var: Variable) -> (usize, u32) {
+ assert!(ln.index() < self.live_nodes);
+ assert!(var.index() < self.vars);
+
+ let var = var.index();
+ let word = var / Self::WORD_RWU_COUNT;
+ let shift = Self::RWU_BITS * (var % Self::WORD_RWU_COUNT);
+ (ln.index() * self.live_node_words + word, shift as u32)
+ }
+
+ fn pick2_rows_mut(&mut self, a: LiveNode, b: LiveNode) -> (&mut [u8], &mut [u8]) {
+ assert!(a.index() < self.live_nodes);
+ assert!(b.index() < self.live_nodes);
+ assert!(a != b);
+
+ let a_start = a.index() * self.live_node_words;
+ let b_start = b.index() * self.live_node_words;
+
+ unsafe {
+ let ptr = self.words.as_mut_ptr();
+ (
+ std::slice::from_raw_parts_mut(ptr.add(a_start), self.live_node_words),
+ std::slice::from_raw_parts_mut(ptr.add(b_start), self.live_node_words),
+ )
+ }
+ }
+
+ pub(super) fn copy(&mut self, dst: LiveNode, src: LiveNode) {
+ if dst == src {
+ return;
+ }
+
+ let (dst_row, src_row) = self.pick2_rows_mut(dst, src);
+ dst_row.copy_from_slice(src_row);
+ }
+
+ /// Sets `dst` to the union of `dst` and `src`, returns true if `dst` was
+ /// changed.
+ pub(super) fn union(&mut self, dst: LiveNode, src: LiveNode) -> bool {
+ if dst == src {
+ return false;
+ }
+
+ let mut changed = false;
+ let (dst_row, src_row) = self.pick2_rows_mut(dst, src);
+ for (dst_word, src_word) in iter::zip(dst_row, &*src_row) {
+ let old = *dst_word;
+ let new = *dst_word | src_word;
+ *dst_word = new;
+ changed |= old != new;
+ }
+ changed
+ }
+
+ pub(super) fn get_reader(&self, ln: LiveNode, var: Variable) -> bool {
+ let (word, shift) = self.word_and_shift(ln, var);
+ (self.words[word] >> shift) & Self::RWU_READER != 0
+ }
+
+ pub(super) fn get_writer(&self, ln: LiveNode, var: Variable) -> bool {
+ let (word, shift) = self.word_and_shift(ln, var);
+ (self.words[word] >> shift) & Self::RWU_WRITER != 0
+ }
+
+ pub(super) fn get_used(&self, ln: LiveNode, var: Variable) -> bool {
+ let (word, shift) = self.word_and_shift(ln, var);
+ (self.words[word] >> shift) & Self::RWU_USED != 0
+ }
+
+ pub(super) fn get(&self, ln: LiveNode, var: Variable) -> RWU {
+ let (word, shift) = self.word_and_shift(ln, var);
+ let rwu_packed = self.words[word] >> shift;
+ RWU {
+ reader: rwu_packed & Self::RWU_READER != 0,
+ writer: rwu_packed & Self::RWU_WRITER != 0,
+ used: rwu_packed & Self::RWU_USED != 0,
+ }
+ }
+
+ pub(super) fn set(&mut self, ln: LiveNode, var: Variable, rwu: RWU) {
+ let mut packed = 0;
+ if rwu.reader {
+ packed |= Self::RWU_READER;
+ }
+ if rwu.writer {
+ packed |= Self::RWU_WRITER;
+ }
+ if rwu.used {
+ packed |= Self::RWU_USED;
+ }
+
+ let (word, shift) = self.word_and_shift(ln, var);
+ let word = &mut self.words[word];
+ *word = (*word & !(Self::RWU_MASK << shift)) | (packed << shift)
+ }
+}
diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs
new file mode 100644
index 000000000..cdda0e388
--- /dev/null
+++ b/compiler/rustc_passes/src/loops.rs
@@ -0,0 +1,287 @@
+use Context::*;
+
+use rustc_errors::{struct_span_err, Applicability};
+use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{Destination, Movability, Node};
+use rustc_middle::hir::map::Map;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::Session;
+use rustc_span::hygiene::DesugaringKind;
+use rustc_span::Span;
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+enum Context {
+ Normal,
+ Loop(hir::LoopSource),
+ Closure(Span),
+ AsyncClosure(Span),
+ LabeledBlock,
+ AnonConst,
+}
+
+#[derive(Copy, Clone)]
+struct CheckLoopVisitor<'a, 'hir> {
+ sess: &'a Session,
+ hir_map: Map<'hir>,
+ cx: Context,
+}
+
+fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
+ tcx.hir().visit_item_likes_in_module(
+ module_def_id,
+ &mut CheckLoopVisitor { sess: &tcx.sess, hir_map: tcx.hir(), cx: Normal },
+ );
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+ *providers = Providers { check_mod_loops, ..*providers };
+}
+
+impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.hir_map
+ }
+
+ fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) {
+ self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c));
+ }
+
+ fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
+ match e.kind {
+ hir::ExprKind::Loop(ref b, _, source, _) => {
+ self.with_context(Loop(source), |v| v.visit_block(&b));
+ }
+ hir::ExprKind::Closure(&hir::Closure {
+ ref fn_decl,
+ body,
+ fn_decl_span,
+ movability,
+ ..
+ }) => {
+ let cx = if let Some(Movability::Static) = movability {
+ AsyncClosure(fn_decl_span)
+ } else {
+ Closure(fn_decl_span)
+ };
+ self.visit_fn_decl(&fn_decl);
+ self.with_context(cx, |v| v.visit_nested_body(body));
+ }
+ hir::ExprKind::Block(ref b, Some(_label)) => {
+ self.with_context(LabeledBlock, |v| v.visit_block(&b));
+ }
+ hir::ExprKind::Break(break_label, ref opt_expr) => {
+ if let Some(e) = opt_expr {
+ self.visit_expr(e);
+ }
+
+ if self.require_label_in_labeled_block(e.span, &break_label, "break") {
+ // If we emitted an error about an unlabeled break in a labeled
+ // block, we don't need any further checking for this break any more
+ return;
+ }
+
+ let loop_id = match break_label.target_id {
+ Ok(loop_id) => Some(loop_id),
+ Err(hir::LoopIdError::OutsideLoopScope) => None,
+ Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
+ self.emit_unlabled_cf_in_while_condition(e.span, "break");
+ None
+ }
+ Err(hir::LoopIdError::UnresolvedLabel) => None,
+ };
+
+ if let Some(Node::Block(_)) = loop_id.and_then(|id| self.hir_map.find(id)) {
+ return;
+ }
+
+ if let Some(break_expr) = opt_expr {
+ let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id {
+ match self.hir_map.expect_expr(loop_id).kind {
+ hir::ExprKind::Loop(_, label, source, sp) => {
+ (Some(sp), label, Some(source))
+ }
+ ref r => {
+ span_bug!(e.span, "break label resolved to a non-loop: {:?}", r)
+ }
+ }
+ } else {
+ (None, None, None)
+ };
+ match loop_kind {
+ None | Some(hir::LoopSource::Loop) => (),
+ Some(kind) => {
+ let mut err = struct_span_err!(
+ self.sess,
+ e.span,
+ E0571,
+ "`break` with value from a `{}` loop",
+ kind.name()
+ );
+ err.span_label(
+ e.span,
+ "can only break with a value inside `loop` or breakable block",
+ );
+ if let Some(head) = head {
+ err.span_label(
+ head,
+ &format!(
+ "you can't `break` with a value in a `{}` loop",
+ kind.name()
+ ),
+ );
+ }
+ err.span_suggestion(
+ e.span,
+ &format!(
+ "use `break` on its own without a value inside this `{}` loop",
+ kind.name(),
+ ),
+ format!(
+ "break{}",
+ break_label
+ .label
+ .map_or_else(String::new, |l| format!(" {}", l.ident))
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ if let (Some(label), None) = (loop_label, break_label.label) {
+ match break_expr.kind {
+ hir::ExprKind::Path(hir::QPath::Resolved(
+ None,
+ hir::Path {
+ segments: [segment],
+ res: hir::def::Res::Err,
+ ..
+ },
+ )) if label.ident.to_string()
+ == format!("'{}", segment.ident) =>
+ {
+ // This error is redundant, we will have already emitted a
+ // suggestion to use the label when `segment` wasn't found
+ // (hence the `Res::Err` check).
+ err.delay_as_bug();
+ }
+ _ => {
+ err.span_suggestion(
+ break_expr.span,
+ "alternatively, you might have meant to use the \
+ available loop label",
+ label.ident,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+ err.emit();
+ }
+ }
+ }
+
+ self.require_break_cx("break", e.span);
+ }
+ hir::ExprKind::Continue(destination) => {
+ self.require_label_in_labeled_block(e.span, &destination, "continue");
+
+ match destination.target_id {
+ Ok(loop_id) => {
+ if let Node::Block(block) = self.hir_map.find(loop_id).unwrap() {
+ struct_span_err!(
+ self.sess,
+ e.span,
+ E0696,
+ "`continue` pointing to a labeled block"
+ )
+ .span_label(e.span, "labeled blocks cannot be `continue`'d")
+ .span_label(block.span, "labeled block the `continue` points to")
+ .emit();
+ }
+ }
+ Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
+ self.emit_unlabled_cf_in_while_condition(e.span, "continue");
+ }
+ Err(_) => {}
+ }
+ self.require_break_cx("continue", e.span)
+ }
+ _ => intravisit::walk_expr(self, e),
+ }
+ }
+}
+
+impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
+ fn with_context<F>(&mut self, cx: Context, f: F)
+ where
+ F: FnOnce(&mut CheckLoopVisitor<'a, 'hir>),
+ {
+ let old_cx = self.cx;
+ self.cx = cx;
+ f(self);
+ self.cx = old_cx;
+ }
+
+ fn require_break_cx(&self, name: &str, span: Span) {
+ let err_inside_of = |article, ty, closure_span| {
+ struct_span_err!(self.sess, span, E0267, "`{}` inside of {} {}", name, article, ty)
+ .span_label(span, format!("cannot `{}` inside of {} {}", name, article, ty))
+ .span_label(closure_span, &format!("enclosing {}", ty))
+ .emit();
+ };
+
+ match self.cx {
+ LabeledBlock | Loop(_) => {}
+ Closure(closure_span) => err_inside_of("a", "closure", closure_span),
+ AsyncClosure(closure_span) => err_inside_of("an", "`async` block", closure_span),
+ Normal | AnonConst => {
+ struct_span_err!(self.sess, span, E0268, "`{}` outside of a loop", name)
+ .span_label(span, format!("cannot `{}` outside of a loop", name))
+ .emit();
+ }
+ }
+ }
+
+ fn require_label_in_labeled_block(
+ &mut self,
+ span: Span,
+ label: &Destination,
+ cf_type: &str,
+ ) -> bool {
+ if !span.is_desugaring(DesugaringKind::QuestionMark) && self.cx == LabeledBlock {
+ if label.label.is_none() {
+ struct_span_err!(
+ self.sess,
+ span,
+ E0695,
+ "unlabeled `{}` inside of a labeled block",
+ cf_type
+ )
+ .span_label(
+ span,
+ format!(
+ "`{}` statements that would diverge to or through \
+ a labeled block need to bear a label",
+ cf_type
+ ),
+ )
+ .emit();
+ return true;
+ }
+ }
+ false
+ }
+ fn emit_unlabled_cf_in_while_condition(&mut self, span: Span, cf_type: &str) {
+ struct_span_err!(
+ self.sess,
+ span,
+ E0590,
+ "`break` or `continue` with no label in the condition of a `while` loop"
+ )
+ .span_label(span, format!("unlabeled `{}` in the condition of a `while` loop", cf_type))
+ .emit();
+ }
+}
diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs
new file mode 100644
index 000000000..20765abf3
--- /dev/null
+++ b/compiler/rustc_passes/src/naked_functions.rs
@@ -0,0 +1,335 @@
+//! Checks validity of naked functions.
+
+use rustc_ast::{Attribute, InlineAsmOptions};
+use rustc_errors::{struct_span_err, Applicability};
+use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::intravisit::{FnKind, Visitor};
+use rustc_hir::{ExprKind, HirId, InlineAsmOperand, StmtKind};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::lint::builtin::UNDEFINED_NAKED_FUNCTION_ABI;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use rustc_target::spec::abi::Abi;
+
+fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
+ tcx.hir().visit_item_likes_in_module(module_def_id, &mut CheckNakedFunctions { tcx });
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+ *providers = Providers { check_mod_naked_functions, ..*providers };
+}
+
+struct CheckNakedFunctions<'tcx> {
+ tcx: TyCtxt<'tcx>,
+}
+
+impl<'tcx> Visitor<'tcx> for CheckNakedFunctions<'tcx> {
+ fn visit_fn(
+ &mut self,
+ fk: FnKind<'_>,
+ _fd: &'tcx hir::FnDecl<'tcx>,
+ body_id: hir::BodyId,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ let ident_span;
+ let fn_header;
+
+ match fk {
+ FnKind::Closure => {
+ // Closures with a naked attribute are rejected during attribute
+ // check. Don't validate them any further.
+ return;
+ }
+ FnKind::ItemFn(ident, _, ref header, ..) => {
+ ident_span = ident.span;
+ fn_header = header;
+ }
+
+ FnKind::Method(ident, ref sig, ..) => {
+ ident_span = ident.span;
+ fn_header = &sig.header;
+ }
+ }
+
+ let attrs = self.tcx.hir().attrs(hir_id);
+ let naked = attrs.iter().any(|attr| attr.has_name(sym::naked));
+ if naked {
+ let body = self.tcx.hir().body(body_id);
+ check_abi(self.tcx, hir_id, fn_header.abi, ident_span);
+ check_no_patterns(self.tcx, body.params);
+ check_no_parameters_use(self.tcx, body);
+ check_asm(self.tcx, body, span);
+ check_inline(self.tcx, attrs);
+ }
+ }
+}
+
+/// Check that the function isn't inlined.
+fn check_inline(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
+ for attr in attrs.iter().filter(|attr| attr.has_name(sym::inline)) {
+ tcx.sess.struct_span_err(attr.span, "naked functions cannot be inlined").emit();
+ }
+}
+
+/// Checks that function uses non-Rust ABI.
+fn check_abi(tcx: TyCtxt<'_>, hir_id: HirId, abi: Abi, fn_ident_span: Span) {
+ if abi == Abi::Rust {
+ tcx.struct_span_lint_hir(UNDEFINED_NAKED_FUNCTION_ABI, hir_id, fn_ident_span, |lint| {
+ lint.build("Rust ABI is unsupported in naked functions").emit();
+ });
+ }
+}
+
+/// Checks that parameters don't use patterns. Mirrors the checks for function declarations.
+fn check_no_patterns(tcx: TyCtxt<'_>, params: &[hir::Param<'_>]) {
+ for param in params {
+ match param.pat.kind {
+ hir::PatKind::Wild
+ | hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, _, None) => {}
+ _ => {
+ tcx.sess
+ .struct_span_err(
+ param.pat.span,
+ "patterns not allowed in naked function parameters",
+ )
+ .emit();
+ }
+ }
+ }
+}
+
+/// Checks that function parameters aren't used in the function body.
+fn check_no_parameters_use<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>) {
+ let mut params = hir::HirIdSet::default();
+ for param in body.params {
+ param.pat.each_binding(|_binding_mode, hir_id, _span, _ident| {
+ params.insert(hir_id);
+ });
+ }
+ CheckParameters { tcx, params }.visit_body(body);
+}
+
+struct CheckParameters<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ params: hir::HirIdSet,
+}
+
+impl<'tcx> Visitor<'tcx> for CheckParameters<'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if let hir::ExprKind::Path(hir::QPath::Resolved(
+ _,
+ hir::Path { res: hir::def::Res::Local(var_hir_id), .. },
+ )) = expr.kind
+ {
+ if self.params.contains(var_hir_id) {
+ self.tcx
+ .sess
+ .struct_span_err(
+ expr.span,
+ "referencing function parameters is not allowed in naked functions",
+ )
+ .help("follow the calling convention in asm block to use parameters")
+ .emit();
+ return;
+ }
+ }
+ hir::intravisit::walk_expr(self, expr);
+ }
+}
+
+/// Checks that function body contains a single inline assembly block.
+fn check_asm<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>, fn_span: Span) {
+ let mut this = CheckInlineAssembly { tcx, items: Vec::new() };
+ this.visit_body(body);
+ if let [(ItemKind::Asm | ItemKind::Err, _)] = this.items[..] {
+ // Ok.
+ } else {
+ let mut diag = struct_span_err!(
+ tcx.sess,
+ fn_span,
+ E0787,
+ "naked functions must contain a single asm block"
+ );
+
+ let mut must_show_error = false;
+ let mut has_asm = false;
+ let mut has_err = false;
+ for &(kind, span) in &this.items {
+ match kind {
+ ItemKind::Asm if has_asm => {
+ must_show_error = true;
+ diag.span_label(span, "multiple asm blocks are unsupported in naked functions");
+ }
+ ItemKind::Asm => has_asm = true,
+ ItemKind::NonAsm => {
+ must_show_error = true;
+ diag.span_label(span, "non-asm is unsupported in naked functions");
+ }
+ ItemKind::Err => has_err = true,
+ }
+ }
+
+ // If the naked function only contains a single asm block and a non-zero number of
+ // errors, then don't show an additional error. This allows for appending/prepending
+ // `compile_error!("...")` statements and reduces error noise.
+ if must_show_error || !has_err {
+ diag.emit();
+ } else {
+ diag.cancel();
+ }
+ }
+}
+
+struct CheckInlineAssembly<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ items: Vec<(ItemKind, Span)>,
+}
+
+#[derive(Copy, Clone)]
+enum ItemKind {
+ Asm,
+ NonAsm,
+ Err,
+}
+
+impl<'tcx> CheckInlineAssembly<'tcx> {
+ fn check_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, span: Span) {
+ match expr.kind {
+ ExprKind::Box(..)
+ | ExprKind::ConstBlock(..)
+ | ExprKind::Array(..)
+ | ExprKind::Call(..)
+ | ExprKind::MethodCall(..)
+ | ExprKind::Tup(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Unary(..)
+ | ExprKind::Lit(..)
+ | ExprKind::Cast(..)
+ | ExprKind::Type(..)
+ | ExprKind::Loop(..)
+ | ExprKind::Match(..)
+ | ExprKind::If(..)
+ | ExprKind::Closure { .. }
+ | ExprKind::Assign(..)
+ | ExprKind::AssignOp(..)
+ | ExprKind::Field(..)
+ | ExprKind::Index(..)
+ | ExprKind::Path(..)
+ | ExprKind::AddrOf(..)
+ | ExprKind::Let(..)
+ | ExprKind::Break(..)
+ | ExprKind::Continue(..)
+ | ExprKind::Ret(..)
+ | ExprKind::Struct(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Yield(..) => {
+ self.items.push((ItemKind::NonAsm, span));
+ }
+
+ ExprKind::InlineAsm(ref asm) => {
+ self.items.push((ItemKind::Asm, span));
+ self.check_inline_asm(asm, span);
+ }
+
+ ExprKind::DropTemps(..) | ExprKind::Block(..) => {
+ hir::intravisit::walk_expr(self, expr);
+ }
+
+ ExprKind::Err => {
+ self.items.push((ItemKind::Err, span));
+ }
+ }
+ }
+
+ fn check_inline_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>, span: Span) {
+ let unsupported_operands: Vec<Span> = asm
+ .operands
+ .iter()
+ .filter_map(|&(ref op, op_sp)| match op {
+ InlineAsmOperand::Const { .. }
+ | InlineAsmOperand::SymFn { .. }
+ | InlineAsmOperand::SymStatic { .. } => None,
+ InlineAsmOperand::In { .. }
+ | InlineAsmOperand::Out { .. }
+ | InlineAsmOperand::InOut { .. }
+ | InlineAsmOperand::SplitInOut { .. } => Some(op_sp),
+ })
+ .collect();
+ if !unsupported_operands.is_empty() {
+ struct_span_err!(
+ self.tcx.sess,
+ unsupported_operands,
+ E0787,
+ "only `const` and `sym` operands are supported in naked functions",
+ )
+ .emit();
+ }
+
+ let unsupported_options: Vec<&'static str> = [
+ (InlineAsmOptions::MAY_UNWIND, "`may_unwind`"),
+ (InlineAsmOptions::NOMEM, "`nomem`"),
+ (InlineAsmOptions::NOSTACK, "`nostack`"),
+ (InlineAsmOptions::PRESERVES_FLAGS, "`preserves_flags`"),
+ (InlineAsmOptions::PURE, "`pure`"),
+ (InlineAsmOptions::READONLY, "`readonly`"),
+ ]
+ .iter()
+ .filter_map(|&(option, name)| if asm.options.contains(option) { Some(name) } else { None })
+ .collect();
+
+ if !unsupported_options.is_empty() {
+ struct_span_err!(
+ self.tcx.sess,
+ span,
+ E0787,
+ "asm options unsupported in naked functions: {}",
+ unsupported_options.join(", ")
+ )
+ .emit();
+ }
+
+ if !asm.options.contains(InlineAsmOptions::NORETURN) {
+ let last_span = asm
+ .operands
+ .last()
+ .map_or_else(|| asm.template_strs.last().unwrap().2, |op| op.1)
+ .shrink_to_hi();
+
+ struct_span_err!(
+ self.tcx.sess,
+ span,
+ E0787,
+ "asm in naked functions must use `noreturn` option"
+ )
+ .span_suggestion(
+ last_span,
+ "consider specifying that the asm block is responsible \
+ for returning from the function",
+ ", options(noreturn)",
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ }
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for CheckInlineAssembly<'tcx> {
+ fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
+ match stmt.kind {
+ StmtKind::Item(..) => {}
+ StmtKind::Local(..) => {
+ self.items.push((ItemKind::NonAsm, stmt.span));
+ }
+ StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => {
+ self.check_expr(expr, stmt.span);
+ }
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ self.check_expr(&expr, expr.span);
+ }
+}
diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs
new file mode 100644
index 000000000..f7e3fac6b
--- /dev/null
+++ b/compiler/rustc_passes/src/reachable.rs
@@ -0,0 +1,417 @@
+// Finds items that are externally reachable, to determine which items
+// need to have their metadata (and possibly their AST) serialized.
+// All items that can be referred to through an exported name are
+// reachable, and when a reachable thing is inline or generic, it
+// makes all other generics or inline functions that it references
+// reachable as well.
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::Node;
+use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
+use rustc_middle::middle::privacy;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{self, DefIdTree, TyCtxt};
+use rustc_session::config::CrateType;
+use rustc_target::spec::abi::Abi;
+
+// Returns true if the given item must be inlined because it may be
+// monomorphized or it was marked with `#[inline]`. This will only return
+// true for functions.
+fn item_might_be_inlined(tcx: TyCtxt<'_>, item: &hir::Item<'_>, attrs: &CodegenFnAttrs) -> bool {
+ if attrs.requests_inline() {
+ return true;
+ }
+
+ match item.kind {
+ hir::ItemKind::Fn(ref sig, ..) if sig.header.is_const() => true,
+ hir::ItemKind::Impl { .. } | hir::ItemKind::Fn(..) => {
+ let generics = tcx.generics_of(item.def_id);
+ generics.requires_monomorphization(tcx)
+ }
+ _ => false,
+ }
+}
+
+fn method_might_be_inlined(
+ tcx: TyCtxt<'_>,
+ impl_item: &hir::ImplItem<'_>,
+ impl_src: LocalDefId,
+) -> bool {
+ let codegen_fn_attrs = tcx.codegen_fn_attrs(impl_item.hir_id().owner.to_def_id());
+ let generics = tcx.generics_of(impl_item.def_id);
+ if codegen_fn_attrs.requests_inline() || generics.requires_monomorphization(tcx) {
+ return true;
+ }
+ if let hir::ImplItemKind::Fn(method_sig, _) = &impl_item.kind {
+ if method_sig.header.is_const() {
+ return true;
+ }
+ }
+ match tcx.hir().find_by_def_id(impl_src) {
+ Some(Node::Item(item)) => item_might_be_inlined(tcx, &item, codegen_fn_attrs),
+ Some(..) | None => span_bug!(impl_item.span, "impl did is not an item"),
+ }
+}
+
+// Information needed while computing reachability.
+struct ReachableContext<'tcx> {
+ // The type context.
+ tcx: TyCtxt<'tcx>,
+ maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>,
+ // The set of items which must be exported in the linkage sense.
+ reachable_symbols: FxHashSet<LocalDefId>,
+ // A worklist of item IDs. Each item ID in this worklist will be inlined
+ // and will be scanned for further references.
+ // FIXME(eddyb) benchmark if this would be faster as a `VecDeque`.
+ worklist: Vec<LocalDefId>,
+ // Whether any output of this compilation is a library
+ any_library: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> {
+ fn visit_nested_body(&mut self, body: hir::BodyId) {
+ let old_maybe_typeck_results =
+ self.maybe_typeck_results.replace(self.tcx.typeck_body(body));
+ let body = self.tcx.hir().body(body);
+ self.visit_body(body);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ let res = match expr.kind {
+ hir::ExprKind::Path(ref qpath) => {
+ Some(self.typeck_results().qpath_res(qpath, expr.hir_id))
+ }
+ hir::ExprKind::MethodCall(..) => self
+ .typeck_results()
+ .type_dependent_def(expr.hir_id)
+ .map(|(kind, def_id)| Res::Def(kind, def_id)),
+ _ => None,
+ };
+
+ if let Some(res) = res && let Some(def_id) = res.opt_def_id().and_then(|el| el.as_local()) {
+ if self.def_id_represents_local_inlined_item(def_id.to_def_id()) {
+ self.worklist.push(def_id);
+ } else {
+ match res {
+ // If this path leads to a constant, then we need to
+ // recurse into the constant to continue finding
+ // items that are reachable.
+ Res::Def(DefKind::Const | DefKind::AssocConst, _) => {
+ self.worklist.push(def_id);
+ }
+
+ // If this wasn't a static, then the destination is
+ // surely reachable.
+ _ => {
+ self.reachable_symbols.insert(def_id);
+ }
+ }
+ }
+ }
+
+ intravisit::walk_expr(self, expr)
+ }
+}
+
+impl<'tcx> ReachableContext<'tcx> {
+ /// Gets the type-checking results for the current body.
+ /// As this will ICE if called outside bodies, only call when working with
+ /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
+ #[track_caller]
+ fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
+ self.maybe_typeck_results
+ .expect("`ReachableContext::typeck_results` called outside of body")
+ }
+
+ // Returns true if the given def ID represents a local item that is
+ // eligible for inlining and false otherwise.
+ fn def_id_represents_local_inlined_item(&self, def_id: DefId) -> bool {
+ let Some(def_id) = def_id.as_local() else {
+ return false;
+ };
+
+ match self.tcx.hir().find_by_def_id(def_id) {
+ Some(Node::Item(item)) => match item.kind {
+ hir::ItemKind::Fn(..) => {
+ item_might_be_inlined(self.tcx, &item, self.tcx.codegen_fn_attrs(def_id))
+ }
+ _ => false,
+ },
+ Some(Node::TraitItem(trait_method)) => match trait_method.kind {
+ hir::TraitItemKind::Const(_, ref default) => default.is_some(),
+ hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)) => true,
+ hir::TraitItemKind::Fn(_, hir::TraitFn::Required(_))
+ | hir::TraitItemKind::Type(..) => false,
+ },
+ Some(Node::ImplItem(impl_item)) => match impl_item.kind {
+ hir::ImplItemKind::Const(..) => true,
+ hir::ImplItemKind::Fn(..) => {
+ let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id);
+ let impl_did = self.tcx.hir().get_parent_item(hir_id);
+ method_might_be_inlined(self.tcx, impl_item, impl_did)
+ }
+ hir::ImplItemKind::TyAlias(_) => false,
+ },
+ Some(_) => false,
+ None => false, // This will happen for default methods.
+ }
+ }
+
+ // Step 2: Mark all symbols that the symbols on the worklist touch.
+ fn propagate(&mut self) {
+ let mut scanned = FxHashSet::default();
+ while let Some(search_item) = self.worklist.pop() {
+ if !scanned.insert(search_item) {
+ continue;
+ }
+
+ if let Some(ref item) = self.tcx.hir().find_by_def_id(search_item) {
+ self.propagate_node(item, search_item);
+ }
+ }
+ }
+
+ fn propagate_node(&mut self, node: &Node<'tcx>, search_item: LocalDefId) {
+ if !self.any_library {
+ // If we are building an executable, only explicitly extern
+ // types need to be exported.
+ let reachable =
+ if let Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, ..), .. })
+ | Node::ImplItem(hir::ImplItem {
+ kind: hir::ImplItemKind::Fn(sig, ..), ..
+ }) = *node
+ {
+ sig.header.abi != Abi::Rust
+ } else {
+ false
+ };
+ let codegen_attrs = if self.tcx.def_kind(search_item).has_codegen_attrs() {
+ self.tcx.codegen_fn_attrs(search_item)
+ } else {
+ CodegenFnAttrs::EMPTY
+ };
+ let is_extern = codegen_attrs.contains_extern_indicator();
+ let std_internal =
+ codegen_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL);
+ if reachable || is_extern || std_internal {
+ self.reachable_symbols.insert(search_item);
+ }
+ } else {
+ // If we are building a library, then reachable symbols will
+ // continue to participate in linkage after this product is
+ // produced. In this case, we traverse the ast node, recursing on
+ // all reachable nodes from this one.
+ self.reachable_symbols.insert(search_item);
+ }
+
+ match *node {
+ Node::Item(item) => {
+ match item.kind {
+ hir::ItemKind::Fn(.., body) => {
+ if item_might_be_inlined(
+ self.tcx,
+ &item,
+ self.tcx.codegen_fn_attrs(item.def_id),
+ ) {
+ self.visit_nested_body(body);
+ }
+ }
+
+ // Reachable constants will be inlined into other crates
+ // unconditionally, so we need to make sure that their
+ // contents are also reachable.
+ hir::ItemKind::Const(_, init) | hir::ItemKind::Static(_, _, init) => {
+ self.visit_nested_body(init);
+ }
+
+ // These are normal, nothing reachable about these
+ // inherently and their children are already in the
+ // worklist, as determined by the privacy pass
+ hir::ItemKind::ExternCrate(_)
+ | hir::ItemKind::Use(..)
+ | hir::ItemKind::OpaqueTy(..)
+ | hir::ItemKind::TyAlias(..)
+ | hir::ItemKind::Macro(..)
+ | hir::ItemKind::Mod(..)
+ | hir::ItemKind::ForeignMod { .. }
+ | hir::ItemKind::Impl { .. }
+ | hir::ItemKind::Trait(..)
+ | hir::ItemKind::TraitAlias(..)
+ | hir::ItemKind::Struct(..)
+ | hir::ItemKind::Enum(..)
+ | hir::ItemKind::Union(..)
+ | hir::ItemKind::GlobalAsm(..) => {}
+ }
+ }
+ Node::TraitItem(trait_method) => {
+ match trait_method.kind {
+ hir::TraitItemKind::Const(_, None)
+ | hir::TraitItemKind::Fn(_, hir::TraitFn::Required(_)) => {
+ // Keep going, nothing to get exported
+ }
+ hir::TraitItemKind::Const(_, Some(body_id))
+ | hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body_id)) => {
+ self.visit_nested_body(body_id);
+ }
+ hir::TraitItemKind::Type(..) => {}
+ }
+ }
+ Node::ImplItem(impl_item) => match impl_item.kind {
+ hir::ImplItemKind::Const(_, body) => {
+ self.visit_nested_body(body);
+ }
+ hir::ImplItemKind::Fn(_, body) => {
+ let impl_def_id = self.tcx.local_parent(search_item);
+ if method_might_be_inlined(self.tcx, impl_item, impl_def_id) {
+ self.visit_nested_body(body)
+ }
+ }
+ hir::ImplItemKind::TyAlias(_) => {}
+ },
+ Node::Expr(&hir::Expr {
+ kind: hir::ExprKind::Closure(&hir::Closure { body, .. }),
+ ..
+ }) => {
+ self.visit_nested_body(body);
+ }
+ // Nothing to recurse on for these
+ Node::ForeignItem(_)
+ | Node::Variant(_)
+ | Node::Ctor(..)
+ | Node::Field(_)
+ | Node::Ty(_)
+ | Node::Crate(_) => {}
+ _ => {
+ bug!(
+ "found unexpected node kind in worklist: {} ({:?})",
+ self.tcx
+ .hir()
+ .node_to_string(self.tcx.hir().local_def_id_to_hir_id(search_item)),
+ node,
+ );
+ }
+ }
+ }
+}
+
+fn check_item<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ id: hir::ItemId,
+ worklist: &mut Vec<LocalDefId>,
+ access_levels: &privacy::AccessLevels,
+) {
+ if has_custom_linkage(tcx, id.def_id) {
+ worklist.push(id.def_id);
+ }
+
+ if !matches!(tcx.def_kind(id.def_id), DefKind::Impl) {
+ return;
+ }
+
+ // We need only trait impls here, not inherent impls, and only non-exported ones
+ let item = tcx.hir().item(id);
+ if let hir::ItemKind::Impl(hir::Impl { of_trait: Some(ref trait_ref), ref items, .. }) =
+ item.kind
+ {
+ if !access_levels.is_reachable(item.def_id) {
+ worklist.extend(items.iter().map(|ii_ref| ii_ref.id.def_id));
+
+ let Res::Def(DefKind::Trait, trait_def_id) = trait_ref.path.res else {
+ unreachable!();
+ };
+
+ if !trait_def_id.is_local() {
+ return;
+ }
+
+ worklist.extend(
+ tcx.provided_trait_methods(trait_def_id).map(|assoc| assoc.def_id.expect_local()),
+ );
+ }
+ }
+}
+
+fn has_custom_linkage<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> bool {
+ // Anything which has custom linkage gets thrown on the worklist no
+ // matter where it is in the crate, along with "special std symbols"
+ // which are currently akin to allocator symbols.
+ if !tcx.def_kind(def_id).has_codegen_attrs() {
+ return false;
+ }
+ let codegen_attrs = tcx.codegen_fn_attrs(def_id);
+ codegen_attrs.contains_extern_indicator()
+ || codegen_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
+ // FIXME(nbdd0121): `#[used]` are marked as reachable here so it's picked up by
+ // `linked_symbols` in cg_ssa. They won't be exported in binary or cdylib due to their
+ // `SymbolExportLevel::Rust` export level but may end up being exported in dylibs.
+ || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED)
+ || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)
+}
+
+fn reachable_set<'tcx>(tcx: TyCtxt<'tcx>, (): ()) -> FxHashSet<LocalDefId> {
+ let access_levels = &tcx.privacy_access_levels(());
+
+ let any_library =
+ tcx.sess.crate_types().iter().any(|ty| {
+ *ty == CrateType::Rlib || *ty == CrateType::Dylib || *ty == CrateType::ProcMacro
+ });
+ let mut reachable_context = ReachableContext {
+ tcx,
+ maybe_typeck_results: None,
+ reachable_symbols: Default::default(),
+ worklist: Vec::new(),
+ any_library,
+ };
+
+ // Step 1: Seed the worklist with all nodes which were found to be public as
+ // a result of the privacy pass along with all local lang items and impl items.
+ // If other crates link to us, they're going to expect to be able to
+ // use the lang items, so we need to be sure to mark them as
+ // exported.
+ reachable_context.worklist.extend(access_levels.map.keys());
+ for item in tcx.lang_items().items().iter() {
+ if let Some(def_id) = *item {
+ if let Some(def_id) = def_id.as_local() {
+ reachable_context.worklist.push(def_id);
+ }
+ }
+ }
+ {
+ // Some methods from non-exported (completely private) trait impls still have to be
+ // reachable if they are called from inlinable code. Generally, it's not known until
+ // monomorphization if a specific trait impl item can be reachable or not. So, we
+ // conservatively mark all of them as reachable.
+ // FIXME: One possible strategy for pruning the reachable set is to avoid marking impl
+ // items of non-exported traits (or maybe all local traits?) unless their respective
+ // trait items are used from inlinable code through method call syntax or UFCS, or their
+ // trait is a lang item.
+ let crate_items = tcx.hir_crate_items(());
+
+ for id in crate_items.items() {
+ check_item(tcx, id, &mut reachable_context.worklist, access_levels);
+ }
+
+ for id in crate_items.impl_items() {
+ if has_custom_linkage(tcx, id.def_id) {
+ reachable_context.worklist.push(id.def_id);
+ }
+ }
+ }
+
+ // Step 2: Mark all symbols that the symbols on the worklist touch.
+ reachable_context.propagate();
+
+ debug!("Inline reachability shows: {:?}", reachable_context.reachable_symbols);
+
+ // Return the set of reachable symbols.
+ reachable_context.reachable_symbols
+}
+
+pub fn provide(providers: &mut Providers) {
+ *providers = Providers { reachable_set, ..*providers };
+}
diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs
new file mode 100644
index 000000000..ca6a2ac3d
--- /dev/null
+++ b/compiler/rustc_passes/src/stability.rs
@@ -0,0 +1,1063 @@
+//! A pass that annotates every item and method with its stability level,
+//! propagating default levels lexically from parent to children ast nodes.
+
+use attr::StabilityLevel;
+use rustc_attr::{self as attr, ConstStability, Stability, Unstable, UnstableReason};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
+use rustc_errors::{struct_span_err, Applicability};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{FieldDef, Generics, HirId, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::middle::privacy::AccessLevels;
+use rustc_middle::middle::stability::{AllowUnstable, DeprecationEntry, Index};
+use rustc_middle::ty::{query::Providers, TyCtxt};
+use rustc_session::lint;
+use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED};
+use rustc_session::Session;
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::Span;
+use rustc_target::spec::abi::Abi;
+
+use std::cmp::Ordering;
+use std::iter;
+use std::mem::replace;
+use std::num::NonZeroU32;
+
+#[derive(PartialEq)]
+enum AnnotationKind {
+ /// Annotation is required if not inherited from unstable parents.
+ Required,
+ /// Annotation is useless, reject it.
+ Prohibited,
+ /// Deprecation annotation is useless, reject it. (Stability attribute is still required.)
+ DeprecationProhibited,
+ /// Annotation itself is useless, but it can be propagated to children.
+ Container,
+}
+
+/// Whether to inherit deprecation flags for nested items. In most cases, we do want to inherit
+/// deprecation, because nested items rarely have individual deprecation attributes, and so
+/// should be treated as deprecated if their parent is. However, default generic parameters
+/// have separate deprecation attributes from their parents, so we do not wish to inherit
+/// deprecation in this case. For example, inheriting deprecation for `T` in `Foo<T>`
+/// would cause a duplicate warning arising from both `Foo` and `T` being deprecated.
+#[derive(Clone)]
+enum InheritDeprecation {
+ Yes,
+ No,
+}
+
+impl InheritDeprecation {
+ fn yes(&self) -> bool {
+ matches!(self, InheritDeprecation::Yes)
+ }
+}
+
+/// Whether to inherit const stability flags for nested items. In most cases, we do not want to
+/// inherit const stability: just because an enclosing `fn` is const-stable does not mean
+/// all `extern` imports declared in it should be const-stable! However, trait methods
+/// inherit const stability attributes from their parent and do not have their own.
+enum InheritConstStability {
+ Yes,
+ No,
+}
+
+impl InheritConstStability {
+ fn yes(&self) -> bool {
+ matches!(self, InheritConstStability::Yes)
+ }
+}
+
+enum InheritStability {
+ Yes,
+ No,
+}
+
+impl InheritStability {
+ fn yes(&self) -> bool {
+ matches!(self, InheritStability::Yes)
+ }
+}
+
+/// A private tree-walker for producing an `Index`.
+struct Annotator<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ index: &'a mut Index,
+ parent_stab: Option<Stability>,
+ parent_const_stab: Option<ConstStability>,
+ parent_depr: Option<DeprecationEntry>,
+ in_trait_impl: bool,
+}
+
+impl<'a, 'tcx> Annotator<'a, 'tcx> {
+ /// Determine the stability for a node based on its attributes and inherited stability. The
+ /// stability is recorded in the index and used as the parent. If the node is a function,
+ /// `fn_sig` is its signature.
+ fn annotate<F>(
+ &mut self,
+ def_id: LocalDefId,
+ item_sp: Span,
+ fn_sig: Option<&'tcx hir::FnSig<'tcx>>,
+ kind: AnnotationKind,
+ inherit_deprecation: InheritDeprecation,
+ inherit_const_stability: InheritConstStability,
+ inherit_from_parent: InheritStability,
+ visit_children: F,
+ ) where
+ F: FnOnce(&mut Self),
+ {
+ let attrs = self.tcx.hir().attrs(self.tcx.hir().local_def_id_to_hir_id(def_id));
+ debug!("annotate(id = {:?}, attrs = {:?})", def_id, attrs);
+
+ let depr = attr::find_deprecation(&self.tcx.sess, attrs);
+ let mut is_deprecated = false;
+ if let Some((depr, span)) = &depr {
+ is_deprecated = true;
+
+ if kind == AnnotationKind::Prohibited || kind == AnnotationKind::DeprecationProhibited {
+ let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id);
+ self.tcx.struct_span_lint_hir(USELESS_DEPRECATED, hir_id, *span, |lint| {
+ lint.build("this `#[deprecated]` annotation has no effect")
+ .span_suggestion_short(
+ *span,
+ "remove the unnecessary deprecation attribute",
+ "",
+ rustc_errors::Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+
+ // `Deprecation` is just two pointers, no need to intern it
+ let depr_entry = DeprecationEntry::local(*depr, def_id);
+ self.index.depr_map.insert(def_id, depr_entry);
+ } else if let Some(parent_depr) = self.parent_depr {
+ if inherit_deprecation.yes() {
+ is_deprecated = true;
+ info!("tagging child {:?} as deprecated from parent", def_id);
+ self.index.depr_map.insert(def_id, parent_depr);
+ }
+ }
+
+ if !self.tcx.features().staged_api {
+ // Propagate unstability. This can happen even for non-staged-api crates in case
+ // -Zforce-unstable-if-unmarked is set.
+ if let Some(stab) = self.parent_stab {
+ if inherit_deprecation.yes() && stab.is_unstable() {
+ self.index.stab_map.insert(def_id, stab);
+ }
+ }
+
+ self.recurse_with_stability_attrs(
+ depr.map(|(d, _)| DeprecationEntry::local(d, def_id)),
+ None,
+ None,
+ visit_children,
+ );
+ return;
+ }
+
+ let (stab, const_stab) = attr::find_stability(&self.tcx.sess, attrs, item_sp);
+ let mut const_span = None;
+
+ let const_stab = const_stab.map(|(const_stab, const_span_node)| {
+ self.index.const_stab_map.insert(def_id, const_stab);
+ const_span = Some(const_span_node);
+ const_stab
+ });
+
+ // If the current node is a function, has const stability attributes and if it doesn not have an intrinsic ABI,
+ // check if the function/method is const or the parent impl block is const
+ if let (Some(const_span), Some(fn_sig)) = (const_span, fn_sig) {
+ if fn_sig.header.abi != Abi::RustIntrinsic
+ && fn_sig.header.abi != Abi::PlatformIntrinsic
+ && !fn_sig.header.is_const()
+ {
+ if !self.in_trait_impl
+ || (self.in_trait_impl && !self.tcx.is_const_fn_raw(def_id.to_def_id()))
+ {
+ missing_const_err(&self.tcx.sess, fn_sig.span, const_span);
+ }
+ }
+ }
+
+ // `impl const Trait for Type` items forward their const stability to their
+ // immediate children.
+ if const_stab.is_none() {
+ debug!("annotate: const_stab not found, parent = {:?}", self.parent_const_stab);
+ if let Some(parent) = self.parent_const_stab {
+ if parent.is_const_unstable() {
+ self.index.const_stab_map.insert(def_id, parent);
+ }
+ }
+ }
+
+ if let Some((rustc_attr::Deprecation { is_since_rustc_version: true, .. }, span)) = &depr {
+ if stab.is_none() {
+ struct_span_err!(
+ self.tcx.sess,
+ *span,
+ E0549,
+ "deprecated attribute must be paired with \
+ either stable or unstable attribute"
+ )
+ .emit();
+ }
+ }
+
+ let stab = stab.map(|(stab, span)| {
+ // Error if prohibited, or can't inherit anything from a container.
+ if kind == AnnotationKind::Prohibited
+ || (kind == AnnotationKind::Container && stab.level.is_stable() && is_deprecated)
+ {
+ self.tcx.sess.struct_span_err(span,"this stability annotation is useless")
+ .span_label(span, "useless stability annotation")
+ .span_label(item_sp, "the stability attribute annotates this item")
+ .emit();
+ }
+
+ debug!("annotate: found {:?}", stab);
+
+ // Check if deprecated_since < stable_since. If it is,
+ // this is *almost surely* an accident.
+ if let (&Some(dep_since), &attr::Stable { since: stab_since, .. }) =
+ (&depr.as_ref().and_then(|(d, _)| d.since), &stab.level)
+ {
+ // Explicit version of iter::order::lt to handle parse errors properly
+ for (dep_v, stab_v) in
+ iter::zip(dep_since.as_str().split('.'), stab_since.as_str().split('.'))
+ {
+ match stab_v.parse::<u64>() {
+ Err(_) => {
+ self.tcx.sess.struct_span_err(span, "invalid stability version found")
+ .span_label(span, "invalid stability version")
+ .span_label(item_sp, "the stability attribute annotates this item")
+ .emit();
+ break;
+ }
+ Ok(stab_vp) => match dep_v.parse::<u64>() {
+ Ok(dep_vp) => match dep_vp.cmp(&stab_vp) {
+ Ordering::Less => {
+ self.tcx.sess.struct_span_err(span, "an API can't be stabilized after it is deprecated")
+ .span_label(span, "invalid version")
+ .span_label(item_sp, "the stability attribute annotates this item")
+ .emit();
+ break;
+ }
+ Ordering::Equal => continue,
+ Ordering::Greater => break,
+ },
+ Err(_) => {
+ if dep_v != "TBD" {
+ self.tcx.sess.struct_span_err(span, "invalid deprecation version found")
+ .span_label(span, "invalid deprecation version")
+ .span_label(item_sp, "the stability attribute annotates this item")
+ .emit();
+ }
+ break;
+ }
+ },
+ }
+ }
+ }
+
+ if let Stability { level: Unstable { implied_by: Some(implied_by), .. }, feature } = stab {
+ self.index.implications.insert(implied_by, feature);
+ }
+
+ self.index.stab_map.insert(def_id, stab);
+ stab
+ });
+
+ if stab.is_none() {
+ debug!("annotate: stab not found, parent = {:?}", self.parent_stab);
+ if let Some(stab) = self.parent_stab {
+ if inherit_deprecation.yes() && stab.is_unstable() || inherit_from_parent.yes() {
+ self.index.stab_map.insert(def_id, stab);
+ }
+ }
+ }
+
+ self.recurse_with_stability_attrs(
+ depr.map(|(d, _)| DeprecationEntry::local(d, def_id)),
+ stab,
+ if inherit_const_stability.yes() { const_stab } else { None },
+ visit_children,
+ );
+ }
+
+ fn recurse_with_stability_attrs(
+ &mut self,
+ depr: Option<DeprecationEntry>,
+ stab: Option<Stability>,
+ const_stab: Option<ConstStability>,
+ f: impl FnOnce(&mut Self),
+ ) {
+ // These will be `Some` if this item changes the corresponding stability attribute.
+ let mut replaced_parent_depr = None;
+ let mut replaced_parent_stab = None;
+ let mut replaced_parent_const_stab = None;
+
+ if let Some(depr) = depr {
+ replaced_parent_depr = Some(replace(&mut self.parent_depr, Some(depr)));
+ }
+ if let Some(stab) = stab {
+ replaced_parent_stab = Some(replace(&mut self.parent_stab, Some(stab)));
+ }
+ if let Some(const_stab) = const_stab {
+ replaced_parent_const_stab =
+ Some(replace(&mut self.parent_const_stab, Some(const_stab)));
+ }
+
+ f(self);
+
+ if let Some(orig_parent_depr) = replaced_parent_depr {
+ self.parent_depr = orig_parent_depr;
+ }
+ if let Some(orig_parent_stab) = replaced_parent_stab {
+ self.parent_stab = orig_parent_stab;
+ }
+ if let Some(orig_parent_const_stab) = replaced_parent_const_stab {
+ self.parent_const_stab = orig_parent_const_stab;
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
+ /// Because stability levels are scoped lexically, we want to walk
+ /// nested items in the context of the outer item, so enable
+ /// deep-walking.
+ type NestedFilter = nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_item(&mut self, i: &'tcx Item<'tcx>) {
+ let orig_in_trait_impl = self.in_trait_impl;
+ let mut kind = AnnotationKind::Required;
+ let mut const_stab_inherit = InheritConstStability::No;
+ let mut fn_sig = None;
+
+ match i.kind {
+ // Inherent impls and foreign modules serve only as containers for other items,
+ // they don't have their own stability. They still can be annotated as unstable
+ // and propagate this instability to children, but this annotation is completely
+ // optional. They inherit stability from their parents when unannotated.
+ hir::ItemKind::Impl(hir::Impl { of_trait: None, .. })
+ | hir::ItemKind::ForeignMod { .. } => {
+ self.in_trait_impl = false;
+ kind = AnnotationKind::Container;
+ }
+ hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) => {
+ self.in_trait_impl = true;
+ kind = AnnotationKind::DeprecationProhibited;
+ const_stab_inherit = InheritConstStability::Yes;
+ }
+ hir::ItemKind::Struct(ref sd, _) => {
+ if let Some(ctor_hir_id) = sd.ctor_hir_id() {
+ self.annotate(
+ self.tcx.hir().local_def_id(ctor_hir_id),
+ i.span,
+ None,
+ AnnotationKind::Required,
+ InheritDeprecation::Yes,
+ InheritConstStability::No,
+ InheritStability::Yes,
+ |_| {},
+ )
+ }
+ }
+ hir::ItemKind::Fn(ref item_fn_sig, _, _) => {
+ fn_sig = Some(item_fn_sig);
+ }
+ _ => {}
+ }
+
+ self.annotate(
+ i.def_id,
+ i.span,
+ fn_sig,
+ kind,
+ InheritDeprecation::Yes,
+ const_stab_inherit,
+ InheritStability::No,
+ |v| intravisit::walk_item(v, i),
+ );
+ self.in_trait_impl = orig_in_trait_impl;
+ }
+
+ fn visit_trait_item(&mut self, ti: &'tcx hir::TraitItem<'tcx>) {
+ let fn_sig = match ti.kind {
+ hir::TraitItemKind::Fn(ref fn_sig, _) => Some(fn_sig),
+ _ => None,
+ };
+
+ self.annotate(
+ ti.def_id,
+ ti.span,
+ fn_sig,
+ AnnotationKind::Required,
+ InheritDeprecation::Yes,
+ InheritConstStability::No,
+ InheritStability::No,
+ |v| {
+ intravisit::walk_trait_item(v, ti);
+ },
+ );
+ }
+
+ fn visit_impl_item(&mut self, ii: &'tcx hir::ImplItem<'tcx>) {
+ let kind =
+ if self.in_trait_impl { AnnotationKind::Prohibited } else { AnnotationKind::Required };
+
+ let fn_sig = match ii.kind {
+ hir::ImplItemKind::Fn(ref fn_sig, _) => Some(fn_sig),
+ _ => None,
+ };
+
+ self.annotate(
+ ii.def_id,
+ ii.span,
+ fn_sig,
+ kind,
+ InheritDeprecation::Yes,
+ InheritConstStability::No,
+ InheritStability::No,
+ |v| {
+ intravisit::walk_impl_item(v, ii);
+ },
+ );
+ }
+
+ fn visit_variant(&mut self, var: &'tcx Variant<'tcx>, g: &'tcx Generics<'tcx>, item_id: HirId) {
+ self.annotate(
+ self.tcx.hir().local_def_id(var.id),
+ var.span,
+ None,
+ AnnotationKind::Required,
+ InheritDeprecation::Yes,
+ InheritConstStability::No,
+ InheritStability::Yes,
+ |v| {
+ if let Some(ctor_hir_id) = var.data.ctor_hir_id() {
+ v.annotate(
+ v.tcx.hir().local_def_id(ctor_hir_id),
+ var.span,
+ None,
+ AnnotationKind::Required,
+ InheritDeprecation::Yes,
+ InheritConstStability::No,
+ InheritStability::No,
+ |_| {},
+ );
+ }
+
+ intravisit::walk_variant(v, var, g, item_id)
+ },
+ )
+ }
+
+ fn visit_field_def(&mut self, s: &'tcx FieldDef<'tcx>) {
+ self.annotate(
+ self.tcx.hir().local_def_id(s.hir_id),
+ s.span,
+ None,
+ AnnotationKind::Required,
+ InheritDeprecation::Yes,
+ InheritConstStability::No,
+ InheritStability::Yes,
+ |v| {
+ intravisit::walk_field_def(v, s);
+ },
+ );
+ }
+
+ fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) {
+ self.annotate(
+ i.def_id,
+ i.span,
+ None,
+ AnnotationKind::Required,
+ InheritDeprecation::Yes,
+ InheritConstStability::No,
+ InheritStability::No,
+ |v| {
+ intravisit::walk_foreign_item(v, i);
+ },
+ );
+ }
+
+ fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
+ let kind = match &p.kind {
+ // Allow stability attributes on default generic arguments.
+ hir::GenericParamKind::Type { default: Some(_), .. }
+ | hir::GenericParamKind::Const { default: Some(_), .. } => AnnotationKind::Container,
+ _ => AnnotationKind::Prohibited,
+ };
+
+ self.annotate(
+ self.tcx.hir().local_def_id(p.hir_id),
+ p.span,
+ None,
+ kind,
+ InheritDeprecation::No,
+ InheritConstStability::No,
+ InheritStability::No,
+ |v| {
+ intravisit::walk_generic_param(v, p);
+ },
+ );
+ }
+}
+
+struct MissingStabilityAnnotations<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ access_levels: &'tcx AccessLevels,
+}
+
+impl<'tcx> MissingStabilityAnnotations<'tcx> {
+ fn check_missing_stability(&self, def_id: LocalDefId, span: Span) {
+ let stab = self.tcx.stability().local_stability(def_id);
+ if !self.tcx.sess.opts.test && stab.is_none() && self.access_levels.is_reachable(def_id) {
+ let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
+ self.tcx.sess.span_err(span, &format!("{} has missing stability attribute", descr));
+ }
+ }
+
+ fn check_missing_const_stability(&self, def_id: LocalDefId, span: Span) {
+ if !self.tcx.features().staged_api {
+ return;
+ }
+
+ let is_const = self.tcx.is_const_fn(def_id.to_def_id())
+ || self.tcx.is_const_trait_impl_raw(def_id.to_def_id());
+ let is_stable = self
+ .tcx
+ .lookup_stability(def_id)
+ .map_or(false, |stability| stability.level.is_stable());
+ let missing_const_stability_attribute = self.tcx.lookup_const_stability(def_id).is_none();
+ let is_reachable = self.access_levels.is_reachable(def_id);
+
+ if is_const && is_stable && missing_const_stability_attribute && is_reachable {
+ let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id());
+ self.tcx.sess.span_err(span, &format!("{descr} has missing const stability attribute"));
+ }
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_item(&mut self, i: &'tcx Item<'tcx>) {
+ // Inherent impls and foreign modules serve only as containers for other items,
+ // they don't have their own stability. They still can be annotated as unstable
+ // and propagate this instability to children, but this annotation is completely
+ // optional. They inherit stability from their parents when unannotated.
+ if !matches!(
+ i.kind,
+ hir::ItemKind::Impl(hir::Impl { of_trait: None, .. })
+ | hir::ItemKind::ForeignMod { .. }
+ ) {
+ self.check_missing_stability(i.def_id, i.span);
+ }
+
+ // Ensure stable `const fn` have a const stability attribute.
+ self.check_missing_const_stability(i.def_id, i.span);
+
+ intravisit::walk_item(self, i)
+ }
+
+ fn visit_trait_item(&mut self, ti: &'tcx hir::TraitItem<'tcx>) {
+ self.check_missing_stability(ti.def_id, ti.span);
+ intravisit::walk_trait_item(self, ti);
+ }
+
+ fn visit_impl_item(&mut self, ii: &'tcx hir::ImplItem<'tcx>) {
+ let impl_def_id = self.tcx.hir().get_parent_item(ii.hir_id());
+ if self.tcx.impl_trait_ref(impl_def_id).is_none() {
+ self.check_missing_stability(ii.def_id, ii.span);
+ self.check_missing_const_stability(ii.def_id, ii.span);
+ }
+ intravisit::walk_impl_item(self, ii);
+ }
+
+ fn visit_variant(&mut self, var: &'tcx Variant<'tcx>, g: &'tcx Generics<'tcx>, item_id: HirId) {
+ self.check_missing_stability(self.tcx.hir().local_def_id(var.id), var.span);
+ intravisit::walk_variant(self, var, g, item_id);
+ }
+
+ fn visit_field_def(&mut self, s: &'tcx FieldDef<'tcx>) {
+ self.check_missing_stability(self.tcx.hir().local_def_id(s.hir_id), s.span);
+ intravisit::walk_field_def(self, s);
+ }
+
+ fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) {
+ self.check_missing_stability(i.def_id, i.span);
+ intravisit::walk_foreign_item(self, i);
+ }
+ // Note that we don't need to `check_missing_stability` for default generic parameters,
+ // as we assume that any default generic parameters without attributes are automatically
+ // stable (assuming they have not inherited instability from their parent).
+}
+
+fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index {
+ let mut index = Index {
+ stab_map: Default::default(),
+ const_stab_map: Default::default(),
+ depr_map: Default::default(),
+ implications: Default::default(),
+ };
+
+ {
+ let mut annotator = Annotator {
+ tcx,
+ index: &mut index,
+ parent_stab: None,
+ parent_const_stab: None,
+ parent_depr: None,
+ in_trait_impl: false,
+ };
+
+ // If the `-Z force-unstable-if-unmarked` flag is passed then we provide
+ // a parent stability annotation which indicates that this is private
+ // with the `rustc_private` feature. This is intended for use when
+ // compiling `librustc_*` crates themselves so we can leverage crates.io
+ // while maintaining the invariant that all sysroot crates are unstable
+ // by default and are unable to be used.
+ if tcx.sess.opts.unstable_opts.force_unstable_if_unmarked {
+ let stability = Stability {
+ level: attr::StabilityLevel::Unstable {
+ reason: UnstableReason::Default,
+ issue: NonZeroU32::new(27812),
+ is_soft: false,
+ implied_by: None,
+ },
+ feature: sym::rustc_private,
+ };
+ annotator.parent_stab = Some(stability);
+ }
+
+ annotator.annotate(
+ CRATE_DEF_ID,
+ tcx.hir().span(CRATE_HIR_ID),
+ None,
+ AnnotationKind::Required,
+ InheritDeprecation::Yes,
+ InheritConstStability::No,
+ InheritStability::No,
+ |v| tcx.hir().walk_toplevel_module(v),
+ );
+ }
+ index
+}
+
+/// Cross-references the feature names of unstable APIs with enabled
+/// features and possibly prints errors.
+fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
+ tcx.hir().visit_item_likes_in_module(module_def_id, &mut Checker { tcx });
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+ *providers = Providers {
+ check_mod_unstable_api_usage,
+ stability_index,
+ stability_implications: |tcx, _| tcx.stability().implications.clone(),
+ lookup_stability: |tcx, id| tcx.stability().local_stability(id.expect_local()),
+ lookup_const_stability: |tcx, id| tcx.stability().local_const_stability(id.expect_local()),
+ lookup_deprecation_entry: |tcx, id| {
+ tcx.stability().local_deprecation_entry(id.expect_local())
+ },
+ ..*providers
+ };
+}
+
+struct Checker<'tcx> {
+ tcx: TyCtxt<'tcx>,
+}
+
+impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
+ type NestedFilter = nested_filter::OnlyBodies;
+
+ /// Because stability levels are scoped lexically, we want to walk
+ /// nested items in the context of the outer item, so enable
+ /// deep-walking.
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
+ match item.kind {
+ hir::ItemKind::ExternCrate(_) => {
+ // compiler-generated `extern crate` items have a dummy span.
+ // `std` is still checked for the `restricted-std` feature.
+ if item.span.is_dummy() && item.ident.name != sym::std {
+ return;
+ }
+
+ let Some(cnum) = self.tcx.extern_mod_stmt_cnum(item.def_id) else {
+ return;
+ };
+ let def_id = cnum.as_def_id();
+ self.tcx.check_stability(def_id, Some(item.hir_id()), item.span, None);
+ }
+
+ // For implementations of traits, check the stability of each item
+ // individually as it's possible to have a stable trait with unstable
+ // items.
+ hir::ItemKind::Impl(hir::Impl {
+ of_trait: Some(ref t),
+ self_ty,
+ items,
+ constness,
+ ..
+ }) => {
+ let features = self.tcx.features();
+ if features.staged_api {
+ let attrs = self.tcx.hir().attrs(item.hir_id());
+ let (stab, const_stab) = attr::find_stability(&self.tcx.sess, attrs, item.span);
+
+ // If this impl block has an #[unstable] attribute, give an
+ // error if all involved types and traits are stable, because
+ // it will have no effect.
+ // See: https://github.com/rust-lang/rust/issues/55436
+ if let Some((Stability { level: attr::Unstable { .. }, .. }, span)) = stab {
+ let mut c = CheckTraitImplStable { tcx: self.tcx, fully_stable: true };
+ c.visit_ty(self_ty);
+ c.visit_trait_ref(t);
+ if c.fully_stable {
+ self.tcx.struct_span_lint_hir(
+ INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
+ item.hir_id(),
+ span,
+ |lint| {lint
+ .build("an `#[unstable]` annotation here has no effect")
+ .note("see issue #55436 <https://github.com/rust-lang/rust/issues/55436> for more information")
+ .emit();}
+ );
+ }
+ }
+
+ // `#![feature(const_trait_impl)]` is unstable, so any impl declared stable
+ // needs to have an error emitted.
+ if features.const_trait_impl
+ && *constness == hir::Constness::Const
+ && const_stab.map_or(false, |(stab, _)| stab.is_const_stable())
+ {
+ self.tcx
+ .sess
+ .struct_span_err(item.span, "trait implementations cannot be const stable yet")
+ .note("see issue #67792 <https://github.com/rust-lang/rust/issues/67792> for more information")
+ .emit();
+ }
+ }
+
+ for impl_item_ref in *items {
+ let impl_item = self.tcx.associated_item(impl_item_ref.id.def_id);
+
+ if let Some(def_id) = impl_item.trait_item_def_id {
+ // Pass `None` to skip deprecation warnings.
+ self.tcx.check_stability(def_id, None, impl_item_ref.span, None);
+ }
+ }
+ }
+
+ _ => (/* pass */),
+ }
+ intravisit::walk_item(self, item);
+ }
+
+ fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, id: hir::HirId) {
+ if let Some(def_id) = path.res.opt_def_id() {
+ let method_span = path.segments.last().map(|s| s.ident.span);
+ let item_is_allowed = self.tcx.check_stability_allow_unstable(
+ def_id,
+ Some(id),
+ path.span,
+ method_span,
+ if is_unstable_reexport(self.tcx, id) {
+ AllowUnstable::Yes
+ } else {
+ AllowUnstable::No
+ },
+ );
+
+ let is_allowed_through_unstable_modules = |def_id| {
+ self.tcx
+ .lookup_stability(def_id)
+ .map(|stab| match stab.level {
+ StabilityLevel::Stable { allowed_through_unstable_modules, .. } => {
+ allowed_through_unstable_modules
+ }
+ _ => false,
+ })
+ .unwrap_or(false)
+ };
+
+ if item_is_allowed && !is_allowed_through_unstable_modules(def_id) {
+ // Check parent modules stability as well if the item the path refers to is itself
+ // stable. We only emit warnings for unstable path segments if the item is stable
+ // or allowed because stability is often inherited, so the most common case is that
+ // both the segments and the item are unstable behind the same feature flag.
+ //
+ // We check here rather than in `visit_path_segment` to prevent visiting the last
+ // path segment twice
+ //
+ // We include special cases via #[rustc_allowed_through_unstable_modules] for items
+ // that were accidentally stabilized through unstable paths before this check was
+ // added, such as `core::intrinsics::transmute`
+ let parents = path.segments.iter().rev().skip(1);
+ for path_segment in parents {
+ if let Some(def_id) = path_segment.res.as_ref().and_then(Res::opt_def_id) {
+ // use `None` for id to prevent deprecation check
+ self.tcx.check_stability_allow_unstable(
+ def_id,
+ None,
+ path.span,
+ None,
+ if is_unstable_reexport(self.tcx, id) {
+ AllowUnstable::Yes
+ } else {
+ AllowUnstable::No
+ },
+ );
+ }
+ }
+ }
+ }
+
+ intravisit::walk_path(self, path)
+ }
+}
+
+/// Check whether a path is a `use` item that has been marked as unstable.
+///
+/// See issue #94972 for details on why this is a special case
+fn is_unstable_reexport<'tcx>(tcx: TyCtxt<'tcx>, id: hir::HirId) -> bool {
+ // Get the LocalDefId so we can lookup the item to check the kind.
+ let Some(def_id) = tcx.hir().opt_local_def_id(id) else { return false; };
+
+ let Some(stab) = tcx.stability().local_stability(def_id) else {
+ return false;
+ };
+
+ if stab.level.is_stable() {
+ // The re-export is not marked as unstable, don't override
+ return false;
+ }
+
+ // If this is a path that isn't a use, we don't need to do anything special
+ if !matches!(tcx.hir().item(hir::ItemId { def_id }).kind, ItemKind::Use(..)) {
+ return false;
+ }
+
+ true
+}
+
+struct CheckTraitImplStable<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ fully_stable: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for CheckTraitImplStable<'tcx> {
+ fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _id: hir::HirId) {
+ if let Some(def_id) = path.res.opt_def_id() {
+ if let Some(stab) = self.tcx.lookup_stability(def_id) {
+ self.fully_stable &= stab.level.is_stable();
+ }
+ }
+ intravisit::walk_path(self, path)
+ }
+
+ fn visit_trait_ref(&mut self, t: &'tcx TraitRef<'tcx>) {
+ if let Res::Def(DefKind::Trait, trait_did) = t.path.res {
+ if let Some(stab) = self.tcx.lookup_stability(trait_did) {
+ self.fully_stable &= stab.level.is_stable();
+ }
+ }
+ intravisit::walk_trait_ref(self, t)
+ }
+
+ fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) {
+ if let TyKind::Never = t.kind {
+ self.fully_stable = false;
+ }
+ intravisit::walk_ty(self, t)
+ }
+}
+
+/// Given the list of enabled features that were not language features (i.e., that
+/// were expected to be library features), and the list of features used from
+/// libraries, identify activated features that don't exist and error about them.
+pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
+ let is_staged_api =
+ tcx.sess.opts.unstable_opts.force_unstable_if_unmarked || tcx.features().staged_api;
+ if is_staged_api {
+ let access_levels = &tcx.privacy_access_levels(());
+ let mut missing = MissingStabilityAnnotations { tcx, access_levels };
+ missing.check_missing_stability(CRATE_DEF_ID, tcx.hir().span(CRATE_HIR_ID));
+ tcx.hir().walk_toplevel_module(&mut missing);
+ tcx.hir().visit_all_item_likes_in_crate(&mut missing);
+ }
+
+ let declared_lang_features = &tcx.features().declared_lang_features;
+ let mut lang_features = FxHashSet::default();
+ for &(feature, span, since) in declared_lang_features {
+ if let Some(since) = since {
+ // Warn if the user has enabled an already-stable lang feature.
+ unnecessary_stable_feature_lint(tcx, span, feature, since);
+ }
+ if !lang_features.insert(feature) {
+ // Warn if the user enables a lang feature multiple times.
+ duplicate_feature_err(tcx.sess, span, feature);
+ }
+ }
+
+ let declared_lib_features = &tcx.features().declared_lib_features;
+ let mut remaining_lib_features = FxIndexMap::default();
+ for (feature, span) in declared_lib_features {
+ if !tcx.sess.opts.unstable_features.is_nightly_build() {
+ struct_span_err!(
+ tcx.sess,
+ *span,
+ E0554,
+ "`#![feature]` may not be used on the {} release channel",
+ env!("CFG_RELEASE_CHANNEL")
+ )
+ .emit();
+ }
+ if remaining_lib_features.contains_key(&feature) {
+ // Warn if the user enables a lib feature multiple times.
+ duplicate_feature_err(tcx.sess, *span, *feature);
+ }
+ remaining_lib_features.insert(feature, *span);
+ }
+ // `stdbuild` has special handling for `libc`, so we need to
+ // recognise the feature when building std.
+ // Likewise, libtest is handled specially, so `test` isn't
+ // available as we'd like it to be.
+ // FIXME: only remove `libc` when `stdbuild` is active.
+ // FIXME: remove special casing for `test`.
+ remaining_lib_features.remove(&sym::libc);
+ remaining_lib_features.remove(&sym::test);
+
+ // We always collect the lib features declared in the current crate, even if there are
+ // no unknown features, because the collection also does feature attribute validation.
+ let local_defined_features = tcx.lib_features(());
+ let mut all_lib_features: FxHashMap<_, _> =
+ local_defined_features.to_vec().iter().map(|el| *el).collect();
+ let mut implications = tcx.stability_implications(rustc_hir::def_id::LOCAL_CRATE).clone();
+ for &cnum in tcx.crates(()) {
+ implications.extend(tcx.stability_implications(cnum));
+ all_lib_features.extend(tcx.defined_lib_features(cnum).iter().map(|el| *el));
+ }
+
+ // Check that every feature referenced by an `implied_by` exists (for features defined in the
+ // local crate).
+ for (implied_by, feature) in tcx.stability_implications(rustc_hir::def_id::LOCAL_CRATE) {
+ // Only `implied_by` needs to be checked, `feature` is guaranteed to exist.
+ if !all_lib_features.contains_key(implied_by) {
+ let span = local_defined_features
+ .stable
+ .get(feature)
+ .map(|(_, span)| span)
+ .or_else(|| local_defined_features.unstable.get(feature))
+ .expect("feature that implied another does not exist");
+ tcx.sess
+ .struct_span_err(
+ *span,
+ format!("feature `{implied_by}` implying `{feature}` does not exist"),
+ )
+ .emit();
+ }
+ }
+
+ if !remaining_lib_features.is_empty() {
+ for (feature, since) in all_lib_features.iter() {
+ if let Some(since) = since && let Some(span) = remaining_lib_features.get(&feature) {
+ // Warn if the user has enabled an already-stable lib feature.
+ if let Some(implies) = implications.get(&feature) {
+ unnecessary_partially_stable_feature_lint(tcx, *span, *feature, *implies, *since);
+ } else {
+ unnecessary_stable_feature_lint(tcx, *span, *feature, *since);
+ }
+ }
+ remaining_lib_features.remove(&feature);
+ if remaining_lib_features.is_empty() {
+ break;
+ }
+ }
+ }
+
+ for (feature, span) in remaining_lib_features {
+ struct_span_err!(tcx.sess, span, E0635, "unknown feature `{}`", feature).emit();
+ }
+
+ // FIXME(#44232): the `used_features` table no longer exists, so we
+ // don't lint about unused features. We should re-enable this one day!
+}
+
+fn unnecessary_partially_stable_feature_lint(
+ tcx: TyCtxt<'_>,
+ span: Span,
+ feature: Symbol,
+ implies: Symbol,
+ since: Symbol,
+) {
+ tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, |lint| {
+ lint.build(&format!(
+ "the feature `{feature}` has been partially stabilized since {since} and is succeeded \
+ by the feature `{implies}`"
+ ))
+ .span_suggestion(
+ span,
+ &format!(
+ "if you are using features which are still unstable, change to using `{implies}`"
+ ),
+ implies,
+ Applicability::MaybeIncorrect,
+ )
+ .span_suggestion(
+ tcx.sess.source_map().span_extend_to_line(span),
+ "if you are using features which are now stable, remove this line",
+ "",
+ Applicability::MaybeIncorrect,
+ )
+ .emit();
+ });
+}
+
+fn unnecessary_stable_feature_lint(tcx: TyCtxt<'_>, span: Span, feature: Symbol, since: Symbol) {
+ tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, |lint| {
+ lint.build(&format!(
+ "the feature `{feature}` has been stable since {since} and no longer requires an \
+ attribute to enable",
+ ))
+ .emit();
+ });
+}
+
+fn duplicate_feature_err(sess: &Session, span: Span, feature: Symbol) {
+ struct_span_err!(sess, span, E0636, "the feature `{}` has already been declared", feature)
+ .emit();
+}
+
+fn missing_const_err(session: &Session, fn_sig_span: Span, const_span: Span) {
+ const ERROR_MSG: &'static str = "attributes `#[rustc_const_unstable]` \
+ and `#[rustc_const_stable]` require \
+ the function or method to be `const`";
+
+ session
+ .struct_span_err(fn_sig_span, ERROR_MSG)
+ .span_help(fn_sig_span, "make the function or method const")
+ .span_label(const_span, "attribute specified here")
+ .emit();
+}
diff --git a/compiler/rustc_passes/src/upvars.rs b/compiler/rustc_passes/src/upvars.rs
new file mode 100644
index 000000000..68d9bf22b
--- /dev/null
+++ b/compiler/rustc_passes/src/upvars.rs
@@ -0,0 +1,97 @@
+//! Upvar (closure capture) collection from cross-body HIR uses of `Res::Local`s.
+
+use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::{self, HirId};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::Span;
+
+pub fn provide(providers: &mut Providers) {
+ providers.upvars_mentioned = |tcx, def_id| {
+ if !tcx.is_closure(def_id) {
+ return None;
+ }
+
+ let local_def_id = def_id.expect_local();
+ let body = tcx.hir().body(tcx.hir().maybe_body_owned_by(local_def_id)?);
+
+ let mut local_collector = LocalCollector::default();
+ local_collector.visit_body(body);
+
+ let mut capture_collector = CaptureCollector {
+ tcx,
+ locals: &local_collector.locals,
+ upvars: FxIndexMap::default(),
+ };
+ capture_collector.visit_body(body);
+
+ if !capture_collector.upvars.is_empty() {
+ Some(tcx.arena.alloc(capture_collector.upvars))
+ } else {
+ None
+ }
+ };
+}
+
+#[derive(Default)]
+struct LocalCollector {
+ // FIXME(eddyb) perhaps use `ItemLocalId` instead?
+ locals: FxHashSet<HirId>,
+}
+
+impl<'tcx> Visitor<'tcx> for LocalCollector {
+ fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
+ if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
+ self.locals.insert(hir_id);
+ }
+ intravisit::walk_pat(self, pat);
+ }
+}
+
+struct CaptureCollector<'a, 'tcx> {
+ tcx: TyCtxt<'tcx>,
+ locals: &'a FxHashSet<HirId>,
+ upvars: FxIndexMap<HirId, hir::Upvar>,
+}
+
+impl CaptureCollector<'_, '_> {
+ fn visit_local_use(&mut self, var_id: HirId, span: Span) {
+ if !self.locals.contains(&var_id) {
+ self.upvars.entry(var_id).or_insert(hir::Upvar { span });
+ }
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for CaptureCollector<'_, 'tcx> {
+ fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
+ if let Res::Local(var_id) = path.res {
+ self.visit_local_use(var_id, path.span);
+ }
+
+ intravisit::walk_path(self, path);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if let hir::ExprKind::Closure { .. } = expr.kind {
+ let closure_def_id = self.tcx.hir().local_def_id(expr.hir_id);
+ if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
+ // Every capture of a closure expression is a local in scope,
+ // that is moved/copied/borrowed into the closure value, and
+ // for this analysis they are like any other access to a local.
+ //
+ // E.g. in `|b| |c| (a, b, c)`, the upvars of the inner closure
+ // are `a` and `b`, and while `a` is not directly used in the
+ // outer closure, it needs to be an upvar there too, so that
+ // the inner closure can take it (from the outer closure's env).
+ for (&var_id, upvar) in upvars {
+ self.visit_local_use(var_id, upvar.span);
+ }
+ }
+ }
+
+ intravisit::walk_expr(self, expr);
+ }
+}
diff --git a/compiler/rustc_passes/src/weak_lang_items.rs b/compiler/rustc_passes/src/weak_lang_items.rs
new file mode 100644
index 000000000..c48b4ecf8
--- /dev/null
+++ b/compiler/rustc_passes/src/weak_lang_items.rs
@@ -0,0 +1,91 @@
+//! Validity checking for weak lang items
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::struct_span_err;
+use rustc_hir::lang_items::{self, LangItem};
+use rustc_hir::weak_lang_items::WEAK_ITEMS_REFS;
+use rustc_middle::middle::lang_items::required;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::config::CrateType;
+
+/// Checks the crate for usage of weak lang items, returning a vector of all the
+/// language items required by this crate, but not defined yet.
+pub fn check_crate<'tcx>(tcx: TyCtxt<'tcx>, items: &mut lang_items::LanguageItems) {
+ // These are never called by user code, they're generated by the compiler.
+ // They will never implicitly be added to the `missing` array unless we do
+ // so here.
+ if items.eh_personality().is_none() {
+ items.missing.push(LangItem::EhPersonality);
+ }
+ if tcx.sess.target.os == "emscripten" && items.eh_catch_typeinfo().is_none() {
+ items.missing.push(LangItem::EhCatchTypeinfo);
+ }
+
+ let crate_items = tcx.hir_crate_items(());
+ for id in crate_items.foreign_items() {
+ let attrs = tcx.hir().attrs(id.hir_id());
+ if let Some((lang_item, _)) = lang_items::extract(attrs) {
+ if let Some(&item) = WEAK_ITEMS_REFS.get(&lang_item) {
+ if items.require(item).is_err() {
+ items.missing.push(item);
+ }
+ } else {
+ let span = tcx.def_span(id.def_id);
+ struct_span_err!(
+ tcx.sess,
+ span,
+ E0264,
+ "unknown external lang item: `{}`",
+ lang_item
+ )
+ .emit();
+ }
+ }
+ }
+
+ verify(tcx, items);
+}
+
+fn verify<'tcx>(tcx: TyCtxt<'tcx>, items: &lang_items::LanguageItems) {
+ // We only need to check for the presence of weak lang items if we're
+ // emitting something that's not an rlib.
+ let needs_check = tcx.sess.crate_types().iter().any(|kind| match *kind {
+ CrateType::Dylib
+ | CrateType::ProcMacro
+ | CrateType::Cdylib
+ | CrateType::Executable
+ | CrateType::Staticlib => true,
+ CrateType::Rlib => false,
+ });
+ if !needs_check {
+ return;
+ }
+
+ let mut missing = FxHashSet::default();
+ for &cnum in tcx.crates(()).iter() {
+ for &item in tcx.missing_lang_items(cnum).iter() {
+ missing.insert(item);
+ }
+ }
+
+ for (name, &item) in WEAK_ITEMS_REFS.iter() {
+ if missing.contains(&item) && required(tcx, item) && items.require(item).is_err() {
+ if item == LangItem::PanicImpl {
+ tcx.sess.err("`#[panic_handler]` function required, but not found");
+ } else if item == LangItem::Oom {
+ if !tcx.features().default_alloc_error_handler {
+ tcx.sess.err("`#[alloc_error_handler]` function required, but not found");
+ tcx.sess.note_without_error("use `#![feature(default_alloc_error_handler)]` for a default error handler");
+ }
+ } else {
+ tcx
+ .sess
+ .diagnostic()
+ .struct_err(&format!("language item required, but not found: `{}`", name))
+ .note(&format!("this can occur when a binary crate with `#![no_std]` is compiled for a target where `{}` is defined in the standard library", name))
+ .help(&format!("you may be able to compile for a target that doesn't need `{}`, specify a target with `--target` or in `.cargo/config`", name))
+ .emit();
+ }
+ }
+ }
+}