diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_middle/src/middle | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_middle/src/middle/codegen_fn_attrs.rs | 146 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/dependency_format.rs | 28 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/exported_symbols.rs | 72 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/lang_items.rs | 61 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/limits.rs | 85 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/mod.rs | 37 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/privacy.rs | 64 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/region.rs | 443 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/resolve_lifetime.rs | 54 | ||||
-rw-r--r-- | compiler/rustc_middle/src/middle/stability.rs | 591 |
10 files changed, 1581 insertions, 0 deletions
diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs new file mode 100644 index 000000000..45d33a165 --- /dev/null +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -0,0 +1,146 @@ +use crate::mir::mono::Linkage; +use rustc_attr::{InlineAttr, InstructionSetAttr, OptimizeAttr}; +use rustc_span::symbol::Symbol; +use rustc_target::spec::SanitizerSet; + +#[derive(Clone, TyEncodable, TyDecodable, HashStable, Debug)] +pub struct CodegenFnAttrs { + pub flags: CodegenFnAttrFlags, + /// Parsed representation of the `#[inline]` attribute + pub inline: InlineAttr, + /// Parsed representation of the `#[optimize]` attribute + pub optimize: OptimizeAttr, + /// The `#[export_name = "..."]` attribute, indicating a custom symbol a + /// function should be exported under + pub export_name: Option<Symbol>, + /// The `#[link_name = "..."]` attribute, indicating a custom symbol an + /// imported function should be imported as. Note that `export_name` + /// probably isn't set when this is set, this is for foreign items while + /// `#[export_name]` is for Rust-defined functions. + pub link_name: Option<Symbol>, + /// The `#[link_ordinal = "..."]` attribute, indicating an ordinal an + /// imported function has in the dynamic library. Note that this must not + /// be set when `link_name` is set. This is for foreign items with the + /// "raw-dylib" kind. + pub link_ordinal: Option<u16>, + /// The `#[target_feature(enable = "...")]` attribute and the enabled + /// features (only enabled features are supported right now). + pub target_features: Vec<Symbol>, + /// The `#[linkage = "..."]` attribute and the value we found. + pub linkage: Option<Linkage>, + /// The `#[link_section = "..."]` attribute, or what executable section this + /// should be placed in. + pub link_section: Option<Symbol>, + /// The `#[no_sanitize(...)]` attribute. Indicates sanitizers for which + /// instrumentation should be disabled inside the annotated function. + pub no_sanitize: SanitizerSet, + /// The `#[instruction_set(set)]` attribute. Indicates if the generated code should + /// be generated against a specific instruction set. Only usable on architectures which allow + /// switching between multiple instruction sets. + pub instruction_set: Option<InstructionSetAttr>, + /// The `#[repr(align(...))]` attribute. Indicates the value of which the function should be + /// aligned to. + pub alignment: Option<u32>, +} + +bitflags! { + #[derive(TyEncodable, TyDecodable, HashStable)] + pub struct CodegenFnAttrFlags: u32 { + /// `#[cold]`: a hint to LLVM that this function, when called, is never on + /// the hot path. + const COLD = 1 << 0; + /// `#[rustc_allocator]`: a hint to LLVM that the pointer returned from this + /// function is never null and the function has no side effects other than allocating. + const ALLOCATOR = 1 << 1; + /// An indicator that function will never unwind. Will become obsolete + /// once C-unwind is fully stabilized. + const NEVER_UNWIND = 1 << 3; + /// `#[naked]`: an indicator to LLVM that no function prologue/epilogue + /// should be generated. + const NAKED = 1 << 4; + /// `#[no_mangle]`: an indicator that the function's name should be the same + /// as its symbol. + const NO_MANGLE = 1 << 5; + /// `#[rustc_std_internal_symbol]`: an indicator that this symbol is a + /// "weird symbol" for the standard library in that it has slightly + /// different linkage, visibility, and reachability rules. + const RUSTC_STD_INTERNAL_SYMBOL = 1 << 6; + /// `#[thread_local]`: indicates a static is actually a thread local + /// piece of memory + const THREAD_LOCAL = 1 << 8; + /// `#[used]`: indicates that LLVM can't eliminate this function (but the + /// linker can!). + const USED = 1 << 9; + /// `#[ffi_returns_twice]`, indicates that an extern function can return + /// multiple times + const FFI_RETURNS_TWICE = 1 << 10; + /// `#[track_caller]`: allow access to the caller location + const TRACK_CALLER = 1 << 11; + /// #[ffi_pure]: applies clang's `pure` attribute to a foreign function + /// declaration. + const FFI_PURE = 1 << 12; + /// #[ffi_const]: applies clang's `const` attribute to a foreign function + /// declaration. + const FFI_CONST = 1 << 13; + /// #[cmse_nonsecure_entry]: with a TrustZone-M extension, declare a + /// function as an entry function from Non-Secure code. + const CMSE_NONSECURE_ENTRY = 1 << 14; + /// `#[no_coverage]`: indicates that the function should be ignored by + /// the MIR `InstrumentCoverage` pass and not added to the coverage map + /// during codegen. + const NO_COVERAGE = 1 << 15; + /// `#[used(linker)]`: indicates that LLVM nor the linker can eliminate this function. + const USED_LINKER = 1 << 16; + /// `#[rustc_deallocator]`: a hint to LLVM that the function only deallocates memory. + const DEALLOCATOR = 1 << 17; + /// `#[rustc_reallocator]`: a hint to LLVM that the function only reallocates memory. + const REALLOCATOR = 1 << 18; + /// `#[rustc_allocator_zeroed]`: a hint to LLVM that the function only allocates zeroed memory. + const ALLOCATOR_ZEROED = 1 << 19; + } +} + +impl CodegenFnAttrs { + pub const EMPTY: &'static Self = &Self::new(); + + pub const fn new() -> CodegenFnAttrs { + CodegenFnAttrs { + flags: CodegenFnAttrFlags::empty(), + inline: InlineAttr::None, + optimize: OptimizeAttr::None, + export_name: None, + link_name: None, + link_ordinal: None, + target_features: vec![], + linkage: None, + link_section: None, + no_sanitize: SanitizerSet::empty(), + instruction_set: None, + alignment: None, + } + } + + /// Returns `true` if `#[inline]` or `#[inline(always)]` is present. + pub fn requests_inline(&self) -> bool { + match self.inline { + InlineAttr::Hint | InlineAttr::Always => true, + InlineAttr::None | InlineAttr::Never => false, + } + } + + /// Returns `true` if it looks like this symbol needs to be exported, for example: + /// + /// * `#[no_mangle]` is present + /// * `#[export_name(...)]` is present + /// * `#[linkage]` is present + pub fn contains_extern_indicator(&self) -> bool { + self.flags.contains(CodegenFnAttrFlags::NO_MANGLE) + || self.export_name.is_some() + || match self.linkage { + // These are private, so make sure we don't try to consider + // them external. + None | Some(Linkage::Internal | Linkage::Private) => false, + Some(_) => true, + } + } +} diff --git a/compiler/rustc_middle/src/middle/dependency_format.rs b/compiler/rustc_middle/src/middle/dependency_format.rs new file mode 100644 index 000000000..e079843bf --- /dev/null +++ b/compiler/rustc_middle/src/middle/dependency_format.rs @@ -0,0 +1,28 @@ +//! Type definitions for learning about the dependency formats of all upstream +//! crates (rlibs/dylibs/oh my). +//! +//! For all the gory details, see the provider of the `dependency_formats` +//! query. + +use rustc_session::config::CrateType; + +/// A list of dependencies for a certain crate type. +/// +/// The length of this vector is the same as the number of external crates used. +/// The value is None if the crate does not need to be linked (it was found +/// statically in another dylib), or Some(kind) if it needs to be linked as +/// `kind` (either static or dynamic). +pub type DependencyList = Vec<Linkage>; + +/// A mapping of all required dependencies for a particular flavor of output. +/// +/// This is local to the tcx, and is generally relevant to one session. +pub type Dependencies = Vec<(CrateType, DependencyList)>; + +#[derive(Copy, Clone, PartialEq, Debug, HashStable, Encodable, Decodable)] +pub enum Linkage { + NotLinked, + IncludedFromDylib, + Static, + Dynamic, +} diff --git a/compiler/rustc_middle/src/middle/exported_symbols.rs b/compiler/rustc_middle/src/middle/exported_symbols.rs new file mode 100644 index 000000000..631fd09ec --- /dev/null +++ b/compiler/rustc_middle/src/middle/exported_symbols.rs @@ -0,0 +1,72 @@ +use crate::ty::subst::SubstsRef; +use crate::ty::{self, Ty, TyCtxt}; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_macros::HashStable; + +/// The SymbolExportLevel of a symbols specifies from which kinds of crates +/// the symbol will be exported. `C` symbols will be exported from any +/// kind of crate, including cdylibs which export very few things. +/// `Rust` will only be exported if the crate produced is a Rust +/// dylib. +#[derive(Eq, PartialEq, Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)] +pub enum SymbolExportLevel { + C, + Rust, +} + +impl SymbolExportLevel { + pub fn is_below_threshold(self, threshold: SymbolExportLevel) -> bool { + threshold == SymbolExportLevel::Rust // export everything from Rust dylibs + || self == SymbolExportLevel::C + } +} + +/// Kind of exported symbols. +#[derive(Eq, PartialEq, Debug, Copy, Clone, Encodable, Decodable, HashStable)] +pub enum SymbolExportKind { + Text, + Data, + Tls, +} + +/// The `SymbolExportInfo` of a symbols specifies symbol-related information +/// that is relevant to code generation and linking. +#[derive(Eq, PartialEq, Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)] +pub struct SymbolExportInfo { + pub level: SymbolExportLevel, + pub kind: SymbolExportKind, + pub used: bool, +} + +#[derive(Eq, PartialEq, Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)] +pub enum ExportedSymbol<'tcx> { + NonGeneric(DefId), + Generic(DefId, SubstsRef<'tcx>), + DropGlue(Ty<'tcx>), + NoDefId(ty::SymbolName<'tcx>), +} + +impl<'tcx> ExportedSymbol<'tcx> { + /// This is the symbol name of an instance if it is instantiated in the + /// local crate. + pub fn symbol_name_for_local_instance(&self, tcx: TyCtxt<'tcx>) -> ty::SymbolName<'tcx> { + match *self { + ExportedSymbol::NonGeneric(def_id) => tcx.symbol_name(ty::Instance::mono(tcx, def_id)), + ExportedSymbol::Generic(def_id, substs) => { + tcx.symbol_name(ty::Instance::new(def_id, substs)) + } + ExportedSymbol::DropGlue(ty) => { + tcx.symbol_name(ty::Instance::resolve_drop_in_place(tcx, ty)) + } + ExportedSymbol::NoDefId(symbol_name) => symbol_name, + } + } +} + +pub fn metadata_symbol_name(tcx: TyCtxt<'_>) -> String { + format!( + "rust_metadata_{}_{:08x}", + tcx.crate_name(LOCAL_CRATE), + tcx.sess.local_stable_crate_id().to_u64(), + ) +} diff --git a/compiler/rustc_middle/src/middle/lang_items.rs b/compiler/rustc_middle/src/middle/lang_items.rs new file mode 100644 index 000000000..cc9706f2d --- /dev/null +++ b/compiler/rustc_middle/src/middle/lang_items.rs @@ -0,0 +1,61 @@ +//! 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::ty::{self, TyCtxt}; + +use rustc_hir::def_id::DefId; +use rustc_hir::LangItem; +use rustc_span::Span; +use rustc_target::spec::PanicStrategy; + +impl<'tcx> TyCtxt<'tcx> { + /// Returns the `DefId` for a given `LangItem`. + /// If not found, fatally aborts compilation. + pub fn require_lang_item(self, lang_item: LangItem, span: Option<Span>) -> DefId { + self.lang_items().require(lang_item).unwrap_or_else(|msg| { + if let Some(span) = span { + self.sess.span_fatal(span, &msg) + } else { + self.sess.fatal(&msg) + } + }) + } + + pub fn fn_trait_kind_from_lang_item(self, id: DefId) -> Option<ty::ClosureKind> { + let items = self.lang_items(); + match Some(id) { + x if x == items.fn_trait() => Some(ty::ClosureKind::Fn), + x if x == items.fn_mut_trait() => Some(ty::ClosureKind::FnMut), + x if x == items.fn_once_trait() => Some(ty::ClosureKind::FnOnce), + _ => None, + } + } + + pub fn is_weak_lang_item(self, item_def_id: DefId) -> bool { + self.lang_items().is_weak_lang_item(item_def_id) + } +} + +/// Returns `true` if the specified `lang_item` must be present for this +/// compilation. +/// +/// Not all lang items are always required for each compilation, particularly in +/// the case of panic=abort. In these situations some lang items are injected by +/// crates and don't actually need to be defined in libstd. +pub fn required(tcx: TyCtxt<'_>, lang_item: LangItem) -> bool { + // If we're not compiling with unwinding, we won't actually need these + // symbols. Other panic runtimes ensure that the relevant symbols are + // available to link things together, but they're never exercised. + match tcx.sess.panic_strategy() { + PanicStrategy::Abort => { + lang_item != LangItem::EhPersonality && lang_item != LangItem::EhCatchTypeinfo + } + PanicStrategy::Unwind => true, + } +} diff --git a/compiler/rustc_middle/src/middle/limits.rs b/compiler/rustc_middle/src/middle/limits.rs new file mode 100644 index 000000000..acced0492 --- /dev/null +++ b/compiler/rustc_middle/src/middle/limits.rs @@ -0,0 +1,85 @@ +//! Registering limits: +//! * recursion_limit, +//! * move_size_limit, +//! * type_length_limit, and +//! * const_eval_limit +//! +//! There are various parts of the compiler that must impose arbitrary limits +//! on how deeply they recurse to prevent stack overflow. Users can override +//! this via an attribute on the crate like `#![recursion_limit="22"]`. This pass +//! just peeks and looks for that attribute. + +use crate::bug; +use crate::ty; +use rustc_ast::Attribute; +use rustc_session::Session; +use rustc_session::{Limit, Limits}; +use rustc_span::symbol::{sym, Symbol}; + +use std::num::IntErrorKind; + +pub fn provide(providers: &mut ty::query::Providers) { + providers.limits = |tcx, ()| Limits { + recursion_limit: get_recursion_limit(tcx.hir().krate_attrs(), tcx.sess), + move_size_limit: get_limit( + tcx.hir().krate_attrs(), + tcx.sess, + sym::move_size_limit, + tcx.sess.opts.unstable_opts.move_size_limit.unwrap_or(0), + ), + type_length_limit: get_limit( + tcx.hir().krate_attrs(), + tcx.sess, + sym::type_length_limit, + 1048576, + ), + const_eval_limit: get_limit( + tcx.hir().krate_attrs(), + tcx.sess, + sym::const_eval_limit, + 1_000_000, + ), + } +} + +pub fn get_recursion_limit(krate_attrs: &[Attribute], sess: &Session) -> Limit { + get_limit(krate_attrs, sess, sym::recursion_limit, 128) +} + +fn get_limit(krate_attrs: &[Attribute], sess: &Session, name: Symbol, default: usize) -> Limit { + for attr in krate_attrs { + if !attr.has_name(name) { + continue; + } + + if let Some(s) = attr.value_str() { + match s.as_str().parse() { + Ok(n) => return Limit::new(n), + Err(e) => { + let mut err = + sess.struct_span_err(attr.span, "`limit` must be a non-negative integer"); + + let value_span = attr + .meta() + .and_then(|meta| meta.name_value_literal_span()) + .unwrap_or(attr.span); + + let error_str = match e.kind() { + IntErrorKind::PosOverflow => "`limit` is too large", + IntErrorKind::Empty => "`limit` must be a non-negative integer", + IntErrorKind::InvalidDigit => "not a valid integer", + IntErrorKind::NegOverflow => { + bug!("`limit` should never negatively overflow") + } + IntErrorKind::Zero => bug!("zero is a valid `limit`"), + kind => bug!("unimplemented IntErrorKind variant: {:?}", kind), + }; + + err.span_label(value_span, error_str); + err.emit(); + } + } + } + } + return Limit::new(default); +} diff --git a/compiler/rustc_middle/src/middle/mod.rs b/compiler/rustc_middle/src/middle/mod.rs new file mode 100644 index 000000000..8dc68b1f5 --- /dev/null +++ b/compiler/rustc_middle/src/middle/mod.rs @@ -0,0 +1,37 @@ +pub mod codegen_fn_attrs; +pub mod dependency_format; +pub mod exported_symbols; +pub mod lang_items; +pub mod lib_features { + use rustc_data_structures::fx::FxHashMap; + use rustc_span::{symbol::Symbol, Span}; + + #[derive(HashStable, Debug)] + pub struct LibFeatures { + /// A map from feature to stabilisation version. + pub stable: FxHashMap<Symbol, (Symbol, Span)>, + pub unstable: FxHashMap<Symbol, Span>, + } + + impl LibFeatures { + pub fn to_vec(&self) -> Vec<(Symbol, Option<Symbol>)> { + let mut all_features: Vec<_> = self + .stable + .iter() + .map(|(f, (s, _))| (*f, Some(*s))) + .chain(self.unstable.iter().map(|(f, _)| (*f, None))) + .collect(); + all_features.sort_unstable_by(|a, b| a.0.as_str().partial_cmp(b.0.as_str()).unwrap()); + all_features + } + } +} +pub mod limits; +pub mod privacy; +pub mod region; +pub mod resolve_lifetime; +pub mod stability; + +pub fn provide(providers: &mut crate::ty::query::Providers) { + limits::provide(providers); +} diff --git a/compiler/rustc_middle/src/middle/privacy.rs b/compiler/rustc_middle/src/middle/privacy.rs new file mode 100644 index 000000000..751c7f464 --- /dev/null +++ b/compiler/rustc_middle/src/middle/privacy.rs @@ -0,0 +1,64 @@ +//! A pass that checks to make sure private fields and methods aren't used +//! outside their scopes. This pass will also generate a set of exported items +//! which are available for use externally when compiled as a library. + +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_macros::HashStable; +use rustc_query_system::ich::StableHashingContext; +use rustc_span::def_id::LocalDefId; +use std::hash::Hash; + +/// Represents the levels of accessibility an item can have. +/// +/// The variants are sorted in ascending order of accessibility. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, HashStable)] +pub enum AccessLevel { + /// Superset of `AccessLevel::Reachable` used to mark impl Trait items. + ReachableFromImplTrait, + /// Exported items + items participating in various kinds of public interfaces, + /// but not directly nameable. For example, if function `fn f() -> T {...}` is + /// public, then type `T` is reachable. Its values can be obtained by other crates + /// even if the type itself is not nameable. + Reachable, + /// Public items + items accessible to other crates with the help of `pub use` re-exports. + Exported, + /// Items accessible to other crates directly, without the help of re-exports. + Public, +} + +/// Holds a map of accessibility levels for reachable HIR nodes. +#[derive(Debug, Clone)] +pub struct AccessLevels<Id = LocalDefId> { + pub map: FxHashMap<Id, AccessLevel>, +} + +impl<Id: Hash + Eq> AccessLevels<Id> { + /// See `AccessLevel::Reachable`. + pub fn is_reachable(&self, id: Id) -> bool { + self.map.get(&id) >= Some(&AccessLevel::Reachable) + } + + /// See `AccessLevel::Exported`. + pub fn is_exported(&self, id: Id) -> bool { + self.map.get(&id) >= Some(&AccessLevel::Exported) + } + + /// See `AccessLevel::Public`. + pub fn is_public(&self, id: Id) -> bool { + self.map.get(&id) >= Some(&AccessLevel::Public) + } +} + +impl<Id> Default for AccessLevels<Id> { + fn default() -> Self { + AccessLevels { map: Default::default() } + } +} + +impl<'a> HashStable<StableHashingContext<'a>> for AccessLevels { + fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { + let AccessLevels { ref map } = *self; + map.hash_stable(hcx, hasher); + } +} diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs new file mode 100644 index 000000000..c886175c6 --- /dev/null +++ b/compiler/rustc_middle/src/middle/region.rs @@ -0,0 +1,443 @@ +//! This file declares the `ScopeTree` type, which describes +//! the parent links in the region hierarchy. +//! +//! For more information about how MIR-based region-checking works, +//! see the [rustc dev guide]. +//! +//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/borrow_check.html + +use crate::ty::TyCtxt; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_hir as hir; +use rustc_hir::Node; +use rustc_macros::HashStable; +use rustc_query_system::ich::StableHashingContext; +use rustc_span::{Span, DUMMY_SP}; + +use std::fmt; +use std::ops::Deref; + +/// Represents a statically-describable scope that can be used to +/// bound the lifetime/region for values. +/// +/// `Node(node_id)`: Any AST node that has any scope at all has the +/// `Node(node_id)` scope. Other variants represent special cases not +/// immediately derivable from the abstract syntax tree structure. +/// +/// `DestructionScope(node_id)` represents the scope of destructors +/// implicitly-attached to `node_id` that run immediately after the +/// expression for `node_id` itself. Not every AST node carries a +/// `DestructionScope`, but those that are `terminating_scopes` do; +/// see discussion with `ScopeTree`. +/// +/// `Remainder { block, statement_index }` represents +/// the scope of user code running immediately after the initializer +/// expression for the indexed statement, until the end of the block. +/// +/// So: the following code can be broken down into the scopes beneath: +/// +/// ```text +/// let a = f().g( 'b: { let x = d(); let y = d(); x.h(y) } ) ; +/// +/// +-+ (D12.) +/// +-+ (D11.) +/// +---------+ (R10.) +/// +-+ (D9.) +/// +----------+ (M8.) +/// +----------------------+ (R7.) +/// +-+ (D6.) +/// +----------+ (M5.) +/// +-----------------------------------+ (M4.) +/// +--------------------------------------------------+ (M3.) +/// +--+ (M2.) +/// +-----------------------------------------------------------+ (M1.) +/// +/// (M1.): Node scope of the whole `let a = ...;` statement. +/// (M2.): Node scope of the `f()` expression. +/// (M3.): Node scope of the `f().g(..)` expression. +/// (M4.): Node scope of the block labeled `'b:`. +/// (M5.): Node scope of the `let x = d();` statement +/// (D6.): DestructionScope for temporaries created during M5. +/// (R7.): Remainder scope for block `'b:`, stmt 0 (let x = ...). +/// (M8.): Node scope of the `let y = d();` statement. +/// (D9.): DestructionScope for temporaries created during M8. +/// (R10.): Remainder scope for block `'b:`, stmt 1 (let y = ...). +/// (D11.): DestructionScope for temporaries and bindings from block `'b:`. +/// (D12.): DestructionScope for temporaries created during M1 (e.g., f()). +/// ``` +/// +/// Note that while the above picture shows the destruction scopes +/// as following their corresponding node scopes, in the internal +/// data structures of the compiler the destruction scopes are +/// represented as enclosing parents. This is sound because we use the +/// enclosing parent relationship just to ensure that referenced +/// values live long enough; phrased another way, the starting point +/// of each range is not really the important thing in the above +/// picture, but rather the ending point. +// +// FIXME(pnkfelix): this currently derives `PartialOrd` and `Ord` to +// placate the same deriving in `ty::FreeRegion`, but we may want to +// actually attach a more meaningful ordering to scopes than the one +// generated via deriving here. +#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Copy, TyEncodable, TyDecodable)] +#[derive(HashStable)] +pub struct Scope { + pub id: hir::ItemLocalId, + pub data: ScopeData, +} + +impl fmt::Debug for Scope { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.data { + ScopeData::Node => write!(fmt, "Node({:?})", self.id), + ScopeData::CallSite => write!(fmt, "CallSite({:?})", self.id), + ScopeData::Arguments => write!(fmt, "Arguments({:?})", self.id), + ScopeData::Destruction => write!(fmt, "Destruction({:?})", self.id), + ScopeData::IfThen => write!(fmt, "IfThen({:?})", self.id), + ScopeData::Remainder(fsi) => write!( + fmt, + "Remainder {{ block: {:?}, first_statement_index: {}}}", + self.id, + fsi.as_u32(), + ), + } + } +} + +#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, TyEncodable, TyDecodable)] +#[derive(HashStable)] +pub enum ScopeData { + Node, + + /// Scope of the call-site for a function or closure + /// (outlives the arguments as well as the body). + CallSite, + + /// Scope of arguments passed to a function or closure + /// (they outlive its body). + Arguments, + + /// Scope of destructors for temporaries of node-id. + Destruction, + + /// Scope of the condition and then block of an if expression + /// Used for variables introduced in an if-let expression. + IfThen, + + /// Scope following a `let id = expr;` binding in a block. + Remainder(FirstStatementIndex), +} + +rustc_index::newtype_index! { + /// Represents a subscope of `block` for a binding that is introduced + /// by `block.stmts[first_statement_index]`. Such subscopes represent + /// a suffix of the block. Note that each subscope does not include + /// the initializer expression, if any, for the statement indexed by + /// `first_statement_index`. + /// + /// For example, given `{ let (a, b) = EXPR_1; let c = EXPR_2; ... }`: + /// + /// * The subscope with `first_statement_index == 0` is scope of both + /// `a` and `b`; it does not include EXPR_1, but does include + /// everything after that first `let`. (If you want a scope that + /// includes EXPR_1 as well, then do not use `Scope::Remainder`, + /// but instead another `Scope` that encompasses the whole block, + /// e.g., `Scope::Node`. + /// + /// * The subscope with `first_statement_index == 1` is scope of `c`, + /// and thus does not include EXPR_2, but covers the `...`. + pub struct FirstStatementIndex { + derive [HashStable] + } +} + +// compilation error if size of `ScopeData` is not the same as a `u32` +static_assert_size!(ScopeData, 4); + +impl Scope { + /// Returns an item-local ID associated with this scope. + /// + /// N.B., likely to be replaced as API is refined; e.g., pnkfelix + /// anticipates `fn entry_node_id` and `fn each_exit_node_id`. + pub fn item_local_id(&self) -> hir::ItemLocalId { + self.id + } + + pub fn hir_id(&self, scope_tree: &ScopeTree) -> Option<hir::HirId> { + scope_tree + .root_body + .map(|hir_id| hir::HirId { owner: hir_id.owner, local_id: self.item_local_id() }) + } + + /// Returns the span of this `Scope`. Note that in general the + /// returned span may not correspond to the span of any `NodeId` in + /// the AST. + pub fn span(&self, tcx: TyCtxt<'_>, scope_tree: &ScopeTree) -> Span { + let Some(hir_id) = self.hir_id(scope_tree) else { + return DUMMY_SP; + }; + let span = tcx.hir().span(hir_id); + if let ScopeData::Remainder(first_statement_index) = self.data { + if let Node::Block(ref blk) = tcx.hir().get(hir_id) { + // Want span for scope starting after the + // indexed statement and ending at end of + // `blk`; reuse span of `blk` and shift `lo` + // forward to end of indexed statement. + // + // (This is the special case alluded to in the + // doc-comment for this method) + + let stmt_span = blk.stmts[first_statement_index.index()].span; + + // To avoid issues with macro-generated spans, the span + // of the statement must be nested in that of the block. + if span.lo() <= stmt_span.lo() && stmt_span.lo() <= span.hi() { + return span.with_lo(stmt_span.lo()); + } + } + } + span + } +} + +pub type ScopeDepth = u32; + +/// The region scope tree encodes information about region relationships. +#[derive(TyEncodable, TyDecodable, Default, Debug)] +pub struct ScopeTree { + /// If not empty, this body is the root of this region hierarchy. + pub root_body: Option<hir::HirId>, + + /// Maps from a scope ID to the enclosing scope id; + /// this is usually corresponding to the lexical nesting, though + /// in the case of closures the parent scope is the innermost + /// conditional expression or repeating block. (Note that the + /// enclosing scope ID for the block associated with a closure is + /// the closure itself.) + pub parent_map: FxIndexMap<Scope, (Scope, ScopeDepth)>, + + /// Maps from a variable or binding ID to the block in which that + /// variable is declared. + var_map: FxIndexMap<hir::ItemLocalId, Scope>, + + /// Maps from a `NodeId` to the associated destruction scope (if any). + destruction_scopes: FxIndexMap<hir::ItemLocalId, Scope>, + + /// Identifies expressions which, if captured into a temporary, ought to + /// have a temporary whose lifetime extends to the end of the enclosing *block*, + /// and not the enclosing *statement*. Expressions that are not present in this + /// table are not rvalue candidates. The set of rvalue candidates is computed + /// during type check based on a traversal of the AST. + pub rvalue_candidates: FxHashMap<hir::HirId, RvalueCandidateType>, + + /// If there are any `yield` nested within a scope, this map + /// stores the `Span` of the last one and its index in the + /// postorder of the Visitor traversal on the HIR. + /// + /// HIR Visitor postorder indexes might seem like a peculiar + /// thing to care about. but it turns out that HIR bindings + /// and the temporary results of HIR expressions are never + /// storage-live at the end of HIR nodes with postorder indexes + /// lower than theirs, and therefore don't need to be suspended + /// at yield-points at these indexes. + /// + /// For an example, suppose we have some code such as: + /// ```rust,ignore (example) + /// foo(f(), yield y, bar(g())) + /// ``` + /// + /// With the HIR tree (calls numbered for expository purposes) + /// + /// ```text + /// Call#0(foo, [Call#1(f), Yield(y), Call#2(bar, Call#3(g))]) + /// ``` + /// + /// Obviously, the result of `f()` was created before the yield + /// (and therefore needs to be kept valid over the yield) while + /// the result of `g()` occurs after the yield (and therefore + /// doesn't). If we want to infer that, we can look at the + /// postorder traversal: + /// ```plain,ignore + /// `foo` `f` Call#1 `y` Yield `bar` `g` Call#3 Call#2 Call#0 + /// ``` + /// + /// In which we can easily see that `Call#1` occurs before the yield, + /// and `Call#3` after it. + /// + /// To see that this method works, consider: + /// + /// Let `D` be our binding/temporary and `U` be our other HIR node, with + /// `HIR-postorder(U) < HIR-postorder(D)`. Suppose, as in our example, + /// U is the yield and D is one of the calls. + /// Let's show that `D` is storage-dead at `U`. + /// + /// Remember that storage-live/storage-dead refers to the state of + /// the *storage*, and does not consider moves/drop flags. + /// + /// Then: + /// + /// 1. From the ordering guarantee of HIR visitors (see + /// `rustc_hir::intravisit`), `D` does not dominate `U`. + /// + /// 2. Therefore, `D` is *potentially* storage-dead at `U` (because + /// we might visit `U` without ever getting to `D`). + /// + /// 3. However, we guarantee that at each HIR point, each + /// binding/temporary is always either always storage-live + /// or always storage-dead. This is what is being guaranteed + /// by `terminating_scopes` including all blocks where the + /// count of executions is not guaranteed. + /// + /// 4. By `2.` and `3.`, `D` is *statically* storage-dead at `U`, + /// QED. + /// + /// This property ought to not on (3) in an essential way -- it + /// is probably still correct even if we have "unrestricted" terminating + /// scopes. However, why use the complicated proof when a simple one + /// works? + /// + /// A subtle thing: `box` expressions, such as `box (&x, yield 2, &y)`. It + /// might seem that a `box` expression creates a `Box<T>` temporary + /// when it *starts* executing, at `HIR-preorder(BOX-EXPR)`. That might + /// be true in the MIR desugaring, but it is not important in the semantics. + /// + /// The reason is that semantically, until the `box` expression returns, + /// the values are still owned by their containing expressions. So + /// we'll see that `&x`. + pub yield_in_scope: FxHashMap<Scope, Vec<YieldData>>, + + /// The number of visit_expr and visit_pat calls done in the body. + /// Used to sanity check visit_expr/visit_pat call count when + /// calculating generator interiors. + pub body_expr_count: FxHashMap<hir::BodyId, usize>, +} + +/// Identifies the reason that a given expression is an rvalue candidate +/// (see the `rvalue_candidates` field for more information what rvalue +/// candidates in general). In constants, the `lifetime` field is None +/// to indicate that certain expressions escape into 'static and +/// should have no local cleanup scope. +#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)] +pub enum RvalueCandidateType { + Borrow { target: hir::ItemLocalId, lifetime: Option<Scope> }, + Pattern { target: hir::ItemLocalId, lifetime: Option<Scope> }, +} + +#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)] +pub struct YieldData { + /// The `Span` of the yield. + pub span: Span, + /// The number of expressions and patterns appearing before the `yield` in the body, plus one. + pub expr_and_pat_count: usize, + pub source: hir::YieldSource, +} + +impl ScopeTree { + pub fn record_scope_parent(&mut self, child: Scope, parent: Option<(Scope, ScopeDepth)>) { + debug!("{:?}.parent = {:?}", child, parent); + + if let Some(p) = parent { + let prev = self.parent_map.insert(child, p); + assert!(prev.is_none()); + } + + // Record the destruction scopes for later so we can query them. + if let ScopeData::Destruction = child.data { + self.destruction_scopes.insert(child.item_local_id(), child); + } + } + + pub fn opt_destruction_scope(&self, n: hir::ItemLocalId) -> Option<Scope> { + self.destruction_scopes.get(&n).cloned() + } + + pub fn record_var_scope(&mut self, var: hir::ItemLocalId, lifetime: Scope) { + debug!("record_var_scope(sub={:?}, sup={:?})", var, lifetime); + assert!(var != lifetime.item_local_id()); + self.var_map.insert(var, lifetime); + } + + pub fn record_rvalue_candidate( + &mut self, + var: hir::HirId, + candidate_type: RvalueCandidateType, + ) { + debug!("record_rvalue_candidate(var={var:?}, type={candidate_type:?})"); + match &candidate_type { + RvalueCandidateType::Borrow { lifetime: Some(lifetime), .. } + | RvalueCandidateType::Pattern { lifetime: Some(lifetime), .. } => { + assert!(var.local_id != lifetime.item_local_id()) + } + _ => {} + } + self.rvalue_candidates.insert(var, candidate_type); + } + + /// Returns the narrowest scope that encloses `id`, if any. + pub fn opt_encl_scope(&self, id: Scope) -> Option<Scope> { + self.parent_map.get(&id).cloned().map(|(p, _)| p) + } + + /// Returns the lifetime of the local variable `var_id`, if any. + pub fn var_scope(&self, var_id: hir::ItemLocalId) -> Option<Scope> { + self.var_map.get(&var_id).cloned() + } + + /// Returns `true` if `subscope` is equal to or is lexically nested inside `superscope`, and + /// `false` otherwise. + /// + /// Used by clippy. + pub fn is_subscope_of(&self, subscope: Scope, superscope: Scope) -> bool { + let mut s = subscope; + debug!("is_subscope_of({:?}, {:?})", subscope, superscope); + while superscope != s { + match self.opt_encl_scope(s) { + None => { + debug!("is_subscope_of({:?}, {:?}, s={:?})=false", subscope, superscope, s); + return false; + } + Some(scope) => s = scope, + } + } + + debug!("is_subscope_of({:?}, {:?})=true", subscope, superscope); + + true + } + + /// Checks whether the given scope contains a `yield`. If so, + /// returns `Some(YieldData)`. If not, returns `None`. + pub fn yield_in_scope(&self, scope: Scope) -> Option<&[YieldData]> { + self.yield_in_scope.get(&scope).map(Deref::deref) + } + + /// Gives the number of expressions visited in a body. + /// Used to sanity check visit_expr call count when + /// calculating generator interiors. + pub fn body_expr_count(&self, body_id: hir::BodyId) -> Option<usize> { + self.body_expr_count.get(&body_id).copied() + } +} + +impl<'a> HashStable<StableHashingContext<'a>> for ScopeTree { + fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { + let ScopeTree { + root_body, + ref body_expr_count, + ref parent_map, + ref var_map, + ref destruction_scopes, + ref rvalue_candidates, + ref yield_in_scope, + } = *self; + + root_body.hash_stable(hcx, hasher); + body_expr_count.hash_stable(hcx, hasher); + parent_map.hash_stable(hcx, hasher); + var_map.hash_stable(hcx, hasher); + destruction_scopes.hash_stable(hcx, hasher); + rvalue_candidates.hash_stable(hcx, hasher); + yield_in_scope.hash_stable(hcx, hasher); + } +} diff --git a/compiler/rustc_middle/src/middle/resolve_lifetime.rs b/compiler/rustc_middle/src/middle/resolve_lifetime.rs new file mode 100644 index 000000000..9b2f44567 --- /dev/null +++ b/compiler/rustc_middle/src/middle/resolve_lifetime.rs @@ -0,0 +1,54 @@ +//! Name resolution for lifetimes: type declarations. + +use crate::ty; + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::ItemLocalId; +use rustc_macros::HashStable; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable, Debug, HashStable)] +pub enum Region { + Static, + EarlyBound(/* index */ u32, /* lifetime decl */ DefId), + LateBound(ty::DebruijnIndex, /* late-bound index */ u32, /* lifetime decl */ DefId), + Free(DefId, /* lifetime decl */ DefId), +} + +/// A set containing, at most, one known element. +/// If two distinct values are inserted into a set, then it +/// becomes `Many`, which can be used to detect ambiguities. +#[derive(Copy, Clone, PartialEq, Eq, TyEncodable, TyDecodable, Debug, HashStable)] +pub enum Set1<T> { + Empty, + One(T), + Many, +} + +impl<T: PartialEq> Set1<T> { + pub fn insert(&mut self, value: T) { + *self = match self { + Set1::Empty => Set1::One(value), + Set1::One(old) if *old == value => return, + _ => Set1::Many, + }; + } +} + +pub type ObjectLifetimeDefault = Set1<Region>; + +/// Maps the id of each lifetime reference to the lifetime decl +/// that it corresponds to. +#[derive(Default, HashStable, Debug)] +pub struct ResolveLifetimes { + /// Maps from every use of a named (not anonymous) lifetime to a + /// `Region` describing how that region is bound + pub defs: FxHashMap<LocalDefId, FxHashMap<ItemLocalId, Region>>, + + /// Set of lifetime def ids that are late-bound; a region can + /// be late-bound if (a) it does NOT appear in a where-clause and + /// (b) it DOES appear in the arguments. + pub late_bound: FxHashMap<LocalDefId, FxHashSet<LocalDefId>>, + + pub late_bound_vars: FxHashMap<LocalDefId, FxHashMap<ItemLocalId, Vec<ty::BoundVariableKind>>>, +} diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs new file mode 100644 index 000000000..414912dd0 --- /dev/null +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -0,0 +1,591 @@ +//! A pass that annotates every item and method with its stability level, +//! propagating default levels lexically from parent to children ast nodes. + +pub use self::StabilityLevel::*; + +use crate::ty::{self, DefIdTree, TyCtxt}; +use rustc_ast::NodeId; +use rustc_attr::{self as attr, ConstStability, Deprecation, Stability}; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_feature::GateIssue; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, HirId}; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE}; +use rustc_session::lint::{BuiltinLintDiagnostics, Level, Lint, LintBuffer}; +use rustc_session::parse::feature_err_issue; +use rustc_session::Session; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::Span; +use std::num::NonZeroU32; + +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum StabilityLevel { + Unstable, + Stable, +} + +/// An entry in the `depr_map`. +#[derive(Copy, Clone, HashStable, Debug, Encodable, Decodable)] +pub struct DeprecationEntry { + /// The metadata of the attribute associated with this entry. + pub attr: Deprecation, + /// The `DefId` where the attr was originally attached. `None` for non-local + /// `DefId`'s. + origin: Option<LocalDefId>, +} + +impl DeprecationEntry { + pub fn local(attr: Deprecation, def_id: LocalDefId) -> DeprecationEntry { + DeprecationEntry { attr, origin: Some(def_id) } + } + + pub fn external(attr: Deprecation) -> DeprecationEntry { + DeprecationEntry { attr, origin: None } + } + + pub fn same_origin(&self, other: &DeprecationEntry) -> bool { + match (self.origin, other.origin) { + (Some(o1), Some(o2)) => o1 == o2, + _ => false, + } + } +} + +/// A stability index, giving the stability level for items and methods. +#[derive(HashStable, Debug)] +pub struct Index { + /// This is mostly a cache, except the stabilities of local items + /// are filled by the annotator. + pub stab_map: FxHashMap<LocalDefId, Stability>, + pub const_stab_map: FxHashMap<LocalDefId, ConstStability>, + pub depr_map: FxHashMap<LocalDefId, DeprecationEntry>, + /// Mapping from feature name to feature name based on the `implied_by` field of `#[unstable]` + /// attributes. If a `#[unstable(feature = "implier", implied_by = "impliee")]` attribute + /// exists, then this map will have a `impliee -> implier` entry. + /// + /// This mapping is necessary unless both the `#[stable]` and `#[unstable]` attributes should + /// specify their implications (both `implies` and `implied_by`). If only one of the two + /// attributes do (as in the current implementation, `implied_by` in `#[unstable]`), then this + /// mapping is necessary for diagnostics. When a "unnecessary feature attribute" error is + /// reported, only the `#[stable]` attribute information is available, so the map is necessary + /// to know that the feature implies another feature. If it were reversed, and the `#[stable]` + /// attribute had an `implies` meta item, then a map would be necessary when avoiding a "use of + /// unstable feature" error for a feature that was implied. + pub implications: FxHashMap<Symbol, Symbol>, +} + +impl Index { + pub fn local_stability(&self, def_id: LocalDefId) -> Option<Stability> { + self.stab_map.get(&def_id).copied() + } + + pub fn local_const_stability(&self, def_id: LocalDefId) -> Option<ConstStability> { + self.const_stab_map.get(&def_id).copied() + } + + pub fn local_deprecation_entry(&self, def_id: LocalDefId) -> Option<DeprecationEntry> { + self.depr_map.get(&def_id).cloned() + } +} + +pub fn report_unstable( + sess: &Session, + feature: Symbol, + reason: Option<Symbol>, + issue: Option<NonZeroU32>, + suggestion: Option<(Span, String, String, Applicability)>, + is_soft: bool, + span: Span, + soft_handler: impl FnOnce(&'static Lint, Span, &str), +) { + let msg = match reason { + Some(r) => format!("use of unstable library feature '{}': {}", feature, r), + None => format!("use of unstable library feature '{}'", &feature), + }; + + if is_soft { + soft_handler(SOFT_UNSTABLE, span, &msg) + } else { + let mut err = + feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), &msg); + if let Some((inner_types, ref msg, sugg, applicability)) = suggestion { + err.span_suggestion(inner_types, msg, sugg, applicability); + } + err.emit(); + } +} + +/// Checks whether an item marked with `deprecated(since="X")` is currently +/// deprecated (i.e., whether X is not greater than the current rustc version). +pub fn deprecation_in_effect(depr: &Deprecation) -> bool { + let is_since_rustc_version = depr.is_since_rustc_version; + let since = depr.since.as_ref().map(Symbol::as_str); + + fn parse_version(ver: &str) -> Vec<u32> { + // We ignore non-integer components of the version (e.g., "nightly"). + ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect() + } + + if !is_since_rustc_version { + // The `since` field doesn't have semantic purpose without `#![staged_api]`. + return true; + } + + if let Some(since) = since { + if since == "TBD" { + return false; + } + + if let Some(rustc) = option_env!("CFG_RELEASE") { + let since: Vec<u32> = parse_version(&since); + let rustc: Vec<u32> = parse_version(rustc); + // We simply treat invalid `since` attributes as relating to a previous + // Rust version, thus always displaying the warning. + if since.len() != 3 { + return true; + } + return since <= rustc; + } + }; + + // Assume deprecation is in effect if "since" field is missing + // or if we can't determine the current Rust version. + true +} + +pub fn deprecation_suggestion( + diag: &mut Diagnostic, + kind: &str, + suggestion: Option<Symbol>, + span: Span, +) { + if let Some(suggestion) = suggestion { + diag.span_suggestion_verbose( + span, + &format!("replace the use of the deprecated {}", kind), + suggestion, + Applicability::MachineApplicable, + ); + } +} + +fn deprecation_lint(is_in_effect: bool) -> &'static Lint { + if is_in_effect { DEPRECATED } else { DEPRECATED_IN_FUTURE } +} + +fn deprecation_message( + is_in_effect: bool, + since: Option<Symbol>, + note: Option<Symbol>, + kind: &str, + path: &str, +) -> String { + let message = if is_in_effect { + format!("use of deprecated {} `{}`", kind, path) + } else { + let since = since.as_ref().map(Symbol::as_str); + + if since == Some("TBD") { + format!("use of {} `{}` that will be deprecated in a future Rust version", kind, path) + } else { + format!( + "use of {} `{}` that will be deprecated in future version {}", + kind, + path, + since.unwrap() + ) + } + }; + + match note { + Some(reason) => format!("{}: {}", message, reason), + None => message, + } +} + +pub fn deprecation_message_and_lint( + depr: &Deprecation, + kind: &str, + path: &str, +) -> (String, &'static Lint) { + let is_in_effect = deprecation_in_effect(depr); + ( + deprecation_message(is_in_effect, depr.since, depr.note, kind, path), + deprecation_lint(is_in_effect), + ) +} + +pub fn early_report_deprecation<'a>( + lint_buffer: &'a mut LintBuffer, + message: &str, + suggestion: Option<Symbol>, + lint: &'static Lint, + span: Span, + node_id: NodeId, +) { + if span.in_derive_expansion() { + return; + } + + let diag = BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span); + lint_buffer.buffer_lint_with_diagnostic(lint, node_id, span, message, diag); +} + +fn late_report_deprecation( + tcx: TyCtxt<'_>, + message: &str, + suggestion: Option<Symbol>, + lint: &'static Lint, + span: Span, + method_span: Option<Span>, + hir_id: HirId, + def_id: DefId, +) { + if span.in_derive_expansion() { + return; + } + let method_span = method_span.unwrap_or(span); + tcx.struct_span_lint_hir(lint, hir_id, method_span, |lint| { + let mut diag = lint.build(message); + if let hir::Node::Expr(_) = tcx.hir().get(hir_id) { + let kind = tcx.def_kind(def_id).descr(def_id); + deprecation_suggestion(&mut diag, kind, suggestion, method_span); + } + diag.emit(); + }); +} + +/// Result of `TyCtxt::eval_stability`. +pub enum EvalResult { + /// We can use the item because it is stable or we provided the + /// corresponding feature gate. + Allow, + /// We cannot use the item because it is unstable and we did not provide the + /// corresponding feature gate. + Deny { + feature: Symbol, + reason: Option<Symbol>, + issue: Option<NonZeroU32>, + suggestion: Option<(Span, String, String, Applicability)>, + is_soft: bool, + }, + /// The item does not have the `#[stable]` or `#[unstable]` marker assigned. + Unmarked, +} + +// See issue #38412. +fn skip_stability_check_due_to_privacy(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + if tcx.def_kind(def_id) == DefKind::TyParam { + // Have no visibility, considered public for the purpose of this check. + return false; + } + match tcx.visibility(def_id) { + // Must check stability for `pub` items. + ty::Visibility::Public => false, + + // These are not visible outside crate; therefore + // stability markers are irrelevant, if even present. + ty::Visibility::Restricted(..) | ty::Visibility::Invisible => true, + } +} + +// See issue #83250. +fn suggestion_for_allocator_api( + tcx: TyCtxt<'_>, + def_id: DefId, + span: Span, + feature: Symbol, +) -> Option<(Span, String, String, Applicability)> { + if feature == sym::allocator_api { + if let Some(trait_) = tcx.opt_parent(def_id) { + if tcx.is_diagnostic_item(sym::Vec, trait_) { + let sm = tcx.sess.parse_sess.source_map(); + let inner_types = sm.span_extend_to_prev_char(span, '<', true); + if let Ok(snippet) = sm.span_to_snippet(inner_types) { + return Some(( + inner_types, + "consider wrapping the inner types in tuple".to_string(), + format!("({})", snippet), + Applicability::MaybeIncorrect, + )); + } + } + } + } + None +} + +/// An override option for eval_stability. +pub enum AllowUnstable { + /// Don't emit an unstable error for the item + Yes, + /// Handle the item normally + No, +} + +impl<'tcx> TyCtxt<'tcx> { + /// Evaluates the stability of an item. + /// + /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding + /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending + /// unstable feature otherwise. + /// + /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been + /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to + /// `id`. + pub fn eval_stability( + self, + def_id: DefId, + id: Option<HirId>, + span: Span, + method_span: Option<Span>, + ) -> EvalResult { + self.eval_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No) + } + + /// Evaluates the stability of an item. + /// + /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding + /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending + /// unstable feature otherwise. + /// + /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been + /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to + /// `id`. + /// + /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally. + pub fn eval_stability_allow_unstable( + self, + def_id: DefId, + id: Option<HirId>, + span: Span, + method_span: Option<Span>, + allow_unstable: AllowUnstable, + ) -> EvalResult { + // Deprecated attributes apply in-crate and cross-crate. + if let Some(id) = id { + if let Some(depr_entry) = self.lookup_deprecation_entry(def_id) { + let parent_def_id = self.hir().get_parent_item(id); + let skip = self + .lookup_deprecation_entry(parent_def_id.to_def_id()) + .map_or(false, |parent_depr| parent_depr.same_origin(&depr_entry)); + + // #[deprecated] doesn't emit a notice if we're not on the + // topmost deprecation. For example, if a struct is deprecated, + // the use of a field won't be linted. + // + // With #![staged_api], we want to emit down the whole + // hierarchy. + let depr_attr = &depr_entry.attr; + if !skip || depr_attr.is_since_rustc_version { + // Calculating message for lint involves calling `self.def_path_str`. + // Which by default to calculate visible path will invoke expensive `visible_parent_map` query. + // So we skip message calculation altogether, if lint is allowed. + let is_in_effect = deprecation_in_effect(depr_attr); + let lint = deprecation_lint(is_in_effect); + if self.lint_level_at_node(lint, id).0 != Level::Allow { + let def_path = with_no_trimmed_paths!(self.def_path_str(def_id)); + let def_kind = self.def_kind(def_id).descr(def_id); + + late_report_deprecation( + self, + &deprecation_message( + is_in_effect, + depr_attr.since, + depr_attr.note, + def_kind, + &def_path, + ), + depr_attr.suggestion, + lint, + span, + method_span, + id, + def_id, + ); + } + } + }; + } + + let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some(); + if !is_staged_api { + return EvalResult::Allow; + } + + let stability = self.lookup_stability(def_id); + debug!( + "stability: \ + inspecting def_id={:?} span={:?} of stability={:?}", + def_id, span, stability + ); + + // Only the cross-crate scenario matters when checking unstable APIs + let cross_crate = !def_id.is_local(); + if !cross_crate { + return EvalResult::Allow; + } + + // Issue #38412: private items lack stability markers. + if skip_stability_check_due_to_privacy(self, def_id) { + return EvalResult::Allow; + } + + match stability { + Some(Stability { + level: attr::Unstable { reason, issue, is_soft, implied_by }, + feature, + .. + }) => { + if span.allows_unstable(feature) { + debug!("stability: skipping span={:?} since it is internal", span); + return EvalResult::Allow; + } + if self.features().active(feature) { + return EvalResult::Allow; + } + + // If this item was previously part of a now-stabilized feature which is still + // active (i.e. the user hasn't removed the attribute for the stabilized feature + // yet) then allow use of this item. + if let Some(implied_by) = implied_by && self.features().active(implied_by) { + return EvalResult::Allow; + } + + // When we're compiling the compiler itself we may pull in + // crates from crates.io, but those crates may depend on other + // crates also pulled in from crates.io. We want to ideally be + // able to compile everything without requiring upstream + // modifications, so in the case that this looks like a + // `rustc_private` crate (e.g., a compiler crate) and we also have + // the `-Z force-unstable-if-unmarked` flag present (we're + // compiling a compiler crate), then let this missing feature + // annotation slide. + if feature == sym::rustc_private && issue == NonZeroU32::new(27812) { + if self.sess.opts.unstable_opts.force_unstable_if_unmarked { + return EvalResult::Allow; + } + } + + if matches!(allow_unstable, AllowUnstable::Yes) { + return EvalResult::Allow; + } + + let suggestion = suggestion_for_allocator_api(self, def_id, span, feature); + EvalResult::Deny { + feature, + reason: reason.to_opt_reason(), + issue, + suggestion, + is_soft, + } + } + Some(_) => { + // Stable APIs are always ok to call and deprecated APIs are + // handled by the lint emitting logic above. + EvalResult::Allow + } + None => EvalResult::Unmarked, + } + } + + /// Checks if an item is stable or error out. + /// + /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not + /// exist, emits an error. + /// + /// This function will also check if the item is deprecated. + /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted. + /// + /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature. + pub fn check_stability( + self, + def_id: DefId, + id: Option<HirId>, + span: Span, + method_span: Option<Span>, + ) -> bool { + self.check_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No) + } + + /// Checks if an item is stable or error out. + /// + /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not + /// exist, emits an error. + /// + /// This function will also check if the item is deprecated. + /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted. + /// + /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally. + /// + /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature. + pub fn check_stability_allow_unstable( + self, + def_id: DefId, + id: Option<HirId>, + span: Span, + method_span: Option<Span>, + allow_unstable: AllowUnstable, + ) -> bool { + self.check_optional_stability( + def_id, + id, + span, + method_span, + allow_unstable, + |span, def_id| { + // The API could be uncallable for other reasons, for example when a private module + // was referenced. + self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id)); + }, + ) + } + + /// Like `check_stability`, except that we permit items to have custom behaviour for + /// missing stability attributes (not necessarily just emit a `bug!`). This is necessary + /// for default generic parameters, which only have stability attributes if they were + /// added after the type on which they're defined. + /// + /// Returns `true` if item is allowed aka, stable or unstable under an enabled feature. + pub fn check_optional_stability( + self, + def_id: DefId, + id: Option<HirId>, + span: Span, + method_span: Option<Span>, + allow_unstable: AllowUnstable, + unmarked: impl FnOnce(Span, DefId), + ) -> bool { + let soft_handler = |lint, span, msg: &_| { + self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| { + lint.build(msg).emit(); + }) + }; + let eval_result = + self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable); + let is_allowed = matches!(eval_result, EvalResult::Allow); + match eval_result { + EvalResult::Allow => {} + EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => report_unstable( + self.sess, + feature, + reason, + issue, + suggestion, + is_soft, + span, + soft_handler, + ), + EvalResult::Unmarked => unmarked(span, def_id), + } + + is_allowed + } + + pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> { + self.lookup_deprecation_entry(id).map(|depr| depr.attr) + } +} |