diff options
104 files changed, 2775 insertions, 1723 deletions
diff --git a/.gitmodules b/.gitmodules index c8504937806..4011a6fa6b9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,7 +28,7 @@ [submodule "src/llvm-project"] path = src/llvm-project url = https://github.com/rust-lang/llvm-project.git - branch = rustc/15.0-2022-08-09 + branch = rustc/15.0-2022-12-07 [submodule "src/doc/embedded-book"] path = src/doc/embedded-book url = https://github.com/rust-embedded/book.git diff --git a/Cargo.lock b/Cargo.lock index 150a70341f5..844cf99b468 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4348,6 +4348,7 @@ dependencies = [ "rustc_span", "rustc_target", "smallvec", + "termize", "tracing", "winapi", ] diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 07558ca075d..4fa18907fcd 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -259,6 +259,8 @@ enum ImplTraitContext { }, /// Impl trait in type aliases. TypeAliasesOpaqueTy, + /// `impl Trait` is unstably accepted in this position. + FeatureGated(ImplTraitPosition, Symbol), /// `impl Trait` is not accepted in this position. Disallowed(ImplTraitPosition), } @@ -1372,17 +1374,15 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } path } - ImplTraitContext::Disallowed( - position @ (ImplTraitPosition::TraitReturn | ImplTraitPosition::ImplReturn), - ) => { + ImplTraitContext::FeatureGated(position, feature) => { self.tcx .sess .create_feature_err( MisplacedImplTrait { span: t.span, - position: DiagnosticArgFromDisplay(&position), + position: DiagnosticArgFromDisplay(position), }, - sym::return_position_impl_trait_in_trait, + *feature, ) .emit(); hir::TyKind::Err @@ -1390,7 +1390,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { ImplTraitContext::Disallowed(position) => { self.tcx.sess.emit_err(MisplacedImplTrait { span: t.span, - position: DiagnosticArgFromDisplay(&position), + position: DiagnosticArgFromDisplay(position), }); hir::TyKind::Err } @@ -1739,14 +1739,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } else { match &decl.output { FnRetTy::Ty(ty) => { - let mut context = if kind.return_impl_trait_allowed(self.tcx) { + let context = if kind.return_impl_trait_allowed(self.tcx) { let fn_def_id = self.local_def_id(fn_node_id); ImplTraitContext::ReturnPositionOpaqueTy { origin: hir::OpaqueTyOrigin::FnReturn(fn_def_id), in_trait: matches!(kind, FnDeclKind::Trait), } } else { - ImplTraitContext::Disallowed(match kind { + let position = match kind { FnDeclKind::Fn | FnDeclKind::Inherent => { unreachable!("fn should allow in-band lifetimes") } @@ -1755,9 +1755,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { FnDeclKind::Pointer => ImplTraitPosition::PointerReturn, FnDeclKind::Trait => ImplTraitPosition::TraitReturn, FnDeclKind::Impl => ImplTraitPosition::ImplReturn, - }) + }; + match kind { + FnDeclKind::Trait | FnDeclKind::Impl => ImplTraitContext::FeatureGated( + position, + sym::return_position_impl_trait_in_trait, + ), + _ => ImplTraitContext::Disallowed(position), + } }; - hir::FnRetTy::Return(self.lower_ty(ty, &mut context)) + hir::FnRetTy::Return(self.lower_ty(ty, &context)) } FnRetTy::Default(span) => hir::FnRetTy::DefaultReturn(self.lower_span(*span)), } @@ -1938,7 +1945,10 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { output, span, if in_trait && !this.tcx.features().return_position_impl_trait_in_trait { - ImplTraitContext::Disallowed(ImplTraitPosition::TraitReturn) + ImplTraitContext::FeatureGated( + ImplTraitPosition::TraitReturn, + sym::return_position_impl_trait_in_trait, + ) } else { ImplTraitContext::ReturnPositionOpaqueTy { origin: hir::OpaqueTyOrigin::FnReturn(fn_def_id), diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 8d23c26e603..592fc5aa645 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -9,7 +9,7 @@ use rustc_ast::{self as ast, *}; use rustc_hir as hir; use rustc_hir::def::{DefKind, PartialRes, Res}; use rustc_hir::GenericArg; -use rustc_span::symbol::{kw, Ident}; +use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::{BytePos, Span, DUMMY_SP}; use smallvec::{smallvec, SmallVec}; @@ -352,11 +352,18 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // fn f(_: impl Fn() -> impl Debug) -> impl Fn() -> impl Debug // // disallowed --^^^^^^^^^^ allowed --^^^^^^^^^^ // ``` - FnRetTy::Ty(ty) - if matches!(itctx, ImplTraitContext::ReturnPositionOpaqueTy { .. }) - && self.tcx.features().impl_trait_in_fn_trait_return => - { - self.lower_ty(&ty, itctx) + FnRetTy::Ty(ty) if matches!(itctx, ImplTraitContext::ReturnPositionOpaqueTy { .. }) => { + if self.tcx.features().impl_trait_in_fn_trait_return { + self.lower_ty(&ty, itctx) + } else { + self.lower_ty( + &ty, + &ImplTraitContext::FeatureGated( + ImplTraitPosition::FnTraitReturn, + sym::impl_trait_in_fn_trait_return, + ), + ) + } } FnRetTy::Ty(ty) => { self.lower_ty(&ty, &ImplTraitContext::Disallowed(ImplTraitPosition::FnTraitReturn)) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 540f15c89e9..882430694e1 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -102,7 +102,7 @@ pub fn link_binary<'a>( sess, crate_type, outputs, - codegen_results.crate_info.local_crate_name.as_str(), + codegen_results.crate_info.local_crate_name, ); match crate_type { CrateType::Rlib => { diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 88d25be6bd8..0604d5ee6fa 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -373,9 +373,21 @@ pub trait Machine<'mir, 'tcx>: Sized { Ok(()) } - /// Executes a retagging operation. + /// Executes a retagging operation for a single pointer. + /// Returns the possibly adjusted pointer. #[inline] - fn retag( + fn retag_ptr_value( + _ecx: &mut InterpCx<'mir, 'tcx, Self>, + _kind: mir::RetagKind, + val: &ImmTy<'tcx, Self::Provenance>, + ) -> InterpResult<'tcx, ImmTy<'tcx, Self::Provenance>> { + Ok(val.clone()) + } + + /// Executes a retagging operation on a compound value. + /// Replaces all pointers stored in the given place. + #[inline] + fn retag_place_contents( _ecx: &mut InterpCx<'mir, 'tcx, Self>, _kind: mir::RetagKind, _place: &PlaceTy<'tcx, Self::Provenance>, diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 60578246eed..81b44a49484 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -8,7 +8,7 @@ use rustc_middle::mir; use rustc_middle::mir::interpret::{InterpResult, Scalar}; use rustc_middle::ty::layout::LayoutOf; -use super::{InterpCx, Machine}; +use super::{ImmTy, InterpCx, Machine}; /// Classify whether an operator is "left-homogeneous", i.e., the LHS has the /// same type as the result. @@ -108,7 +108,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Stacked Borrows. Retag(kind, place) => { let dest = self.eval_place(**place)?; - M::retag(self, *kind, &dest)?; + M::retag_place_contents(self, *kind, &dest)?; } Intrinsic(box ref intrinsic) => self.emulate_nondiverging_intrinsic(intrinsic)?, @@ -247,10 +247,41 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { self.write_scalar(Scalar::from_machine_usize(len, self), &dest)?; } - AddressOf(_, place) | Ref(_, _, place) => { + Ref(_, borrow_kind, place) => { let src = self.eval_place(place)?; let place = self.force_allocation(&src)?; - self.write_immediate(place.to_ref(self), &dest)?; + let val = ImmTy::from_immediate(place.to_ref(self), dest.layout); + // A fresh reference was created, make sure it gets retagged. + let val = M::retag_ptr_value( + self, + if borrow_kind.allows_two_phase_borrow() { + mir::RetagKind::TwoPhase + } else { + mir::RetagKind::Default + }, + &val, + )?; + self.write_immediate(*val, &dest)?; + } + + AddressOf(_, place) => { + // Figure out whether this is an addr_of of an already raw place. + let place_base_raw = if place.has_deref() { + let ty = self.frame().body.local_decls[place.local].ty; + ty.is_unsafe_ptr() + } else { + // Not a deref, and thus not raw. + false + }; + + let src = self.eval_place(place)?; + let place = self.force_allocation(&src)?; + let mut val = ImmTy::from_immediate(place.to_ref(self), dest.layout); + if !place_base_raw { + // If this was not already raw, it needs retagging. + val = M::retag_ptr_value(self, mir::RetagKind::Raw, &val)?; + } + self.write_immediate(*val, &dest)?; } NullaryOp(null_op, ty) => { diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs index 22f87514dd8..f06ca5a0733 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -25,6 +25,7 @@ use rustc_data_structures::sync::SeqCst; use rustc_errors::registry::{InvalidErrorCode, Registry}; use rustc_errors::{ErrorGuaranteed, PResult}; use rustc_feature::find_gated_cfg; +use rustc_hir::def_id::LOCAL_CRATE; use rustc_interface::util::{self, collect_crate_types, get_codegen_backend}; use rustc_interface::{interface, Queries}; use rustc_lint::LintStore; @@ -374,14 +375,14 @@ fn run_compiler( queries.global_ctxt()?.peek_mut().enter(|tcx| { let result = tcx.analysis(()); if sess.opts.unstable_opts.save_analysis { - let crate_name = queries.crate_name()?.peek().clone(); + let crate_name = tcx.crate_name(LOCAL_CRATE); sess.time("save_analysis", || { save::process_crate( tcx, - &crate_name, + crate_name, compiler.input(), None, - DumpHandler::new(compiler.output_dir().as_deref(), &crate_name), + DumpHandler::new(compiler.output_dir().as_deref(), crate_name), ) }); } @@ -678,7 +679,7 @@ fn print_crate_info( let crate_types = collect_crate_types(sess, attrs); for &style in &crate_types { let fname = - rustc_session::output::filename_for_input(sess, style, &id, &t_outputs); + rustc_session::output::filename_for_input(sess, style, id, &t_outputs); println!("{}", fname.file_name().unwrap().to_string_lossy()); } } diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 13e2d1ebbe7..9d6a4f9a1fd 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -960,7 +960,7 @@ pub trait LintStoreExpand { node_id: NodeId, attrs: &[Attribute], items: &[P<Item>], - name: &str, + name: Symbol, ); } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index e799fa404f6..1014ec2209c 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1122,7 +1122,7 @@ impl InvocationCollectorNode for P<ast::Item> { ecx.current_expansion.lint_node_id, &attrs, &items, - ident.name.as_str(), + ident.name, ); } diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 636e6e1b48d..8bc022e1e17 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -827,7 +827,7 @@ impl<'tcx> AttributeMap<'tcx> { pub struct OwnerNodes<'tcx> { /// Pre-computed hash of the full HIR. pub hash_including_bodies: Fingerprint, - /// Pre-computed hash of the item signature, sithout recursing into the body. + /// Pre-computed hash of the item signature, without recursing into the body. pub hash_without_bodies: Fingerprint, /// Full HIR for the current owner. // The zeroth node's parent should never be accessed: the owner's parent is computed by the diff --git a/compiler/rustc_hir/src/tests.rs b/compiler/rustc_hir/src/tests.rs index 4636d515249..d4791150947 100644 --- a/compiler/rustc_hir/src/tests.rs +++ b/compiler/rustc_hir/src/tests.rs @@ -1,5 +1,7 @@ use crate::definitions::{DefKey, DefPathData, DisambiguatedDefPathData}; use rustc_span::def_id::{DefPathHash, StableCrateId}; +use rustc_span::edition::Edition; +use rustc_span::{create_session_if_not_set_then, Symbol}; #[test] fn def_path_hash_depends_on_crate_id() { @@ -11,26 +13,28 @@ fn def_path_hash_depends_on_crate_id() { // the crate by changing the crate disambiguator (e.g. via bumping the // crate's version number). - let id0 = StableCrateId::new("foo", false, vec!["1".to_string()]); - let id1 = StableCrateId::new("foo", false, vec!["2".to_string()]); + create_session_if_not_set_then(Edition::Edition2024, |_| { + let id0 = StableCrateId::new(Symbol::intern("foo"), false, vec!["1".to_string()]); + let id1 = StableCrateId::new(Symbol::intern("foo"), false, vec!["2".to_string()]); - let h0 = mk_test_hash(id0); - let h1 = mk_test_hash(id1); + let h0 = mk_test_hash(id0); + let h1 = mk_test_hash(id1); - assert_ne!(h0.stable_crate_id(), h1.stable_crate_id()); - assert_ne!(h0.local_hash(), h1.local_hash()); + assert_ne!(h0.stable_crate_id(), h1.stable_crate_id()); + assert_ne!(h0.local_hash(), h1.local_hash()); - fn mk_test_hash(stable_crate_id: StableCrateId) -> DefPathHash { - let parent_hash = DefPathHash::new(stable_crate_id, 0); + fn mk_test_hash(stable_crate_id: StableCrateId) -> DefPathHash { + let parent_hash = DefPathHash::new(stable_crate_id, 0); - let key = DefKey { - parent: None, - disambiguated_data: DisambiguatedDefPathData { - data: DefPathData::CrateRoot, - disambiguator: 0, - }, - }; + let key = DefKey { + parent: None, + disambiguated_data: DisambiguatedDefPathData { + data: DefPathData::CrateRoot, + disambiguator: 0, + }, + }; - key.compute_stable_hash(parent_hash) - } + key.compute_stable_hash(parent_hash) + } + }) } diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index 639f81f20bf..0a7e25300cb 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -4,7 +4,6 @@ use hir::{ GenericParamKind, HirId, Node, }; use rustc_hir as hir; -use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::lint; @@ -143,20 +142,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::Generics { Some(tcx.typeck_root_def_id(def_id)) } Node::Item(item) => match item.kind { - ItemKind::OpaqueTy(hir::OpaqueTy { - origin: - hir::OpaqueTyOrigin::FnReturn(fn_def_id) | hir::OpaqueTyOrigin::AsyncFn(fn_def_id), - in_trait, - .. - }) => { - if in_trait { - assert!(matches!(tcx.def_kind(fn_def_id), DefKind::AssocFn)) - } else { - assert!(matches!(tcx.def_kind(fn_def_id), DefKind::AssocFn | DefKind::Fn)) - } - Some(fn_def_id.to_def_id()) - } - ItemKind::OpaqueTy(hir::OpaqueTy { origin: hir::OpaqueTyOrigin::TyAlias, .. }) => { + ItemKind::OpaqueTy(hir::OpaqueTy { .. }) => { let parent_id = tcx.hir().get_parent_item(hir_id); assert_ne!(parent_id, hir::CRATE_OWNER_ID); debug!("generics_of: parent of opaque ty {:?} is {:?}", def_id, parent_id); diff --git a/compiler/rustc_hir_typeck/src/check.rs b/compiler/rustc_hir_typeck/src/check.rs index 0c9a350c295..32f86b8042c 100644 --- a/compiler/rustc_hir_typeck/src/check.rs +++ b/compiler/rustc_hir_typeck/src/check.rs @@ -1,7 +1,7 @@ use crate::coercion::CoerceMany; use crate::gather_locals::GatherLocalsVisitor; use crate::FnCtxt; -use crate::{GeneratorTypes, UnsafetyState}; +use crate::GeneratorTypes; use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::intravisit::Visitor; @@ -30,7 +30,6 @@ pub(super) fn check_fn<'a, 'tcx>( can_be_generator: Option<hir::Movability>, ) -> Option<GeneratorTypes<'tcx>> { let fn_id = fcx.tcx.hir().local_def_id_to_hir_id(fn_def_id); - fcx.ps.set(UnsafetyState::function(fn_sig.unsafety, fn_id)); let tcx = fcx.tcx; let hir = tcx.hir(); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 8a875e05b19..60fec05d36d 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -1393,8 +1393,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { blk: &'tcx hir::Block<'tcx>, expected: Expectation<'tcx>, ) -> Ty<'tcx> { - let prev = self.ps.replace(self.ps.get().recurse(blk)); - // In some cases, blocks have just one exit, but other blocks // can be targeted by multiple breaks. This can happen both // with labeled blocks as well as when we desugar @@ -1558,7 +1556,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_ty(blk.hir_id, ty); - self.ps.set(prev); ty } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index 1e9b5752130..30b59da7852 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -8,7 +8,7 @@ use rustc_errors::ErrorGuaranteed; pub use suggestions::*; use crate::coercion::DynamicCoerceMany; -use crate::{Diverges, EnclosingBreakables, Inherited, UnsafetyState}; +use crate::{Diverges, EnclosingBreakables, Inherited}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir_analysis::astconv::AstConv; @@ -74,8 +74,6 @@ pub struct FnCtxt<'a, 'tcx> { pub(super) resume_yield_tys: Option<(Ty<'tcx>, Ty<'tcx>)>, - pub(super) ps: Cell<UnsafetyState>, - /// Whether the last checked node generates a divergence (e.g., /// `return` will set this to `Always`). In general, when entering /// an expression or other node in the tree, the initial value @@ -129,7 +127,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ret_coercion: None, ret_coercion_span: Cell::new(None), resume_yield_tys: None, - ps: Cell::new(UnsafetyState::function(hir::Unsafety::Normal, hir::CRATE_HIR_ID)), diverges: Cell::new(Diverges::Maybe), enclosing_breakables: RefCell::new(EnclosingBreakables { stack: Vec::new(), diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 09bd123350d..5b2352cda34 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -89,38 +89,6 @@ pub struct LocalTy<'tcx> { revealed_ty: Ty<'tcx>, } -#[derive(Copy, Clone)] -pub struct UnsafetyState { - pub def: hir::HirId, - pub unsafety: hir::Unsafety, - from_fn: bool, -} - -impl UnsafetyState { - pub fn function(unsafety: hir::Unsafety, def: hir::HirId) -> UnsafetyState { - UnsafetyState { def, unsafety, from_fn: true } - } - - pub fn recurse(self, blk: &hir::Block<'_>) -> UnsafetyState { - use hir::BlockCheckMode; - match self.unsafety { - // If this unsafe, then if the outer function was already marked as - // unsafe we shouldn't attribute the unsafe'ness to the block. This - // way the block can be warned about instead of ignoring this - // extraneous block (functions are never warned about). - hir::Unsafety::Unsafe if self.from_fn => self, - - unsafety => { - let (unsafety, def) = match blk.rules { - BlockCheckMode::UnsafeBlock(..) => (hir::Unsafety::Unsafe, blk.hir_id), - BlockCheckMode::DefaultBlock => (unsafety, self.def), - }; - UnsafetyState { def, unsafety, from_fn: false } - } - } - } -} - /// If this `DefId` is a "primary tables entry", returns /// `Some((body_id, body_ty, fn_sig))`. Otherwise, returns `None`. /// diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 58d6e6d7efd..97ebed05855 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -109,6 +109,7 @@ use rustc_data_structures::{base_n, flock}; use rustc_errors::ErrorGuaranteed; use rustc_fs_util::{link_or_copy, LinkOrCopy}; use rustc_session::{Session, StableCrateId}; +use rustc_span::Symbol; use std::fs as std_fs; use std::io::{self, ErrorKind}; @@ -202,7 +203,7 @@ pub fn in_incr_comp_dir(incr_comp_session_dir: &Path, file_name: &str) -> PathBu /// [`rustc_interface::queries::dep_graph`]: ../../rustc_interface/struct.Queries.html#structfield.dep_graph pub fn prepare_session_directory( sess: &Session, - crate_name: &str, + crate_name: Symbol, stable_crate_id: StableCrateId, ) -> Result<(), ErrorGuaranteed> { if sess.opts.incremental.is_none() { @@ -657,7 +658,7 @@ fn string_to_timestamp(s: &str) -> Result<SystemTime, ()> { Ok(UNIX_EPOCH + duration) } -fn crate_path(sess: &Session, crate_name: &str, stable_crate_id: StableCrateId) -> PathBuf { +fn crate_path(sess: &Session, crate_name: Symbol, stable_crate_id: StableCrateId) -> PathBuf { let incr_dir = sess.opts.incremental.as_ref().unwrap().clone(); let stable_crate_id = base_n::encode(stable_crate_id.to_u64() as u128, INT_ENCODE_BASE); diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 3256ca1fb20..987559d7e47 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -56,22 +56,17 @@ use crate::infer::ExpectedFound; use crate::traits::error_reporting::report_object_safety_error; use crate::traits::{ IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode, - StatementAsExpression, }; -use crate::errors::SuggAddLetForLetChains; -use hir::intravisit::{walk_expr, walk_stmt}; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed, IntoDiagnosticArg}; use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan}; use rustc_hir as hir; -use rustc_hir::def::{CtorKind, DefKind}; +use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_hir::intravisit::Visitor; use rustc_hir::lang_items::LangItem; use rustc_hir::Node; use rustc_middle::dep_graph::DepContext; -use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::relate::{self, RelateResult, TypeRelation}; use rustc_middle::ty::{ self, error::TypeError, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, @@ -80,9 +75,11 @@ use rustc_middle::ty::{ use rustc_span::{sym, symbol::kw, BytePos, DesugaringKind, Pos, Span}; use rustc_target::spec::abi; use std::ops::{ControlFlow, Deref}; +use std::path::PathBuf; use std::{cmp, fmt, iter}; mod note; +mod suggest; pub(crate) mod need_type_info; pub use need_type_info::TypeAnnotationNeeded; @@ -806,87 +803,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } - fn suggest_remove_semi_or_return_binding( - &self, - err: &mut Diagnostic, - first_id: Option<hir::HirId>, - first_ty: Ty<'tcx>, - first_span: Span, - second_id: Option<hir::HirId>, - second_ty: Ty<'tcx>, - second_span: Span, - ) { - let remove_semicolon = [ - (first_id, self.resolve_vars_if_possible(second_ty)), - (second_id, self.resolve_vars_if_possible(first_ty)), - ] - .into_iter() - .find_map(|(id, ty)| { - let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None }; - self.could_remove_semicolon(blk, ty) - }); - match remove_semicolon { - Some((sp, StatementAsExpression::NeedsBoxing)) => { - err.multipart_suggestion( - "consider removing this semicolon and boxing the expressions", - vec![ - (first_span.shrink_to_lo(), "Box::new(".to_string()), - (first_span.shrink_to_hi(), ")".to_string()), - (second_span.shrink_to_lo(), "Box::new(".to_string()), - (second_span.shrink_to_hi(), ")".to_string()), - (sp, String::new()), - ], - Applicability::MachineApplicable, - ); - } - Some((sp, StatementAsExpression::CorrectType)) => { - err.span_suggestion_short( - sp, - "consider removing this semicolon", - "", - Applicability::MachineApplicable, - ); - } - None => { - for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] { - if let Some(id) = id - && let hir::Node::Block(blk) = self.tcx.hir().get(id) - && self.consider_returning_binding(blk, ty, err) - { - break; - } - } - } - } - } - - fn suggest_boxing_for_return_impl_trait( - &self, - err: &mut Diagnostic, - return_sp: Span, - arm_spans: impl Iterator<Item = Span>, - ) { - err.multipart_suggestion( - "you could change the return type to be a boxed trait object", - vec![ - (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()), - (return_sp.shrink_to_hi(), ">".to_string()), - ], - Applicability::MaybeIncorrect, - ); - let sugg = arm_spans - .flat_map(|sp| { - [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())] - .into_iter() - }) - .collect::<Vec<_>>(); - err.multipart_suggestion( - "if you change the return type to expect trait objects, box the returned expressions", - sugg, - Applicability::MaybeIncorrect, - ); - } - /// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value` /// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and /// populate `other_value` with `other_ty`. @@ -1352,10 +1268,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { .map(|(mod_str, _)| mod_str.len() + separator_len) .sum(); - debug!( - "cmp: separator_len={}, split_idx={}, min_len={}", - separator_len, split_idx, min_len - ); + debug!(?separator_len, ?split_idx, ?min_len, "cmp"); if split_idx >= min_len { // paths are identical, highlight everything @@ -1366,7 +1279,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } else { let (common, uniq1) = t1_str.split_at(split_idx); let (_, uniq2) = t2_str.split_at(split_idx); - debug!("cmp: common={}, uniq1={}, uniq2={}", common, uniq1, uniq2); + debug!(?common, ?uniq1, ?uniq2, "cmp"); values.0.push_normal(common); values.0.push_highlighted(uniq1); @@ -1659,17 +1572,14 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } ValuePairs::Regions(_) => (false, Mismatch::Fixed("lifetime")), }; - let vals = match self.values_str(values) { - Some((expected, found)) => Some((expected, found)), - None => { - // Derived error. Cancel the emitter. - // NOTE(eddyb) this was `.cancel()`, but `diag` - // is borrowed, so we can't fully defuse it. - diag.downgrade_to_delayed_bug(); - return; - } + let Some(vals) = self.values_str(values) else { + // Derived error. Cancel the emitter. + // NOTE(eddyb) this was `.cancel()`, but `diag` + // is borrowed, so we can't fully defuse it. + diag.downgrade_to_delayed_bug(); + return; }; - (vals, exp_found, is_simple_error, Some(values)) + (Some(vals), exp_found, is_simple_error, Some(values)) } }; @@ -1701,7 +1611,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { label_or_note(span, &terr.to_string()); } - if let Some((expected, found)) = expected_found { + if let Some((expected, found, exp_p, found_p)) = expected_found { let (expected_label, found_label, exp_found) = match exp_found { Mismatch::Variable(ef) => ( ef.expected.prefix_string(self.tcx), @@ -1818,32 +1728,41 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } TypeError::Sorts(values) => { let extra = expected == found; - let sort_string = |ty: Ty<'tcx>| match (extra, ty.kind()) { - (true, ty::Opaque(def_id, _)) => { - let sm = self.tcx.sess.source_map(); - let pos = sm.lookup_char_pos(self.tcx.def_span(*def_id).lo()); - format!( - " (opaque type at <{}:{}:{}>)", - sm.filename_for_diagnostics(&pos.file.name), - pos.line, - pos.col.to_usize() + 1, - ) - } - (true, ty::Projection(proj)) - if self.tcx.def_kind(proj.item_def_id) - == DefKind::ImplTraitPlaceholder => - { - let sm = self.tcx.sess.source_map(); - let pos = sm.lookup_char_pos(self.tcx.def_span(proj.item_def_id).lo()); - format!( - " (trait associated opaque type at <{}:{}:{}>)", - sm.filename_for_diagnostics(&pos.file.name), - pos.line, - pos.col.to_usize() + 1, - ) + let sort_string = |ty: Ty<'tcx>, path: Option<PathBuf>| { + let mut s = match (extra, ty.kind()) { + (true, ty::Opaque(def_id, _)) => { + let sm = self.tcx.sess.source_map(); + let pos = sm.lookup_char_pos(self.tcx.def_span(*def_id).lo()); + format!( + " (opaque type at <{}:{}:{}>)", + sm.filename_for_diagnostics(&pos.file.name), + pos.line, + pos.col.to_usize() + 1, + ) + } + (true, ty::Projection(proj)) + if self.tcx.def_kind(proj.item_def_id) + == DefKind::ImplTraitPlaceholder => + { + let sm = self.tcx.sess.source_map(); + let pos = sm.lookup_char_pos(self.tcx.def_span(proj.item_def_id).lo()); + format!( + " (trait associated opaque type at <{}:{}:{}>)", + sm.filename_for_diagnostics(&pos.file.name), + pos.line, + pos.col.to_usize() + 1, + ) + } + (true, _) => format!(" ({})", ty.sort_string(self.tcx)), + (false, _) => "".to_string(), + }; + if let Some(path) = path { + s.push_str(&format!( + "\nthe full type name has been written to '{}'", + path.display(), + )); } - (true, _) => format!(" ({})", ty.sort_string(self.tcx)), - (false, _) => "".to_string(), + s }; if !(values.expected.is_simple_text() && values.found.is_simple_text()) || (exp_found.map_or(false, |ef| { @@ -1865,8 +1784,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { expected, &found_label, found, - &sort_string(values.expected), - &sort_string(values.found), + &sort_string(values.expected, exp_p), + &sort_string(values.found, found_p), ); } } @@ -1940,310 +1859,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { debug!(?diag); } - fn suggest_tuple_pattern( - &self, - cause: &ObligationCause<'tcx>, - exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, - diag: &mut Diagnostic, - ) { - // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with - // some modifications due to that being in typeck and this being in infer. - if let ObligationCauseCode::Pattern { .. } = cause.code() { - if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() { - let compatible_variants: Vec<_> = expected_adt - .variants() - .iter() - .filter(|variant| { - variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn) - }) - .filter_map(|variant| { - let sole_field = &variant.fields[0]; - let sole_field_ty = sole_field.ty(self.tcx, substs); - if self.same_type_modulo_infer(sole_field_ty, exp_found.found) { - let variant_path = - with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); - // FIXME #56861: DRYer prelude filtering - if let Some(path) = variant_path.strip_prefix("std::prelude::") { - if let Some((_, path)) = path.split_once("::") { - return Some(path.to_string()); - } - } - Some(variant_path) - } else { - None - } - }) - .collect(); - match &compatible_variants[..] { - [] => {} - [variant] => { - diag.multipart_suggestion_verbose( - &format!("try wrapping the pattern in `{}`", variant), - vec![ - (cause.span.shrink_to_lo(), format!("{}(", variant)), - (cause.span.shrink_to_hi(), ")".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } - _ => { - // More than one matching variant. - diag.multipart_suggestions( - &format!( - "try wrapping the pattern in a variant of `{}`", - self.tcx.def_path_str(expected_adt.did()) - ), - compatible_variants.into_iter().map(|variant| { - vec![ - (cause.span.shrink_to_lo(), format!("{}(", variant)), - (cause.span.shrink_to_hi(), ")".to_string()), - ] - }), - Applicability::MaybeIncorrect, - ); - } - } - } - } - } - - /// A possible error is to forget to add `.await` when using futures: - /// - /// ```compile_fail,E0308 - /// async fn make_u32() -> u32 { - /// 22 - /// } - /// - /// fn take_u32(x: u32) {} - /// - /// async fn foo() { - /// let x = make_u32(); - /// take_u32(x); - /// } - /// ``` - /// - /// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the - /// expected type. If this is the case, and we are inside of an async body, it suggests adding - /// `.await` to the tail of the expression. - fn suggest_await_on_expect_found( - &self, - cause: &ObligationCause<'tcx>, - exp_span: Span, - exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, - diag: &mut Diagnostic, - ) { - debug!( - "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}", - exp_span, exp_found.expected, exp_found.found, - ); - - if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() { - return; - } - - match ( - self.get_impl_future_output_ty(exp_found.expected), - self.get_impl_future_output_ty(exp_found.found), - ) { - (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause - .code() - { - ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { - let then_span = self.find_block_span_from_hir_id(*then_id); - diag.multipart_suggestion( - "consider `await`ing on both `Future`s", - vec![ - (then_span.shrink_to_hi(), ".await".to_string()), - (exp_span.shrink_to_hi(), ".await".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { - prior_arms, - .. - }) => { - if let [.., arm_span] = &prior_arms[..] { - diag.multipart_suggestion( - "consider `await`ing on both `Future`s", - vec![ - (arm_span.shrink_to_hi(), ".await".to_string()), - (exp_span.shrink_to_hi(), ".await".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } else { - diag.help("consider `await`ing on both `Future`s"); - } - } - _ => { - diag.help("consider `await`ing on both `Future`s"); - } - }, - (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => { - diag.span_suggestion_verbose( - exp_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code() - { - ObligationCauseCode::Pattern { span: Some(then_span), .. } => { - diag.span_suggestion_verbose( - then_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { - let then_span = self.find_block_span_from_hir_id(*then_id); - diag.span_suggestion_verbose( - then_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { - ref prior_arms, - .. - }) => { - diag.multipart_suggestion_verbose( - "consider `await`ing on the `Future`", - prior_arms - .iter() - .map(|arm| (arm.shrink_to_hi(), ".await".to_string())) - .collect(), - Applicability::MaybeIncorrect, - ); - } - _ => {} - }, - _ => {} - } - } - - fn suggest_accessing_field_where_appropriate( - &self, - cause: &ObligationCause<'tcx>, - exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, - diag: &mut Diagnostic, - ) { - debug!( - "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})", - cause, exp_found - ); - if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() { - if expected_def.is_enum() { - return; - } - - if let Some((name, ty)) = expected_def - .non_enum_variant() - .fields - .iter() - .filter(|field| field.vis.is_accessible_from(field.did, self.tcx)) - .map(|field| (field.name, field.ty(self.tcx, expected_substs))) - .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found)) - { - if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() { - if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { - let suggestion = if expected_def.is_struct() { - format!("{}.{}", snippet, name) - } else if expected_def.is_union() { - format!("unsafe {{ {}.{} }}", snippet, name) - } else { - return; - }; - diag.span_suggestion( - span, - &format!( - "you might have meant to use field `{}` whose type is `{}`", - name, ty - ), - suggestion, - Applicability::MaybeIncorrect, - ); - } - } - } - } - } - - /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate, - /// suggests it. - fn suggest_as_ref_where_appropriate( - &self, - span: Span, - exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, - diag: &mut Diagnostic, - ) { - if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) - && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found) - { - diag.span_suggestion( - span, - msg, - // HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&` - format!("{}.as_ref()", snippet.trim_start_matches('&')), - Applicability::MachineApplicable, - ); - } - } - - pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> { - if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) = - (expected.kind(), found.kind()) - { - if let ty::Adt(found_def, found_substs) = *found_ty.kind() { - if exp_def == &found_def { - let have_as_ref = &[ - ( - sym::Option, - "you can convert from `&Option<T>` to `Option<&T>` using \ - `.as_ref()`", - ), - ( - sym::Result, - "you can convert from `&Result<T, E>` to \ - `Result<&T, &E>` using `.as_ref()`", - ), - ]; - if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| { - self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg) - }) { - let mut show_suggestion = true; - for (exp_ty, found_ty) in - iter::zip(exp_substs.types(), found_substs.types()) - { - match *exp_ty.kind() { - ty::Ref(_, exp_ty, _) => { - match (exp_ty.kind(), found_ty.kind()) { - (_, ty::Param(_)) - | (_, ty::Infer(_)) - | (ty::Param(_), _) - | (ty::Infer(_), _) => {} - _ if self.same_type_modulo_infer(exp_ty, found_ty) => {} - _ => show_suggestion = false, - }; - } - ty::Param(_) | ty::Infer(_) => {} - _ => show_suggestion = false, - } - } - if show_suggestion { - return Some(*msg); - } - } - } - } - } - None - } - pub fn report_and_explain_type_error( &self, trace: TypeTrace<'tcx>, @@ -2339,7 +1954,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let code = trace.cause.code(); if let &MatchExpressionArm(box MatchExpressionArmCause { source, .. }) = code && let hir::MatchSource::TryDesugar = source - && let Some((expected_ty, found_ty)) = self.values_str(trace.values) + && let Some((expected_ty, found_ty, _, _)) = self.values_str(trace.values) { err.note(&format!( "`?` operator cannot convert from `{}` to `{}`", @@ -2357,67 +1972,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { diag } - /// Try to find code with pattern `if Some(..) = expr` - /// use a `visitor` to mark the `if` which its span contains given error span, - /// and then try to find a assignment in the `cond` part, which span is equal with error span - fn suggest_let_for_letchains( - &self, - err: &mut Diagnostic, - cause: &ObligationCause<'_>, - span: Span, - ) { - let hir = self.tcx.hir(); - let fn_hir_id = hir.get_parent_node(cause.body_id); - if let Some(node) = self.tcx.hir().find(fn_hir_id) && - let hir::Node::Item(hir::Item { - kind: hir::ItemKind::Fn(_sig, _, body_id), .. - }) = node { - let body = hir.body(*body_id); - - /// Find the if expression with given span - struct IfVisitor { - pub result: bool, - pub found_if: bool, - pub err_span: Span, - } - - impl<'v> Visitor<'v> for IfVisitor { - fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { - if self.result { return; } - match ex.kind { - hir::ExprKind::If(cond, _, _) => { - self.found_if = true; - walk_expr(self, cond); - self.found_if = false; - } - _ => walk_expr(self, ex), - } - } - - fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) { - if let hir::StmtKind::Local(hir::Local { - span, pat: hir::Pat{..}, ty: None, init: Some(_), .. - }) = &ex.kind - && self.found_if - && span.eq(&self.err_span) { - self.result = true; - } - walk_stmt(self, ex); - } - - fn visit_body(&mut self, body: &'v hir::Body<'v>) { - hir::intravisit::walk_body(self, body); - } - } - - let mut visitor = IfVisitor { err_span: span, found_if: false, result: false }; - visitor.visit_body(&body); - if visitor.result { - err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()}); - } - } - } - fn emit_tuple_wrap_err( &self, err: &mut Diagnostic, @@ -2455,7 +2009,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn values_str( &self, values: ValuePairs<'tcx>, - ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { + ) -> Option<(DiagnosticStyledString, DiagnosticStyledString, Option<PathBuf>, Option<PathBuf>)> + { match values { infer::Regions(exp_found) => self.expected_found_str(exp_found), infer::Terms(exp_found) => self.expected_found_str_term(exp_found), @@ -2465,7 +2020,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { found: exp_found.found.print_only_trait_path(), }; match self.expected_found_str(pretty_exp_found) { - Some((expected, found)) if expected == found => { + Some((expected, found, _, _)) if expected == found => { self.expected_found_str(exp_found) } ret => ret, @@ -2477,7 +2032,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { found: exp_found.found.print_only_trait_path(), }; match self.expected_found_str(pretty_exp_found) { - Some((expected, found)) if expected == found => { + Some((expected, found, _, _)) if expected == found => { self.expected_found_str(exp_found) } ret => ret, @@ -2489,17 +2044,41 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn expected_found_str_term( &self, exp_found: ty::error::ExpectedFound<ty::Term<'tcx>>, - ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { + ) -> Option<(DiagnosticStyledString, DiagnosticStyledString, Option<PathBuf>, Option<PathBuf>)> + { let exp_found = self.resolve_vars_if_possible(exp_found); if exp_found.references_error() { return None; } Some(match (exp_found.expected.unpack(), exp_found.found.unpack()) { - (ty::TermKind::Ty(expected), ty::TermKind::Ty(found)) => self.cmp(expected, found), + (ty::TermKind::Ty(expected), ty::TermKind::Ty(found)) => { + let (mut exp, mut fnd) = self.cmp(expected, found); + // Use the terminal width as the basis to determine when to compress the printed + // out type, but give ourselves some leeway to avoid ending up creating a file for + // a type that is somewhat shorter than the path we'd write to. + let len = self.tcx.sess().diagnostic_width() + 40; + let exp_s = exp.content(); + let fnd_s = fnd.content(); + let mut exp_p = None; + let mut fnd_p = None; + if exp_s.len() > len { + let (exp_s, exp_path) = self.tcx.short_ty_string(expected); + exp = DiagnosticStyledString::highlighted(exp_s); + exp_p = exp_path; + } + if fnd_s.len() > len { + let (fnd_s, fnd_path) = self.tcx.short_ty_string(found); + fnd = DiagnosticStyledString::highlighted(fnd_s); + fnd_p = fnd_path; + } + (exp, fnd, exp_p, fnd_p) + } _ => ( DiagnosticStyledString::highlighted(exp_found.expected.to_string()), DiagnosticStyledString::highlighted(exp_found.found.to_string()), + None, + None, ), }) } @@ -2508,7 +2087,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn expected_found_str<T: fmt::Display + TypeFoldable<'tcx>>( &self, exp_found: ty::error::ExpectedFound<T>, - ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { + ) -> Option<(DiagnosticStyledString, DiagnosticStyledString, Option<PathBuf>, Option<PathBuf>)> + { let exp_found = self.resolve_vars_if_possible(exp_found); if exp_found.references_error() { return None; @@ -2517,6 +2097,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { Some(( DiagnosticStyledString::highlighted(exp_found.expected.to_string()), DiagnosticStyledString::highlighted(exp_found.found.to_string()), + None, + None, )) } @@ -2850,36 +2432,29 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { debug!("report_sub_sup_conflict: sup_region={:?}", sup_region); debug!("report_sub_sup_conflict: sup_origin={:?}", sup_origin); - if let (&infer::Subtype(ref sup_trace), &infer::Subtype(ref sub_trace)) = - (&sup_origin, &sub_origin) + if let infer::Subtype(ref sup_trace) = sup_origin + && let infer::Subtype(ref sub_trace) = sub_origin + && let Some((sup_expected, sup_found, _, _)) = self.values_str(sup_trace.values) + && let Some((sub_expected, sub_found, _, _)) = self.values_str(sub_trace.values) + && sub_expected == sup_expected + && sub_found == sup_found { - debug!("report_sub_sup_conflict: sup_trace={:?}", sup_trace); - debug!("report_sub_sup_conflict: sub_trace={:?}", sub_trace); - debug!("report_sub_sup_conflict: sup_trace.values={:?}", sup_trace.values); - debug!("report_sub_sup_conflict: sub_trace.values={:?}", sub_trace.values); - - if let (Some((sup_expected, sup_found)), Some((sub_expected, sub_found))) = - (self.values_str(sup_trace.values), self.values_str(sub_trace.values)) - { - if sub_expected == sup_expected && sub_found == sup_found { - note_and_explain_region( - self.tcx, - &mut err, - "...but the lifetime must also be valid for ", - sub_region, - "...", - None, - ); - err.span_note( - sup_trace.cause.span, - &format!("...so that the {}", sup_trace.cause.as_requirement_str()), - ); + note_and_explain_region( + self.tcx, + &mut err, + "...but the lifetime must also be valid for ", + sub_region, + "...", + None, + ); + err.span_note( + sup_trace.cause.span, + &format!("...so that the {}", sup_trace.cause.as_requirement_str()), + ); - err.note_expected_found(&"", sup_expected, &"", sup_found); - err.emit(); - return; - } - } + err.note_expected_found(&"", sup_expected, &"", sup_found); + err.emit(); + return; } self.note_region_origin(&mut err, &sup_origin); @@ -3228,211 +2803,3 @@ impl<'tcx> InferCtxt<'tcx> { } } } - -impl<'tcx> TypeErrCtxt<'_, 'tcx> { - /// Be helpful when the user wrote `{... expr; }` and taking the `;` off - /// is enough to fix the error. - pub fn could_remove_semicolon( - &self, - blk: &'tcx hir::Block<'tcx>, - expected_ty: Ty<'tcx>, - ) -> Option<(Span, StatementAsExpression)> { - let blk = blk.innermost_block(); - // Do not suggest if we have a tail expr. - if blk.expr.is_some() { - return None; - } - let last_stmt = blk.stmts.last()?; - let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else { - return None; - }; - let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?; - let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) { - _ if last_expr_ty.references_error() => return None, - _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => { - StatementAsExpression::CorrectType - } - (ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _)) - if last_def_id == exp_def_id => - { - StatementAsExpression::CorrectType - } - (ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => { - debug!( - "both opaque, likely future {:?} {:?} {:?} {:?}", - last_def_id, last_bounds, exp_def_id, exp_bounds - ); - - let last_local_id = last_def_id.as_local()?; - let exp_local_id = exp_def_id.as_local()?; - - match ( - &self.tcx.hir().expect_item(last_local_id).kind, - &self.tcx.hir().expect_item(exp_local_id).kind, - ) { - ( - hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }), - hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }), - ) if iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| { - match (left, right) { - ( - hir::GenericBound::Trait(tl, ml), - hir::GenericBound::Trait(tr, mr), - ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id() - && ml == mr => - { - true - } - ( - hir::GenericBound::LangItemTrait(langl, _, _, argsl), - hir::GenericBound::LangItemTrait(langr, _, _, argsr), - ) if langl == langr => { - // FIXME: consider the bounds! - debug!("{:?} {:?}", argsl, argsr); - true - } - _ => false, - } - }) => - { - StatementAsExpression::NeedsBoxing - } - _ => StatementAsExpression::CorrectType, - } - } - _ => return None, - }; - let span = if last_stmt.span.from_expansion() { - let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span); - self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)? - } else { - last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1)) - }; - Some((span, needs_box)) - } - - /// Suggest returning a local binding with a compatible type if the block - /// has no return expression. - pub fn consider_returning_binding( - &self, - blk: &'tcx hir::Block<'tcx>, - expected_ty: Ty<'tcx>, - err: &mut Diagnostic, - ) -> bool { - let blk = blk.innermost_block(); - // Do not suggest if we have a tail expr. - if blk.expr.is_some() { - return false; - } - let mut shadowed = FxIndexSet::default(); - let mut candidate_idents = vec![]; - let mut find_compatible_candidates = |pat: &hir::Pat<'_>| { - if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind - && let Some(pat_ty) = self - .typeck_results - .as_ref() - .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id)) - { - let pat_ty = self.resolve_vars_if_possible(pat_ty); - if self.same_type_modulo_infer(pat_ty, expected_ty) - && !(pat_ty, expected_ty).references_error() - && shadowed.insert(ident.name) - { - candidate_idents.push((*ident, pat_ty)); - } - } - true - }; - - let hir = self.tcx.hir(); - for stmt in blk.stmts.iter().rev() { - let hir::StmtKind::Local(local) = &stmt.kind else { continue; }; - local.pat.walk(&mut find_compatible_candidates); - } - match hir.find(hir.get_parent_node(blk.hir_id)) { - Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => { - match hir.find(hir.get_parent_node(*hir_id)) { - Some(hir::Node::Arm(hir::Arm { pat, .. })) => { - pat.walk(&mut find_compatible_candidates); - } - Some( - hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. }) - | hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Fn(_, body), - .. - }) - | hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)), - .. - }) - | hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Closure(hir::Closure { body, .. }), - .. - }), - ) => { - for param in hir.body(*body).params { - param.pat.walk(&mut find_compatible_candidates); - } - } - Some(hir::Node::Expr(hir::Expr { - kind: - hir::ExprKind::If( - hir::Expr { kind: hir::ExprKind::Let(let_), .. }, - then_block, - _, - ), - .. - })) if then_block.hir_id == *hir_id => { - let_.pat.walk(&mut find_compatible_candidates); - } - _ => {} - } - } - _ => {} - } - - match &candidate_idents[..] { - [(ident, _ty)] => { - let sm = self.tcx.sess.source_map(); - if let Some(stmt) = blk.stmts.last() { - let stmt_span = sm.stmt_span(stmt.span, blk.span); - let sugg = if sm.is_multiline(blk.span) - && let Some(spacing) = sm.indentation_before(stmt_span) - { - format!("\n{spacing}{ident}") - } else { - format!(" {ident}") - }; - err.span_suggestion_verbose( - stmt_span.shrink_to_hi(), - format!("consider returning the local binding `{ident}`"), - sugg, - Applicability::MaybeIncorrect, - ); - } else { - let sugg = if sm.is_multiline(blk.span) - && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo()) - { - format!("\n{spacing} {ident}\n{spacing}") - } else { - format!(" {ident} ") - }; - let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi(); - err.span_suggestion_verbose( - sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span), - format!("consider returning the local binding `{ident}`"), - sugg, - Applicability::MaybeIncorrect, - ); - } - true - } - values if (1..3).contains(&values.len()) => { - let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>(); - err.span_note(spans, "consider returning one of these bindings"); - true - } - _ => false, - } - } -} diff --git a/compiler/rustc_infer/src/infer/error_reporting/note.rs b/compiler/rustc_infer/src/infer/error_reporting/note.rs index 41b115f3377..d2dffa4a0b7 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/note.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/note.rs @@ -16,7 +16,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { infer::Subtype(ref trace) => RegionOriginNote::WithRequirement { span: trace.cause.span, requirement: ObligationCauseAsDiagArg(trace.cause.clone()), - expected_found: self.values_str(trace.values), + expected_found: self.values_str(trace.values).map(|(e, f, _, _)| (e, f)), } .add_to_diagnostic(err), infer::Reborrow(span) => { diff --git a/compiler/rustc_infer/src/infer/error_reporting/note_region.rs b/compiler/rustc_infer/src/infer/error_reporting/note_region.rs new file mode 100644 index 00000000000..41b115f3377 --- /dev/null +++ b/compiler/rustc_infer/src/infer/error_reporting/note_region.rs @@ -0,0 +1,427 @@ +use crate::errors::RegionOriginNote; +use crate::infer::error_reporting::{note_and_explain_region, TypeErrCtxt}; +use crate::infer::{self, SubregionOrigin}; +use rustc_errors::{ + fluent, struct_span_err, AddToDiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, +}; +use rustc_middle::traits::ObligationCauseCode; +use rustc_middle::ty::error::TypeError; +use rustc_middle::ty::{self, Region}; + +use super::ObligationCauseAsDiagArg; + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + pub(super) fn note_region_origin(&self, err: &mut Diagnostic, origin: &SubregionOrigin<'tcx>) { + match *origin { + infer::Subtype(ref trace) => RegionOriginNote::WithRequirement { + span: trace.cause.span, + requirement: ObligationCauseAsDiagArg(trace.cause.clone()), + expected_found: self.values_str(trace.values), + } + .add_to_diagnostic(err), + infer::Reborrow(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_reborrow }.add_to_diagnostic(err) + } + infer::ReborrowUpvar(span, ref upvar_id) => { + let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id); + RegionOriginNote::WithName { + span, + msg: fluent::infer_reborrow, + name: &var_name.to_string(), + continues: false, + } + .add_to_diagnostic(err); + } + infer::RelateObjectBound(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_object_bound } + .add_to_diagnostic(err); + } + infer::DataBorrowed(ty, span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_data_borrowed, + name: &self.ty_to_string(ty), + continues: false, + } + .add_to_diagnostic(err); + } + infer::ReferenceOutlivesReferent(ty, span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_reference_outlives_referent, + name: &self.ty_to_string(ty), + continues: false, + } + .add_to_diagnostic(err); + } + infer::RelateParamBound(span, ty, opt_span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_relate_param_bound, + name: &self.ty_to_string(ty), + continues: opt_span.is_some(), + } + .add_to_diagnostic(err); + if let Some(span) = opt_span { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_param_bound_2 } + .add_to_diagnostic(err); + } + } + infer::RelateRegionParamBound(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_region_param_bound } + .add_to_diagnostic(err); + } + infer::CompareImplItemObligation { span, .. } => { + RegionOriginNote::Plain { span, msg: fluent::infer_compare_impl_item_obligation } + .add_to_diagnostic(err); + } + infer::CheckAssociatedTypeBounds { ref parent, .. } => { + self.note_region_origin(err, &parent); + } + infer::AscribeUserTypeProvePredicate(span) => { + RegionOriginNote::Plain { + span, + msg: fluent::infer_ascribe_user_type_prove_predicate, + } + .add_to_diagnostic(err); + } + } + } + + pub(super) fn report_concrete_failure( + &self, + origin: SubregionOrigin<'tcx>, + sub: Region<'tcx>, + sup: Region<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + match origin { + infer::Subtype(box trace) => { + let terr = TypeError::RegionsDoesNotOutlive(sup, sub); + let mut err = self.report_and_explain_type_error(trace, terr); + match (*sub, *sup) { + (ty::RePlaceholder(_), ty::RePlaceholder(_)) => {} + (ty::RePlaceholder(_), _) => { + note_and_explain_region( + self.tcx, + &mut err, + "", + sup, + " doesn't meet the lifetime requirements", + None, + ); + } + (_, ty::RePlaceholder(_)) => { + note_and_explain_region( + self.tcx, + &mut err, + "the required lifetime does not necessarily outlive ", + sub, + "", + None, + ); + } + _ => { + note_and_explain_region(self.tcx, &mut err, "", sup, "...", None); + note_and_explain_region( + self.tcx, + &mut err, + "...does not necessarily outlive ", + sub, + "", + None, + ); + } + } + err + } + infer::Reborrow(span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0312, + "lifetime of reference outlives lifetime of borrowed content..." + ); + note_and_explain_region( + self.tcx, + &mut err, + "...the reference is valid for ", + sub, + "...", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "...but the borrowed content is only valid for ", + sup, + "", + None, + ); + err + } + infer::ReborrowUpvar(span, ref upvar_id) => { + let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id); + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0313, + "lifetime of borrowed pointer outlives lifetime of captured variable `{}`...", + var_name + ); + note_and_explain_region( + self.tcx, + &mut err, + "...the borrowed pointer is valid for ", + sub, + "...", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + &format!("...but `{}` is only valid for ", var_name), + sup, + "", + None, + ); + err + } + infer::RelateObjectBound(span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0476, + "lifetime of the source pointer does not outlive lifetime bound of the \ + object type" + ); + note_and_explain_region( + self.tcx, + &mut err, + "object type is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "source pointer is only valid for ", + sup, + "", + None, + ); + err + } + infer::RelateParamBound(span, ty, opt_span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0477, + "the type `{}` does not fulfill the required lifetime", + self.ty_to_string(ty) + ); + match *sub { + ty::ReStatic => note_and_explain_region( + self.tcx, + &mut err, + "type must satisfy ", + sub, + if opt_span.is_some() { " as required by this binding" } else { "" }, + opt_span, + ), + _ => note_and_explain_region( + self.tcx, + &mut err, + "type must outlive ", + sub, + if opt_span.is_some() { " as required by this binding" } else { "" }, + opt_span, + ), + } + err + } + infer::RelateRegionParamBound(span) => { + let mut err = + struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied"); + note_and_explain_region( + self.tcx, + &mut err, + "lifetime parameter instantiated with ", + sup, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but lifetime parameter must outlive ", + sub, + "", + None, + ); + err + } + infer::DataBorrowed(ty, span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0490, + "a value of type `{}` is borrowed for too long", + self.ty_to_string(ty) + ); + note_and_explain_region( + self.tcx, + &mut err, + "the type is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but the borrow lasts for ", + sup, + "", + None, + ); + err + } + infer::ReferenceOutlivesReferent(ty, span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0491, + "in type `{}`, reference has a longer lifetime than the data it references", + self.ty_to_string(ty) + ); + note_and_explain_region( + self.tcx, + &mut err, + "the pointer is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but the referenced data is only valid for ", + sup, + "", + None, + ); + err + } + infer::CompareImplItemObligation { span, impl_item_def_id, trait_item_def_id } => self + .report_extra_impl_obligation( + span, + impl_item_def_id, + trait_item_def_id, + &format!("`{}: {}`", sup, sub), + ), + infer::CheckAssociatedTypeBounds { impl_item_def_id, trait_item_def_id, parent } => { + let mut err = self.report_concrete_failure(*parent, sub, sup); + + let trait_item_span = self.tcx.def_span(trait_item_def_id); + let item_name = self.tcx.item_name(impl_item_def_id.to_def_id()); + err.span_label( + trait_item_span, + format!("definition of `{}` from trait", item_name), + ); + + let trait_predicates = self.tcx.explicit_predicates_of(trait_item_def_id); + let impl_predicates = self.tcx.explicit_predicates_of(impl_item_def_id); + + let impl_predicates: rustc_data_structures::fx::FxHashSet<_> = + impl_predicates.predicates.into_iter().map(|(pred, _)| pred).collect(); + let clauses: Vec<_> = trait_predicates + .predicates + .into_iter() + .filter(|&(pred, _)| !impl_predicates.contains(pred)) + .map(|(pred, _)| format!("{}", pred)) + .collect(); + + if !clauses.is_empty() { + let generics = self.tcx.hir().get_generics(impl_item_def_id).unwrap(); + let where_clause_span = generics.tail_span_for_predicate_suggestion(); + + let suggestion = format!( + "{} {}", + generics.add_where_or_trailing_comma(), + clauses.join(", "), + ); + err.span_suggestion( + where_clause_span, + &format!( + "try copying {} from the trait", + if clauses.len() > 1 { "these clauses" } else { "this clause" } + ), + suggestion, + rustc_errors::Applicability::MaybeIncorrect, + ); + } + + err + } + infer::AscribeUserTypeProvePredicate(span) => { + let mut err = + struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied"); + note_and_explain_region( + self.tcx, + &mut err, + "lifetime instantiated with ", + sup, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but lifetime must outlive ", + sub, + "", + None, + ); + err + } + } + } + + pub(super) fn report_placeholder_failure( + &self, + placeholder_origin: SubregionOrigin<'tcx>, + sub: Region<'tcx>, + sup: Region<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + // I can't think how to do better than this right now. -nikomatsakis + debug!(?placeholder_origin, ?sub, ?sup, "report_placeholder_failure"); + match placeholder_origin { + infer::Subtype(box ref trace) + if matches!( + &trace.cause.code().peel_derives(), + ObligationCauseCode::BindingObligation(..) + | ObligationCauseCode::ExprBindingObligation(..) + ) => + { + // Hack to get around the borrow checker because trace.cause has an `Rc`. + if let ObligationCauseCode::BindingObligation(_, span) + | ObligationCauseCode::ExprBindingObligation(_, span, ..) = + &trace.cause.code().peel_derives() + { + let span = *span; + let mut err = self.report_concrete_failure(placeholder_origin, sub, sup); + err.span_note(span, "the lifetime requirement is introduced here"); + err + } else { + unreachable!() + } + } + infer::Subtype(box trace) => { + let terr = TypeError::RegionsPlaceholderMismatch; + return self.report_and_explain_type_error(trace, terr); + } + _ => return self.report_concrete_failure(placeholder_origin, sub, sup), + } + } +} diff --git a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs new file mode 100644 index 00000000000..73b5a2cc4ad --- /dev/null +++ b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs @@ -0,0 +1,672 @@ +use hir::def::CtorKind; +use hir::intravisit::{walk_expr, walk_stmt, Visitor}; +use rustc_data_structures::fx::FxIndexSet; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir as hir; +use rustc_middle::traits::{ + IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode, + StatementAsExpression, +}; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::{self as ty, Ty, TypeVisitable}; +use rustc_span::{sym, BytePos, Span}; + +use crate::errors::SuggAddLetForLetChains; + +use super::TypeErrCtxt; + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + pub(super) fn suggest_remove_semi_or_return_binding( + &self, + err: &mut Diagnostic, + first_id: Option<hir::HirId>, + first_ty: Ty<'tcx>, + first_span: Span, + second_id: Option<hir::HirId>, + second_ty: Ty<'tcx>, + second_span: Span, + ) { + let remove_semicolon = [ + (first_id, self.resolve_vars_if_possible(second_ty)), + (second_id, self.resolve_vars_if_possible(first_ty)), + ] + .into_iter() + .find_map(|(id, ty)| { + let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None }; + self.could_remove_semicolon(blk, ty) + }); + match remove_semicolon { + Some((sp, StatementAsExpression::NeedsBoxing)) => { + err.multipart_suggestion( + "consider removing this semicolon and boxing the expressions", + vec![ + (first_span.shrink_to_lo(), "Box::new(".to_string()), + (first_span.shrink_to_hi(), ")".to_string()), + (second_span.shrink_to_lo(), "Box::new(".to_string()), + (second_span.shrink_to_hi(), ")".to_string()), + (sp, String::new()), + ], + Applicability::MachineApplicable, + ); + } + Some((sp, StatementAsExpression::CorrectType)) => { + err.span_suggestion_short( + sp, + "consider removing this semicolon", + "", + Applicability::MachineApplicable, + ); + } + None => { + for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] { + if let Some(id) = id + && let hir::Node::Block(blk) = self.tcx.hir().get(id) + && self.consider_returning_binding(blk, ty, err) + { + break; + } + } + } + } + } + + pub(super) fn suggest_boxing_for_return_impl_trait( + &self, + err: &mut Diagnostic, + return_sp: Span, + arm_spans: impl Iterator<Item = Span>, + ) { + err.multipart_suggestion( + "you could change the return type to be a boxed trait object", + vec![ + (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()), + (return_sp.shrink_to_hi(), ">".to_string()), + ], + Applicability::MaybeIncorrect, + ); + let sugg = arm_spans + .flat_map(|sp| { + [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())] + .into_iter() + }) + .collect::<Vec<_>>(); + err.multipart_suggestion( + "if you change the return type to expect trait objects, box the returned expressions", + sugg, + Applicability::MaybeIncorrect, + ); + } + + pub(super) fn suggest_tuple_pattern( + &self, + cause: &ObligationCause<'tcx>, + exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, + diag: &mut Diagnostic, + ) { + // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with + // some modifications due to that being in typeck and this being in infer. + if let ObligationCauseCode::Pattern { .. } = cause.code() { + if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() { + let compatible_variants: Vec<_> = expected_adt + .variants() + .iter() + .filter(|variant| { + variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn) + }) + .filter_map(|variant| { + let sole_field = &variant.fields[0]; + let sole_field_ty = sole_field.ty(self.tcx, substs); + if self.same_type_modulo_infer(sole_field_ty, exp_found.found) { + let variant_path = + with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); + // FIXME #56861: DRYer prelude filtering + if let Some(path) = variant_path.strip_prefix("std::prelude::") { + if let Some((_, path)) = path.split_once("::") { + return Some(path.to_string()); + } + } + Some(variant_path) + } else { + None + } + }) + .collect(); + match &compatible_variants[..] { + [] => {} + [variant] => { + diag.multipart_suggestion_verbose( + &format!("try wrapping the pattern in `{}`", variant), + vec![ + (cause.span.shrink_to_lo(), format!("{}(", variant)), + (cause.span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + _ => { + // More than one matching variant. + diag.multipart_suggestions( + &format!( + "try wrapping the pattern in a variant of `{}`", + self.tcx.def_path_str(expected_adt.did()) + ), + compatible_variants.into_iter().map(|variant| { + vec![ + (cause.span.shrink_to_lo(), format!("{}(", variant)), + (cause.span.shrink_to_hi(), ")".to_string()), + ] + }), + Applicability::MaybeIncorrect, + ); + } + } + } + } + } + + /// A possible error is to forget to add `.await` when using futures: + /// + /// ```compile_fail,E0308 + /// async fn make_u32() -> u32 { + /// 22 + /// } + /// + /// fn take_u32(x: u32) {} + /// + /// async fn foo() { + /// let x = make_u32(); + /// take_u32(x); + /// } + /// ``` + /// + /// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the + /// expected type. If this is the case, and we are inside of an async body, it suggests adding + /// `.await` to the tail of the expression. + pub(super) fn suggest_await_on_expect_found( + &self, + cause: &ObligationCause<'tcx>, + exp_span: Span, + exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, + diag: &mut Diagnostic, + ) { + debug!( + "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}", + exp_span, exp_found.expected, exp_found.found, + ); + + if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() { + return; + } + + match ( + self.get_impl_future_output_ty(exp_found.expected), + self.get_impl_future_output_ty(exp_found.found), + ) { + (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause + .code() + { + ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { + let then_span = self.find_block_span_from_hir_id(*then_id); + diag.multipart_suggestion( + "consider `await`ing on both `Future`s", + vec![ + (then_span.shrink_to_hi(), ".await".to_string()), + (exp_span.shrink_to_hi(), ".await".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + prior_arms, + .. + }) => { + if let [.., arm_span] = &prior_arms[..] { + diag.multipart_suggestion( + "consider `await`ing on both `Future`s", + vec![ + (arm_span.shrink_to_hi(), ".await".to_string()), + (exp_span.shrink_to_hi(), ".await".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else { + diag.help("consider `await`ing on both `Future`s"); + } + } + _ => { + diag.help("consider `await`ing on both `Future`s"); + } + }, + (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => { + diag.span_suggestion_verbose( + exp_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code() + { + ObligationCauseCode::Pattern { span: Some(then_span), .. } => { + diag.span_suggestion_verbose( + then_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { + let then_span = self.find_block_span_from_hir_id(*then_id); + diag.span_suggestion_verbose( + then_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + ref prior_arms, + .. + }) => { + diag.multipart_suggestion_verbose( + "consider `await`ing on the `Future`", + prior_arms + .iter() + .map(|arm| (arm.shrink_to_hi(), ".await".to_string())) + .collect(), + Applicability::MaybeIncorrect, + ); + } + _ => {} + }, + _ => {} + } + } + + pub(super) fn suggest_accessing_field_where_appropriate( + &self, + cause: &ObligationCause<'tcx>, + exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, + diag: &mut Diagnostic, + ) { + debug!( + "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})", + cause, exp_found + ); + if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() { + if expected_def.is_enum() { + return; + } + + if let Some((name, ty)) = expected_def + .non_enum_variant() + .fields + .iter() + .filter(|field| field.vis.is_accessible_from(field.did, self.tcx)) + .map(|field| (field.name, field.ty(self.tcx, expected_substs))) + .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found)) + { + if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() { + if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { + let suggestion = if expected_def.is_struct() { + format!("{}.{}", snippet, name) + } else if expected_def.is_union() { + format!("unsafe {{ {}.{} }}", snippet, name) + } else { + return; + }; + diag.span_suggestion( + span, + &format!( + "you might have meant to use field `{}` whose type is `{}`", + name, ty + ), + suggestion, + Applicability::MaybeIncorrect, + ); + } + } + } + } + } + + /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate, + /// suggests it. + pub(super) fn suggest_as_ref_where_appropriate( + &self, + span: Span, + exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, + diag: &mut Diagnostic, + ) { + if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found) + { + diag.span_suggestion( + span, + msg, + // HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&` + format!("{}.as_ref()", snippet.trim_start_matches('&')), + Applicability::MachineApplicable, + ); + } + } + + pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> { + if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) = + (expected.kind(), found.kind()) + { + if let ty::Adt(found_def, found_substs) = *found_ty.kind() { + if exp_def == &found_def { + let have_as_ref = &[ + ( + sym::Option, + "you can convert from `&Option<T>` to `Option<&T>` using \ + `.as_ref()`", + ), + ( + sym::Result, + "you can convert from `&Result<T, E>` to \ + `Result<&T, &E>` using `.as_ref()`", + ), + ]; + if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| { + self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg) + }) { + let mut show_suggestion = true; + for (exp_ty, found_ty) in + std::iter::zip(exp_substs.types(), found_substs.types()) + { + match *exp_ty.kind() { + ty::Ref(_, exp_ty, _) => { + match (exp_ty.kind(), found_ty.kind()) { + (_, ty::Param(_)) + | (_, ty::Infer(_)) + | (ty::Param(_), _) + | (ty::Infer(_), _) => {} + _ if self.same_type_modulo_infer(exp_ty, found_ty) => {} + _ => show_suggestion = false, + }; + } + ty::Param(_) | ty::Infer(_) => {} + _ => show_suggestion = false, + } + } + if show_suggestion { + return Some(*msg); + } + } + } + } + } + None + } + + /// Try to find code with pattern `if Some(..) = expr` + /// use a `visitor` to mark the `if` which its span contains given error span, + /// and then try to find a assignment in the `cond` part, which span is equal with error span + pub(super) fn suggest_let_for_letchains( + &self, + err: &mut Diagnostic, + cause: &ObligationCause<'_>, + span: Span, + ) { + let hir = self.tcx.hir(); + let fn_hir_id = hir.get_parent_node(cause.body_id); + if let Some(node) = self.tcx.hir().find(fn_hir_id) && + let hir::Node::Item(hir::Item { + kind: hir::ItemKind::Fn(_sig, _, body_id), .. + }) = node { + let body = hir.body(*body_id); + + /// Find the if expression with given span + struct IfVisitor { + pub result: bool, + pub found_if: bool, + pub err_span: Span, + } + + impl<'v> Visitor<'v> for IfVisitor { + fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { + if self.result { return; } + match ex.kind { + hir::ExprKind::If(cond, _, _) => { + self.found_if = true; + walk_expr(self, cond); + self.found_if = false; + } + _ => walk_expr(self, ex), + } + } + + fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) { + if let hir::StmtKind::Local(hir::Local { + span, pat: hir::Pat{..}, ty: None, init: Some(_), .. + }) = &ex.kind + && self.found_if + && span.eq(&self.err_span) { + self.result = true; + } + walk_stmt(self, ex); + } + + fn visit_body(&mut self, body: &'v hir::Body<'v>) { + hir::intravisit::walk_body(self, body); + } + } + + let mut visitor = IfVisitor { err_span: span, found_if: false, result: false }; + visitor.visit_body(&body); + if visitor.result { + err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()}); + } + } + } +} + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + /// Be helpful when the user wrote `{... expr; }` and taking the `;` off + /// is enough to fix the error. + pub fn could_remove_semicolon( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + ) -> Option<(Span, StatementAsExpression)> { + let blk = blk.innermost_block(); + // Do not suggest if we have a tail expr. + if blk.expr.is_some() { + return None; + } + let last_stmt = blk.stmts.last()?; + let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else { + return None; + }; + let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?; + let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) { + _ if last_expr_ty.references_error() => return None, + _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => { + StatementAsExpression::CorrectType + } + (ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _)) + if last_def_id == exp_def_id => + { + StatementAsExpression::CorrectType + } + (ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => { + debug!( + "both opaque, likely future {:?} {:?} {:?} {:?}", + last_def_id, last_bounds, exp_def_id, exp_bounds + ); + + let last_local_id = last_def_id.as_local()?; + let exp_local_id = exp_def_id.as_local()?; + + match ( + &self.tcx.hir().expect_item(last_local_id).kind, + &self.tcx.hir().expect_item(exp_local_id).kind, + ) { + ( + hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }), + hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }), + ) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| { + match (left, right) { + ( + hir::GenericBound::Trait(tl, ml), + hir::GenericBound::Trait(tr, mr), + ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id() + && ml == mr => + { + true + } + ( + hir::GenericBound::LangItemTrait(langl, _, _, argsl), + hir::GenericBound::LangItemTrait(langr, _, _, argsr), + ) if langl == langr => { + // FIXME: consider the bounds! + debug!("{:?} {:?}", argsl, argsr); + true + } + _ => false, + } + }) => + { + StatementAsExpression::NeedsBoxing + } + _ => StatementAsExpression::CorrectType, + } + } + _ => return None, + }; + let span = if last_stmt.span.from_expansion() { + let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span); + self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)? + } else { + last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1)) + }; + Some((span, needs_box)) + } + + /// Suggest returning a local binding with a compatible type if the block + /// has no return expression. + pub fn consider_returning_binding( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + err: &mut Diagnostic, + ) -> bool { + let blk = blk.innermost_block(); + // Do not suggest if we have a tail expr. + if blk.expr.is_some() { + return false; + } + let mut shadowed = FxIndexSet::default(); + let mut candidate_idents = vec![]; + let mut find_compatible_candidates = |pat: &hir::Pat<'_>| { + if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind + && let Some(pat_ty) = self + .typeck_results + .as_ref() + .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id)) + { + let pat_ty = self.resolve_vars_if_possible(pat_ty); + if self.same_type_modulo_infer(pat_ty, expected_ty) + && !(pat_ty, expected_ty).references_error() + && shadowed.insert(ident.name) + { + candidate_idents.push((*ident, pat_ty)); + } + } + true + }; + + let hir = self.tcx.hir(); + for stmt in blk.stmts.iter().rev() { + let hir::StmtKind::Local(local) = &stmt.kind else { continue; }; + local.pat.walk(&mut find_compatible_candidates); + } + match hir.find(hir.get_parent_node(blk.hir_id)) { + Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => { + match hir.find(hir.get_parent_node(*hir_id)) { + Some(hir::Node::Arm(hir::Arm { pat, .. })) => { + pat.walk(&mut find_compatible_candidates); + } + Some( + hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. }) + | hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(_, body), + .. + }) + | hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)), + .. + }) + | hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(hir::Closure { body, .. }), + .. + }), + ) => { + for param in hir.body(*body).params { + param.pat.walk(&mut find_compatible_candidates); + } + } + Some(hir::Node::Expr(hir::Expr { + kind: + hir::ExprKind::If( + hir::Expr { kind: hir::ExprKind::Let(let_), .. }, + then_block, + _, + ), + .. + })) if then_block.hir_id == *hir_id => { + let_.pat.walk(&mut find_compatible_candidates); + } + _ => {} + } + } + _ => {} + } + + match &candidate_idents[..] { + [(ident, _ty)] => { + let sm = self.tcx.sess.source_map(); + if let Some(stmt) = blk.stmts.last() { + let stmt_span = sm.stmt_span(stmt.span, blk.span); + let sugg = if sm.is_multiline(blk.span) + && let Some(spacing) = sm.indentation_before(stmt_span) + { + format!("\n{spacing}{ident}") + } else { + format!(" {ident}") + }; + err.span_suggestion_verbose( + stmt_span.shrink_to_hi(), + format!("consider returning the local binding `{ident}`"), + sugg, + Applicability::MaybeIncorrect, + ); + } else { + let sugg = if sm.is_multiline(blk.span) + && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo()) + { + format!("\n{spacing} {ident}\n{spacing}") + } else { + format!(" {ident} ") + }; + let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi(); + err.span_suggestion_verbose( + sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span), + format!("consider returning the local binding `{ident}`"), + sugg, + Applicability::MaybeIncorrect, + ); + } + true + } + values if (1..3).contains(&values.len()) => { + let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>(); + err.span_note(spans, "consider returning one of these bindings"); + true + } + _ => false, + } + } +} diff --git a/compiler/rustc_infer/src/infer/note.rs b/compiler/rustc_infer/src/infer/note.rs new file mode 100644 index 00000000000..2ccbd164faa --- /dev/null +++ b/compiler/rustc_infer/src/infer/note.rs @@ -0,0 +1,203 @@ +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + fn note_error_origin( + &self, + err: &mut Diagnostic, + cause: &ObligationCause<'tcx>, + exp_found: Option<ty::error::ExpectedFound<Ty<'tcx>>>, + terr: TypeError<'tcx>, + ) { + match *cause.code() { + ObligationCauseCode::Pattern { origin_expr: true, span: Some(span), root_ty } => { + let ty = self.resolve_vars_if_possible(root_ty); + if !matches!(ty.kind(), ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_))) + { + // don't show type `_` + if span.desugaring_kind() == Some(DesugaringKind::ForLoop) + && let ty::Adt(def, substs) = ty.kind() + && Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option) + { + err.span_label(span, format!("this is an iterator with items of type `{}`", substs.type_at(0))); + } else { + err.span_label(span, format!("this expression has type `{}`", ty)); + } + } + if let Some(ty::error::ExpectedFound { found, .. }) = exp_found + && ty.is_box() && ty.boxed_ty() == found + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + { + err.span_suggestion( + span, + "consider dereferencing the boxed value", + format!("*{}", snippet), + Applicability::MachineApplicable, + ); + } + } + ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => { + err.span_label(span, "expected due to this"); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + arm_block_id, + arm_span, + arm_ty, + prior_arm_block_id, + prior_arm_span, + prior_arm_ty, + source, + ref prior_arms, + scrut_hir_id, + opt_suggest_box_span, + scrut_span, + .. + }) => match source { + hir::MatchSource::TryDesugar => { + if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found { + let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id); + let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind { + let arg_expr = args.first().expect("try desugaring call w/out arg"); + self.typeck_results.as_ref().and_then(|typeck_results| { + typeck_results.expr_ty_opt(arg_expr) + }) + } else { + bug!("try desugaring w/out call expr as scrutinee"); + }; + + match scrut_ty { + Some(ty) if expected == ty => { + let source_map = self.tcx.sess.source_map(); + err.span_suggestion( + source_map.end_point(cause.span), + "try removing this `?`", + "", + Applicability::MachineApplicable, + ); + } + _ => {} + } + } + } + _ => { + // `prior_arm_ty` can be `!`, `expected` will have better info when present. + let t = self.resolve_vars_if_possible(match exp_found { + Some(ty::error::ExpectedFound { expected, .. }) => expected, + _ => prior_arm_ty, + }); + let source_map = self.tcx.sess.source_map(); + let mut any_multiline_arm = source_map.is_multiline(arm_span); + if prior_arms.len() <= 4 { + for sp in prior_arms { + any_multiline_arm |= source_map.is_multiline(*sp); + err.span_label(*sp, format!("this is found to be of type `{}`", t)); + } + } else if let Some(sp) = prior_arms.last() { + any_multiline_arm |= source_map.is_multiline(*sp); + err.span_label( + *sp, + format!("this and all prior arms are found to be of type `{}`", t), + ); + } + let outer_error_span = if any_multiline_arm { + // Cover just `match` and the scrutinee expression, not + // the entire match body, to reduce diagram noise. + cause.span.shrink_to_lo().to(scrut_span) + } else { + cause.span + }; + let msg = "`match` arms have incompatible types"; + err.span_label(outer_error_span, msg); + self.suggest_remove_semi_or_return_binding( + err, + prior_arm_block_id, + prior_arm_ty, + prior_arm_span, + arm_block_id, + arm_ty, + arm_span, + ); + if let Some(ret_sp) = opt_suggest_box_span { + // Get return type span and point to it. + self.suggest_boxing_for_return_impl_trait( + err, + ret_sp, + prior_arms.iter().chain(std::iter::once(&arm_span)).map(|s| *s), + ); + } + } + }, + ObligationCauseCode::IfExpression(box IfExpressionCause { + then_id, + else_id, + then_ty, + else_ty, + outer_span, + opt_suggest_box_span, + }) => { + let then_span = self.find_block_span_from_hir_id(then_id); + let else_span = self.find_block_span_from_hir_id(else_id); + err.span_label(then_span, "expected because of this"); + if let Some(sp) = outer_span { + err.span_label(sp, "`if` and `else` have incompatible types"); + } + self.suggest_remove_semi_or_return_binding( + err, + Some(then_id), + then_ty, + then_span, + Some(else_id), + else_ty, + else_span, + ); + if let Some(ret_sp) = opt_suggest_box_span { + self.suggest_boxing_for_return_impl_trait( + err, + ret_sp, + [then_span, else_span].into_iter(), + ); + } + } + ObligationCauseCode::LetElse => { + err.help("try adding a diverging expression, such as `return` or `panic!(..)`"); + err.help("...or use `match` instead of `let...else`"); + } + _ => { + if let ObligationCauseCode::BindingObligation(_, span) + | ObligationCauseCode::ExprBindingObligation(_, span, ..) + = cause.code().peel_derives() + && let TypeError::RegionsPlaceholderMismatch = terr + { + err.span_note( * span, + "the lifetime requirement is introduced here"); + } + } + } + } +} + +impl<'tcx> InferCtxt<'tcx> { + /// Given a [`hir::Block`], get the span of its last expression or + /// statement, peeling off any inner blocks. + pub fn find_block_span(&self, block: &'tcx hir::Block<'tcx>) -> Span { + let block = block.innermost_block(); + if let Some(expr) = &block.expr { + expr.span + } else if let Some(stmt) = block.stmts.last() { + // possibly incorrect trailing `;` in the else arm + stmt.span + } else { + // empty block; point at its entirety + block.span + } + } + + /// Given a [`hir::HirId`] for a block, get the span of its last expression + /// or statement, peeling off any inner blocks. + pub fn find_block_span_from_hir_id(&self, hir_id: hir::HirId) -> Span { + match self.tcx.hir().get(hir_id) { + hir::Node::Block(blk) => self.find_block_span(blk), + // The parser was in a weird state if either of these happen, but + // it's better not to panic. + hir::Node::Expr(e) => e.span, + _ => rustc_span::DUMMY_SP, + } + } +} diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index f75c8669fa1..f808c1438bf 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -158,7 +158,7 @@ pub fn create_resolver( sess: Lrc<Session>, metadata_loader: Box<MetadataLoaderDyn>, krate: &ast::Crate, - crate_name: &str, + crate_name: Symbol, ) -> BoxedResolver { trace!("create_resolver"); BoxedResolver::new(sess, move |sess, resolver_arenas| { @@ -171,7 +171,7 @@ pub fn register_plugins<'a>( metadata_loader: &'a dyn MetadataLoader, register_lints: impl Fn(&Session, &mut LintStore), mut krate: ast::Crate, - crate_name: &str, + crate_name: Symbol, ) -> Result<(ast::Crate, LintStore)> { krate = sess.time("attributes_injection", || { rustc_builtin_macros::cmdline_attrs::inject( @@ -228,19 +228,21 @@ fn pre_expansion_lint<'a>( lint_store: &LintStore, registered_tools: &RegisteredTools, check_node: impl EarlyCheckNode<'a>, - node_name: &str, + node_name: Symbol, ) { - sess.prof.generic_activity_with_arg("pre_AST_expansion_lint_checks", node_name).run(|| { - rustc_lint::check_ast_node( - sess, - true, - lint_store, - registered_tools, - None, - rustc_lint::BuiltinCombinedPreExpansionLintPass::new(), - check_node, - ); - }); + sess.prof.generic_activity_with_arg("pre_AST_expansion_lint_checks", node_name.as_str()).run( + || { + rustc_lint::check_ast_node( + sess, + true, + lint_store, + registered_tools, + None, + rustc_lint::BuiltinCombinedPreExpansionLintPass::new(), + check_node, + ); + }, + ); } // Cannot implement directly for `LintStore` due to trait coherence. @@ -254,7 +256,7 @@ impl LintStoreExpand for LintStoreExpandImpl<'_> { node_id: ast::NodeId, attrs: &[ast::Attribute], items: &[rustc_ast::ptr::P<ast::Item>], - name: &str, + name: Symbol, ) { pre_expansion_lint(sess, self.0, registered_tools, (node_id, attrs, items), name); } @@ -268,7 +270,7 @@ pub fn configure_and_expand( sess: &Session, lint_store: &LintStore, mut krate: ast::Crate, - crate_name: &str, + crate_name: Symbol, resolver: &mut Resolver<'_>, ) -> Result<ast::Crate> { trace!("configure_and_expand"); @@ -462,7 +464,7 @@ fn generated_output_paths( sess: &Session, outputs: &OutputFilenames, exact_name: bool, - crate_name: &str, + crate_name: Symbol, ) -> Vec<PathBuf> { let mut out_filenames = Vec::new(); for output_type in sess.opts.output_types.keys() { @@ -661,7 +663,7 @@ pub fn prepare_outputs( compiler: &Compiler, krate: &ast::Crate, boxed_resolver: &RefCell<BoxedResolver>, - crate_name: &str, + crate_name: Symbol, ) -> Result<OutputFilenames> { let _timer = sess.timer("prepare_outputs"); @@ -771,7 +773,7 @@ pub fn create_global_ctxt<'tcx>( dep_graph: DepGraph, resolver: Rc<RefCell<BoxedResolver>>, outputs: OutputFilenames, - crate_name: &str, + crate_name: Symbol, queries: &'tcx OnceCell<TcxQueries<'tcx>>, global_ctxt: &'tcx OnceCell<GlobalCtxt<'tcx>>, arena: &'tcx WorkerLocal<Arena<'tcx>>, diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index f5ddd647b24..39e1f2204b0 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -17,6 +17,7 @@ use rustc_query_impl::Queries as TcxQueries; use rustc_session::config::{self, OutputFilenames, OutputType}; use rustc_session::{output::find_crate_name, Session}; use rustc_span::symbol::sym; +use rustc_span::Symbol; use std::any::Any; use std::cell::{Ref, RefCell, RefMut}; use std::rc::Rc; @@ -74,7 +75,7 @@ pub struct Queries<'tcx> { dep_graph_future: Query<Option<DepGraphFuture>>, parse: Query<ast::Crate>, - crate_name: Query<String>, + crate_name: Query<Symbol>, register_plugins: Query<(ast::Crate, Lrc<LintStore>)>, expansion: Query<(Lrc<ast::Crate>, Rc<RefCell<BoxedResolver>>, Lrc<LintStore>)>, dep_graph: Query<DepGraph>, @@ -135,7 +136,7 @@ impl<'tcx> Queries<'tcx> { &*self.codegen_backend().metadata_loader(), self.compiler.register_lints.as_deref().unwrap_or_else(|| empty), krate, - &crate_name, + crate_name, )?; // Compute the dependency graph (in the background). We want to do @@ -149,7 +150,7 @@ impl<'tcx> Queries<'tcx> { }) } - pub fn crate_name(&self) -> Result<&Query<String>> { + pub fn crate_name(&self) -> Result<&Query<Symbol>> { self.crate_name.compute(|| { Ok({ let parse_result = self.parse()?; @@ -165,7 +166,7 @@ impl<'tcx> Queries<'tcx> { ) -> Result<&Query<(Lrc<ast::Crate>, Rc<RefCell<BoxedResolver>>, Lrc<LintStore>)>> { trace!("expansion"); self.expansion.compute(|| { - let crate_name = self.crate_name()?.peek().clone(); + let crate_name = *self.crate_name()?.peek(); let (krate, lint_store) = self.register_plugins()?.take(); let _timer = self.session().timer("configure_and_expand"); let sess = self.session(); @@ -173,10 +174,10 @@ impl<'tcx> Queries<'tcx> { sess.clone(), self.codegen_backend().metadata_loader(), &krate, - &crate_name, + crate_name, ); let krate = resolver.access(|resolver| { - passes::configure_and_expand(sess, &lint_store, krate, &crate_name, resolver) + passes::configure_and_expand(sess, &lint_store, krate, crate_name, resolver) })?; Ok((Lrc::new(krate), Rc::new(RefCell::new(resolver)), lint_store)) }) @@ -201,20 +202,20 @@ impl<'tcx> Queries<'tcx> { pub fn prepare_outputs(&self) -> Result<&Query<OutputFilenames>> { self.prepare_outputs.compute(|| { let (krate, boxed_resolver, _) = &*self.expansion()?.peek(); - let crate_name = self.crate_name()?.peek(); + let crate_name = *self.crate_name()?.peek(); passes::prepare_outputs( self.session(), self.compiler, krate, &*boxed_resolver, - &crate_name, + crate_name, ) }) } pub fn global_ctxt(&'tcx self) -> Result<&Query<QueryContext<'tcx>>> { self.global_ctxt.compute(|| { - let crate_name = self.crate_name()?.peek().clone(); + let crate_name = *self.crate_name()?.peek(); let outputs = self.prepare_outputs()?.take(); let dep_graph = self.dep_graph()?.peek().clone(); let (krate, resolver, lint_store) = self.expansion()?.take(); @@ -225,7 +226,7 @@ impl<'tcx> Queries<'tcx> { dep_graph, resolver, outputs, - &crate_name, + crate_name, &self.queries, &self.gcx, &self.arena, diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 1a2389c7a84..efeaac8fe9a 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -245,7 +245,7 @@ impl<'a> CrateLoader<'a> { pub fn new( sess: &'a Session, metadata_loader: Box<MetadataLoaderDyn>, - local_crate_name: &str, + local_crate_name: Symbol, ) -> Self { let mut stable_crate_ids = FxHashMap::default(); stable_crate_ids.insert(sess.local_stable_crate_id(), LOCAL_CRATE); @@ -253,7 +253,7 @@ impl<'a> CrateLoader<'a> { CrateLoader { sess, metadata_loader, - local_crate_name: Symbol::intern(local_crate_name), + local_crate_name, cstore: CStore { // We add an empty entry for LOCAL_CRATE (which maps to zero) in // order to make array indices in `metas` match with the @@ -1000,7 +1000,7 @@ impl<'a> CrateLoader<'a> { ); let name = match orig_name { Some(orig_name) => { - validate_crate_name(self.sess, orig_name.as_str(), Some(item.span)); + validate_crate_name(self.sess, orig_name, Some(item.span)); orig_name } None => item.ident.name, diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index 4fa440c7ca6..7601f6bd322 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -61,8 +61,7 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) { .unwrap_or(MetadataKind::None); let crate_name = tcx.crate_name(LOCAL_CRATE); - let out_filename = - filename_for_metadata(tcx.sess, crate_name.as_str(), tcx.output_filenames(())); + let out_filename = filename_for_metadata(tcx.sess, crate_name, tcx.output_filenames(())); // To avoid races with another rustc process scanning the output directory, // we need to write the file somewhere else and atomically move it to its // final destination, with an `fs::rename` call. In order for the rename to diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 988d27ba262..5ba053820e0 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -408,7 +408,7 @@ impl std::fmt::Display for NonDivergingIntrinsic<'_> { #[derive(Copy, Clone, TyEncodable, TyDecodable, Debug, PartialEq, Eq, Hash, HashStable)] #[rustc_pass_by_value] pub enum RetagKind { - /// The initial retag when entering a function. + /// The initial retag of arguments when entering a function. FnEntry, /// Retag preparing for a two-phase borrow. TwoPhase, diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index af573376a26..b44bc14ec26 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1285,7 +1285,7 @@ impl<'tcx> TyCtxt<'tcx> { on_disk_cache: Option<&'tcx dyn OnDiskCache<'tcx>>, queries: &'tcx dyn query::QueryEngine<'tcx>, query_kinds: &'tcx [DepKindStruct<'tcx>], - crate_name: &str, + crate_name: Symbol, output_filenames: OutputFilenames, ) -> GlobalCtxt<'tcx> { let data_layout = s.target.parse_data_layout().unwrap_or_else(|err| { @@ -1325,7 +1325,7 @@ impl<'tcx> TyCtxt<'tcx> { pred_rcache: Default::default(), selection_cache: Default::default(), evaluation_cache: Default::default(), - crate_name: Symbol::intern(crate_name), + crate_name, data_layout, alloc_map: Lock::new(interpret::AllocMap::new()), output_filenames: Arc::new(output_filenames), diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index d83e17574a0..aa61c39b8d8 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -986,23 +986,32 @@ fn foo(&self) -> Self::T { String::new() } } pub fn short_ty_string(self, ty: Ty<'tcx>) -> (String, Option<PathBuf>) { - let length_limit = 50; - let type_limit = 4; + let width = self.sess.diagnostic_width(); + let length_limit = width.saturating_sub(30); + let mut type_limit = 50; let regular = FmtPrinter::new(self, hir::def::Namespace::TypeNS) .pretty_print_type(ty) .expect("could not write to `String`") .into_buffer(); - if regular.len() <= length_limit { + if regular.len() <= width { return (regular, None); } - let short = FmtPrinter::new_with_limit( - self, - hir::def::Namespace::TypeNS, - rustc_session::Limit(type_limit), - ) - .pretty_print_type(ty) - .expect("could not write to `String`") - .into_buffer(); + let mut short; + loop { + // Look for the longest properly trimmed path that still fits in lenght_limit. + short = FmtPrinter::new_with_limit( + self, + hir::def::Namespace::TypeNS, + rustc_session::Limit(type_limit), + ) + .pretty_print_type(ty) + .expect("could not write to `String`") + .into_buffer(); + if short.len() <= length_limit || type_limit == 0 { + break; + } + type_limit -= 1; + } if regular == short { return (regular, None); } diff --git a/compiler/rustc_mir_transform/src/add_retag.rs b/compiler/rustc_mir_transform/src/add_retag.rs index 036b5589849..3d22035f078 100644 --- a/compiler/rustc_mir_transform/src/add_retag.rs +++ b/compiler/rustc_mir_transform/src/add_retag.rs @@ -10,16 +10,6 @@ use rustc_middle::ty::{self, Ty, TyCtxt}; pub struct AddRetag; -/// Determines whether this place is "stable": Whether, if we evaluate it again -/// after the assignment, we can be sure to obtain the same place value. -/// (Concurrent accesses by other threads are no problem as these are anyway non-atomic -/// copies. Data races are UB.) -fn is_stable(place: PlaceRef<'_>) -> bool { - // Which place this evaluates to can change with any memory write, - // so cannot assume deref to be stable. - !place.has_deref() -} - /// Determine whether this type may contain a reference (or box), and thus needs retagging. /// We will only recurse `depth` times into Tuples/ADTs to bound the cost of this. fn may_contain_reference<'tcx>(ty: Ty<'tcx>, depth: u32, tcx: TyCtxt<'tcx>) -> bool { @@ -69,22 +59,10 @@ impl<'tcx> MirPass<'tcx> for AddRetag { let basic_blocks = body.basic_blocks.as_mut(); let local_decls = &body.local_decls; let needs_retag = |place: &Place<'tcx>| { - // FIXME: Instead of giving up for unstable places, we should introduce - // a temporary and retag on that. - is_stable(place.as_ref()) + !place.has_deref() // we're not eally interested in stores to "outside" locations, they are hard to keep track of anyway && may_contain_reference(place.ty(&*local_decls, tcx).ty, /*depth*/ 3, tcx) && !local_decls[place.local].is_deref_temp() }; - let place_base_raw = |place: &Place<'tcx>| { - // If this is a `Deref`, get the type of what we are deref'ing. - if place.has_deref() { - let ty = &local_decls[place.local].ty; - ty.is_unsafe_ptr() - } else { - // Not a deref, and thus not raw. - false - } - }; // PART 1 // Retag arguments at the beginning of the start block. @@ -108,7 +86,7 @@ impl<'tcx> MirPass<'tcx> for AddRetag { } // PART 2 - // Retag return values of functions. Also escape-to-raw the argument of `drop`. + // Retag return values of functions. // We collect the return destinations because we cannot mutate while iterating. let returns = basic_blocks .iter_mut() @@ -140,30 +118,25 @@ impl<'tcx> MirPass<'tcx> for AddRetag { } // PART 3 - // Add retag after assignment. + // Add retag after assignments where data "enters" this function: the RHS is behind a deref and the LHS is not. for block_data in basic_blocks { // We want to insert statements as we iterate. To this end, we // iterate backwards using indices. for i in (0..block_data.statements.len()).rev() { let (retag_kind, place) = match block_data.statements[i].kind { - // Retag-as-raw after escaping to a raw pointer, if the referent - // is not already a raw pointer. - StatementKind::Assign(box (lplace, Rvalue::AddressOf(_, ref rplace))) - if !place_base_raw(rplace) => - { - (RetagKind::Raw, lplace) - } // Retag after assignments of reference type. StatementKind::Assign(box (ref place, ref rvalue)) if needs_retag(place) => { - let kind = match rvalue { - Rvalue::Ref(_, borrow_kind, _) - if borrow_kind.allows_two_phase_borrow() => - { - RetagKind::TwoPhase - } - _ => RetagKind::Default, + let add_retag = match rvalue { + // Ptr-creating operations already do their own internal retagging, no + // need to also add a retag statement. + Rvalue::Ref(..) | Rvalue::AddressOf(..) => false, + _ => true, }; - (kind, *place) + if add_retag { + (RetagKind::Default, *place) + } else { + continue; + } } // Do nothing for the rest _ => continue, diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs index 8922298ecaf..69f96fe48ea 100644 --- a/compiler/rustc_mir_transform/src/generator.rs +++ b/compiler/rustc_mir_transform/src/generator.rs @@ -985,16 +985,6 @@ fn create_generator_drop_shim<'tcx>( tcx.mk_ptr(ty::TypeAndMut { ty: gen_ty, mutbl: hir::Mutability::Mut }), source_info, ); - if tcx.sess.opts.unstable_opts.mir_emit_retag { - // Alias tracking must know we changed the type - body.basic_blocks_mut()[START_BLOCK].statements.insert( - 0, - Statement { - source_info, - kind: StatementKind::Retag(RetagKind::Raw, Box::new(Place::from(SELF_ARG))), - }, - ) - } // Make sure we remove dead blocks to remove // unrelated code from the resume part of the function diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs index a115bb2831a..16b7dcad17e 100644 --- a/compiler/rustc_mir_transform/src/shim.rs +++ b/compiler/rustc_mir_transform/src/shim.rs @@ -177,16 +177,6 @@ fn build_drop_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, ty: Option<Ty<'tcx>>) if ty.is_some() { // The first argument (index 0), but add 1 for the return value. let dropee_ptr = Place::from(Local::new(1 + 0)); - if tcx.sess.opts.unstable_opts.mir_emit_retag { - // Function arguments should be retagged, and we make this one raw. - body.basic_blocks_mut()[START_BLOCK].statements.insert( - 0, - Statement { - source_info, - kind: StatementKind::Retag(RetagKind::Raw, Box::new(dropee_ptr)), - }, - ); - } let patch = { let param_env = tcx.param_env_reveal_all_normalized(def_id); let mut elaborator = diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 4ef89cfb255..82f5d0f534a 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1196,7 +1196,7 @@ impl<'a> Resolver<'a> { pub fn new( session: &'a Session, krate: &Crate, - crate_name: &str, + crate_name: Symbol, metadata_loader: Box<MetadataLoaderDyn>, arenas: &'a ResolverArenas<'a>, ) -> Resolver<'a> { diff --git a/compiler/rustc_save_analysis/src/dump_visitor.rs b/compiler/rustc_save_analysis/src/dump_visitor.rs index b4528853825..9ae07cb005b 100644 --- a/compiler/rustc_save_analysis/src/dump_visitor.rs +++ b/compiler/rustc_save_analysis/src/dump_visitor.rs @@ -111,7 +111,7 @@ impl<'tcx> DumpVisitor<'tcx> { self.save_ctxt.lookup_def_id(ref_id) } - pub fn dump_crate_info(&mut self, name: &str) { + pub fn dump_crate_info(&mut self, name: Symbol) { let source_file = self.tcx.sess.local_crate_source_file.as_ref(); let crate_root = source_file.map(|source_file| { let source_file = Path::new(source_file); @@ -124,7 +124,7 @@ impl<'tcx> DumpVisitor<'tcx> { let data = CratePreludeData { crate_id: GlobalCrateId { - name: name.into(), + name: name.to_string(), disambiguator: (self.tcx.sess.local_stable_crate_id().to_u64(), 0), }, crate_root: crate_root.unwrap_or_else(|| "<no source>".to_owned()), @@ -135,7 +135,7 @@ impl<'tcx> DumpVisitor<'tcx> { self.dumper.crate_prelude(data); } - pub fn dump_compilation_options(&mut self, input: &Input, crate_name: &str) { + pub fn dump_compilation_options(&mut self, input: &Input, crate_name: Symbol) { // Apply possible `remap-path-prefix` remapping to the input source file // (and don't include remapping args anymore) let (program, arguments) = { diff --git a/compiler/rustc_save_analysis/src/lib.rs b/compiler/rustc_save_analysis/src/lib.rs index f05eb2b7432..7735c571310 100644 --- a/compiler/rustc_save_analysis/src/lib.rs +++ b/compiler/rustc_save_analysis/src/lib.rs @@ -95,7 +95,7 @@ impl<'tcx> SaveContext<'tcx> { } /// Returns path to the compilation output (e.g., libfoo-12345678.rmeta) - pub fn compilation_output(&self, crate_name: &str) -> PathBuf { + pub fn compilation_output(&self, crate_name: Symbol) -> PathBuf { let sess = &self.tcx.sess; // Save-analysis is emitted per whole session, not per each crate type let crate_type = sess.crate_types()[0]; @@ -894,8 +894,8 @@ pub struct DumpHandler<'a> { } impl<'a> DumpHandler<'a> { - pub fn new(odir: Option<&'a Path>, cratename: &str) -> DumpHandler<'a> { - DumpHandler { odir, cratename: cratename.to_owned() } + pub fn new(odir: Option<&'a Path>, cratename: Symbol) -> DumpHandler<'a> { + DumpHandler { odir, cratename: cratename.to_string() } } fn output_file(&self, ctx: &SaveContext<'_>) -> (BufWriter<File>, PathBuf) { @@ -960,7 +960,7 @@ impl SaveHandler for CallbackHandler<'_> { pub fn process_crate<'l, 'tcx, H: SaveHandler>( tcx: TyCtxt<'tcx>, - cratename: &str, + cratename: Symbol, input: &'l Input, config: Option<Config>, mut handler: H, diff --git a/compiler/rustc_session/Cargo.toml b/compiler/rustc_session/Cargo.toml index a052f293341..cbbba2252bf 100644 --- a/compiler/rustc_session/Cargo.toml +++ b/compiler/rustc_session/Cargo.toml @@ -18,6 +18,7 @@ rustc_fs_util = { path = "../rustc_fs_util" } rustc_ast = { path = "../rustc_ast" } rustc_lint_defs = { path = "../rustc_lint_defs" } smallvec = "1.8.1" +termize = "0.1.1" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 8cb9e1a6f1a..ee492f802a7 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -129,10 +129,10 @@ pub struct FileIsNotWriteable<'a> { #[derive(Diagnostic)] #[diag(session_crate_name_does_not_match)] -pub struct CrateNameDoesNotMatch<'a> { +pub struct CrateNameDoesNotMatch { #[primary_span] pub span: Span, - pub s: &'a str, + pub s: Symbol, pub name: Symbol, } @@ -151,11 +151,11 @@ pub struct CrateNameEmpty { #[derive(Diagnostic)] #[diag(session_invalid_character_in_create_name)] -pub struct InvalidCharacterInCrateName<'a> { +pub struct InvalidCharacterInCrateName { #[primary_span] pub span: Option<Span>, pub character: char, - pub crate_name: &'a str, + pub crate_name: Symbol, } #[derive(Subdiagnostic)] diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs index 2511bee46af..8ee3057de62 100644 --- a/compiler/rustc_session/src/output.rs +++ b/compiler/rustc_session/src/output.rs @@ -7,14 +7,14 @@ use crate::errors::{ use crate::Session; use rustc_ast as ast; use rustc_span::symbol::sym; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; use std::path::{Path, PathBuf}; pub fn out_filename( sess: &Session, crate_type: CrateType, outputs: &OutputFilenames, - crate_name: &str, + crate_name: Symbol, ) -> PathBuf { let default_filename = filename_for_input(sess, crate_type, crate_name, outputs); let out_filename = outputs @@ -45,9 +45,9 @@ fn is_writeable(p: &Path) -> bool { } } -pub fn find_crate_name(sess: &Session, attrs: &[ast::Attribute], input: &Input) -> String { - let validate = |s: String, span: Option<Span>| { - validate_crate_name(sess, &s, span); +pub fn find_crate_name(sess: &Session, attrs: &[ast::Attribute], input: &Input) -> Symbol { + let validate = |s: Symbol, span: Option<Span>| { + validate_crate_name(sess, s, span); s }; @@ -59,38 +59,39 @@ pub fn find_crate_name(sess: &Session, attrs: &[ast::Attribute], input: &Input) sess.find_by_name(attrs, sym::crate_name).and_then(|at| at.value_str().map(|s| (at, s))); if let Some(ref s) = sess.opts.crate_name { + let s = Symbol::intern(s); if let Some((attr, name)) = attr_crate_name { - if name.as_str() != s { + if name != s { sess.emit_err(CrateNameDoesNotMatch { span: attr.span, s, name }); } } - return validate(s.clone(), None); + return validate(s, None); } if let Some((attr, s)) = attr_crate_name { - return validate(s.to_string(), Some(attr.span)); + return validate(s, Some(attr.span)); } if let Input::File(ref path) = *input { if let Some(s) = path.file_stem().and_then(|s| s.to_str()) { if s.starts_with('-') { sess.emit_err(CrateNameInvalid { s }); } else { - return validate(s.replace('-', "_"), None); + return validate(Symbol::intern(&s.replace('-', "_")), None); } } } - "rust_out".to_string() + Symbol::intern("rust_out") } -pub fn validate_crate_name(sess: &Session, s: &str, sp: Option<Span>) { +pub fn validate_crate_name(sess: &Session, s: Symbol, sp: Option<Span>) { let mut err_count = 0; { if s.is_empty() { err_count += 1; sess.emit_err(CrateNameEmpty { span: sp }); } - for c in s.chars() { + for c in s.as_str().chars() { if c.is_alphanumeric() { continue; } @@ -109,7 +110,7 @@ pub fn validate_crate_name(sess: &Session, s: &str, sp: Option<Span>) { pub fn filename_for_metadata( sess: &Session, - crate_name: &str, + crate_name: Symbol, outputs: &OutputFilenames, ) -> PathBuf { // If the command-line specified the path, use that directly. @@ -132,7 +133,7 @@ pub fn filename_for_metadata( pub fn filename_for_input( sess: &Session, crate_type: CrateType, - crate_name: &str, + crate_name: Symbol, outputs: &OutputFilenames, ) -> PathBuf { let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index e99e460913e..4c049a8d628 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -952,6 +952,17 @@ impl Session { ) -> Option<Symbol> { attrs.iter().find(|at| at.has_name(name)).and_then(|at| at.value_str()) } + + pub fn diagnostic_width(&self) -> usize { + let default_column_width = 140; + if let Some(width) = self.opts.diagnostic_width { + width + } else if self.opts.unstable_opts.ui_testing { + default_column_width + } else { + termize::dimensions().map_or(default_column_width, |(w, _)| w) + } + } } // JUSTIFICATION: defn of the suggested wrapper fns diff --git a/compiler/rustc_span/src/def_id.rs b/compiler/rustc_span/src/def_id.rs index 0ad1f1a0da7..e62ce2c266a 100644 --- a/compiler/rustc_span/src/def_id.rs +++ b/compiler/rustc_span/src/def_id.rs @@ -1,4 +1,4 @@ -use crate::HashStableContext; +use crate::{HashStableContext, Symbol}; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey}; use rustc_data_structures::AtomicRef; @@ -149,9 +149,11 @@ impl StableCrateId { /// Computes the stable ID for a crate with the given name and /// `-Cmetadata` arguments. - pub fn new(crate_name: &str, is_exe: bool, mut metadata: Vec<String>) -> StableCrateId { + pub fn new(crate_name: Symbol, is_exe: bool, mut metadata: Vec<String>) -> StableCrateId { let mut hasher = StableHasher::new(); - crate_name.hash(&mut hasher); + // We must hash the string text of the crate name, not the id, as the id is not stable + // across builds. + crate_name.as_str().hash(&mut hasher); // We don't want the stable crate ID to depend on the order of // -C metadata arguments, so sort them: diff --git a/compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs b/compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs index 8dad941b534..06529c2e403 100644 --- a/compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs +++ b/compiler/rustc_target/src/spec/wasm32_unknown_unknown.rs @@ -33,12 +33,6 @@ pub fn target() -> Target { // For now this target just never has an entry symbol no matter the output // type, so unconditionally pass this. "--no-entry", - // Rust really needs a way for users to specify exports and imports in - // the source code. --export-dynamic isn't the right tool for this job, - // however it does have the side effect of automatically exporting a lot - // of symbols, which approximates what people want when compiling for - // wasm32-unknown-unknown expect, so use it for now. - "--export-dynamic", ], ); options.add_pre_link_args( @@ -48,7 +42,6 @@ pub fn target() -> Target { // otherwise "--target=wasm32-unknown-unknown", "-Wl,--no-entry", - "-Wl,--export-dynamic", ], ); diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index d3cfd61e195..ea4bf42c515 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -4,7 +4,6 @@ pub mod auto_trait; mod chalk_fulfill; -pub mod codegen; mod coherence; pub mod const_evaluatable; mod engine; @@ -20,9 +19,9 @@ mod select; mod specialize; mod structural_match; mod util; +mod vtable; pub mod wf; -use crate::errors::DumpVTableEntries; use crate::infer::outlives::env::OutlivesEnvironment; use crate::infer::{InferCtxt, TyCtxtInferExt}; use crate::traits::error_reporting::TypeErrCtxtExt as _; @@ -30,15 +29,11 @@ use crate::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_hir::def_id::DefId; -use rustc_hir::lang_items::LangItem; use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::visit::TypeVisitable; -use rustc_middle::ty::{ - self, DefIdTree, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeSuperVisitable, VtblEntry, -}; +use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeSuperVisitable}; use rustc_middle::ty::{InternalSubsts, SubstsRef}; -use rustc_span::{sym, Span}; -use smallvec::SmallVec; +use rustc_span::Span; use std::fmt::Debug; use std::ops::ControlFlow; @@ -567,369 +562,12 @@ fn is_impossible_method<'tcx>( false } -#[derive(Clone, Debug)] -enum VtblSegment<'tcx> { - MetadataDSA, - TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool }, -} - -/// Prepare the segments for a vtable -fn prepare_vtable_segments<'tcx, T>( - tcx: TyCtxt<'tcx>, - trait_ref: ty::PolyTraitRef<'tcx>, - mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow<T>, -) -> Option<T> { - // The following constraints holds for the final arrangement. - // 1. The whole virtual table of the first direct super trait is included as the - // the prefix. If this trait doesn't have any super traits, then this step - // consists of the dsa metadata. - // 2. Then comes the proper pointer metadata(vptr) and all own methods for all - // other super traits except those already included as part of the first - // direct super trait virtual table. - // 3. finally, the own methods of this trait. - - // This has the advantage that trait upcasting to the first direct super trait on each level - // is zero cost, and to another trait includes only replacing the pointer with one level indirection, - // while not using too much extra memory. - - // For a single inheritance relationship like this, - // D --> C --> B --> A - // The resulting vtable will consists of these segments: - // DSA, A, B, C, D - - // For a multiple inheritance relationship like this, - // D --> C --> A - // \-> B - // The resulting vtable will consists of these segments: - // DSA, A, B, B-vptr, C, D - - // For a diamond inheritance relationship like this, - // D --> B --> A - // \-> C -/ - // The resulting vtable will consists of these segments: - // DSA, A, B, C, C-vptr, D - - // For a more complex inheritance relationship like this: - // O --> G --> C --> A - // \ \ \-> B - // | |-> F --> D - // | \-> E - // |-> N --> J --> H - // \ \-> I - // |-> M --> K - // \-> L - // The resulting vtable will consists of these segments: - // DSA, A, B, B-vptr, C, D, D-vptr, E, E-vptr, F, F-vptr, G, - // H, H-vptr, I, I-vptr, J, J-vptr, K, K-vptr, L, L-vptr, M, M-vptr, - // N, N-vptr, O - - // emit dsa segment first. - if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::MetadataDSA) { - return Some(v); - } - - let mut emit_vptr_on_new_entry = false; - let mut visited = util::PredicateSet::new(tcx); - let predicate = trait_ref.without_const().to_predicate(tcx); - let mut stack: SmallVec<[(ty::PolyTraitRef<'tcx>, _, _); 5]> = - smallvec![(trait_ref, emit_vptr_on_new_entry, None)]; - visited.insert(predicate); - - // the main traversal loop: - // basically we want to cut the inheritance directed graph into a few non-overlapping slices of nodes - // that each node is emitted after all its descendents have been emitted. - // so we convert the directed graph into a tree by skipping all previously visited nodes using a visited set. - // this is done on the fly. - // Each loop run emits a slice - it starts by find a "childless" unvisited node, backtracking upwards, and it - // stops after it finds a node that has a next-sibling node. - // This next-sibling node will used as the starting point of next slice. - - // Example: - // For a diamond inheritance relationship like this, - // D#1 --> B#0 --> A#0 - // \-> C#1 -/ - - // Starting point 0 stack [D] - // Loop run #0: Stack after diving in is [D B A], A is "childless" - // after this point, all newly visited nodes won't have a vtable that equals to a prefix of this one. - // Loop run #0: Emitting the slice [B A] (in reverse order), B has a next-sibling node, so this slice stops here. - // Loop run #0: Stack after exiting out is [D C], C is the next starting point. - // Loop run #1: Stack after diving in is [D C], C is "childless", since its child A is skipped(already emitted). - // Loop run #1: Emitting the slice [D C] (in reverse order). No one has a next-sibling node. - // Loop run #1: Stack after exiting out is []. Now the function exits. - - loop { - // dive deeper into the stack, recording the path - 'diving_in: loop { - if let Some((inner_most_trait_ref, _, _)) = stack.last() { - let inner_most_trait_ref = *inner_most_trait_ref; - let mut direct_super_traits_iter = tcx - .super_predicates_of(inner_most_trait_ref.def_id()) - .predicates - .into_iter() - .filter_map(move |(pred, _)| { - pred.subst_supertrait(tcx, &inner_most_trait_ref).to_opt_poly_trait_pred() - }); - - 'diving_in_skip_visited_traits: loop { - if let Some(next_super_trait) = direct_super_traits_iter.next() { - if visited.insert(next_super_trait.to_predicate(tcx)) { - // We're throwing away potential constness of super traits here. - // FIXME: handle ~const super traits - let next_super_trait = next_super_trait.map_bound(|t| t.trait_ref); - stack.push(( - next_super_trait, - emit_vptr_on_new_entry, - Some(direct_super_traits_iter), - )); - break 'diving_in_skip_visited_traits; - } else { - continue 'diving_in_skip_visited_traits; - } - } else { - break 'diving_in; - } - } - } - } - - // Other than the left-most path, vptr should be emitted for each trait. - emit_vptr_on_new_entry = true; - - // emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level. - 'exiting_out: loop { - if let Some((inner_most_trait_ref, emit_vptr, siblings_opt)) = stack.last_mut() { - if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::TraitOwnEntries { - trait_ref: *inner_most_trait_ref, - emit_vptr: *emit_vptr, - }) { - return Some(v); - } - - 'exiting_out_skip_visited_traits: loop { - if let Some(siblings) = siblings_opt { - if let Some(next_inner_most_trait_ref) = siblings.next() { - if visited.insert(next_inner_most_trait_ref.to_predicate(tcx)) { - // We're throwing away potential constness of super traits here. - // FIXME: handle ~const super traits - let next_inner_most_trait_ref = - next_inner_most_trait_ref.map_bound(|t| t.trait_ref); - *inner_most_trait_ref = next_inner_most_trait_ref; - *emit_vptr = emit_vptr_on_new_entry; - break 'exiting_out; - } else { - continue 'exiting_out_skip_visited_traits; - } - } - } - stack.pop(); - continue 'exiting_out; - } - } - // all done - return None; - } - } -} - -fn dump_vtable_entries<'tcx>( - tcx: TyCtxt<'tcx>, - sp: Span, - trait_ref: ty::PolyTraitRef<'tcx>, - entries: &[VtblEntry<'tcx>], -) { - tcx.sess.emit_err(DumpVTableEntries { - span: sp, - trait_ref, - entries: format!("{:#?}", entries), - }); -} - -fn own_existential_vtable_entries<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> &'tcx [DefId] { - let trait_methods = tcx - .associated_items(trait_def_id) - .in_definition_order() - .filter(|item| item.kind == ty::AssocKind::Fn); - // Now list each method's DefId (for within its trait). - let own_entries = trait_methods.filter_map(move |trait_method| { - debug!("own_existential_vtable_entry: trait_method={:?}", trait_method); - let def_id = trait_method.def_id; - - // Some methods cannot be called on an object; skip those. - if !is_vtable_safe_method(tcx, trait_def_id, &trait_method) { - debug!("own_existential_vtable_entry: not vtable safe"); - return None; - } - - Some(def_id) - }); - - tcx.arena.alloc_from_iter(own_entries.into_iter()) -} - -/// Given a trait `trait_ref`, iterates the vtable entries -/// that come from `trait_ref`, including its supertraits. -fn vtable_entries<'tcx>( - tcx: TyCtxt<'tcx>, - trait_ref: ty::PolyTraitRef<'tcx>, -) -> &'tcx [VtblEntry<'tcx>] { - debug!("vtable_entries({:?})", trait_ref); - - let mut entries = vec![]; - - let vtable_segment_callback = |segment| -> ControlFlow<()> { - match segment { - VtblSegment::MetadataDSA => { - entries.extend(TyCtxt::COMMON_VTABLE_ENTRIES); - } - VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { - let existential_trait_ref = trait_ref - .map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)); - - // Lookup the shape of vtable for the trait. - let own_existential_entries = - tcx.own_existential_vtable_entries(existential_trait_ref.def_id()); - - let own_entries = own_existential_entries.iter().copied().map(|def_id| { - debug!("vtable_entries: trait_method={:?}", def_id); - - // The method may have some early-bound lifetimes; add regions for those. - let substs = trait_ref.map_bound(|trait_ref| { - InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind { - GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(), - GenericParamDefKind::Type { .. } - | GenericParamDefKind::Const { .. } => { - trait_ref.substs[param.index as usize] - } - }) - }); - - // The trait type may have higher-ranked lifetimes in it; - // erase them if they appear, so that we get the type - // at some particular call site. - let substs = tcx - .normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs); - - // It's possible that the method relies on where-clauses that - // do not hold for this particular set of type parameters. - // Note that this method could then never be called, so we - // do not want to try and codegen it, in that case (see #23435). - let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs); - if impossible_predicates(tcx, predicates.predicates) { - debug!("vtable_entries: predicates do not hold"); - return VtblEntry::Vacant; - } - - let instance = ty::Instance::resolve_for_vtable( - tcx, - ty::ParamEnv::reveal_all(), - def_id, - substs, - ) - .expect("resolution failed during building vtable representation"); - VtblEntry::Method(instance) - }); - - entries.extend(own_entries); - - if emit_vptr { - entries.push(VtblEntry::TraitVPtr(trait_ref)); - } - } - } - - ControlFlow::Continue(()) - }; - - let _ = prepare_vtable_segments(tcx, trait_ref, vtable_segment_callback); - - if tcx.has_attr(trait_ref.def_id(), sym::rustc_dump_vtable) { - let sp = tcx.def_span(trait_ref.def_id()); - dump_vtable_entries(tcx, sp, trait_ref, &entries); - } - - tcx.arena.alloc_from_iter(entries.into_iter()) -} - -/// Find slot base for trait methods within vtable entries of another trait -fn vtable_trait_first_method_offset<'tcx>( - tcx: TyCtxt<'tcx>, - key: ( - ty::PolyTraitRef<'tcx>, // trait_to_be_found - ty::PolyTraitRef<'tcx>, // trait_owning_vtable - ), -) -> usize { - let (trait_to_be_found, trait_owning_vtable) = key; - - // #90177 - let trait_to_be_found_erased = tcx.erase_regions(trait_to_be_found); - - let vtable_segment_callback = { - let mut vtable_base = 0; - - move |segment| { - match segment { - VtblSegment::MetadataDSA => { - vtable_base += TyCtxt::COMMON_VTABLE_ENTRIES.len(); - } - VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { - if tcx.erase_regions(trait_ref) == trait_to_be_found_erased { - return ControlFlow::Break(vtable_base); - } - vtable_base += util::count_own_vtable_entries(tcx, trait_ref); - if emit_vptr { - vtable_base += 1; - } - } - } - ControlFlow::Continue(()) - } - }; - - if let Some(vtable_base) = - prepare_vtable_segments(tcx, trait_owning_vtable, vtable_segment_callback) - { - vtable_base - } else { - bug!("Failed to find info for expected trait in vtable"); - } -} - -/// Find slot offset for trait vptr within vtable entries of another trait -pub fn vtable_trait_upcasting_coercion_new_vptr_slot<'tcx>( - tcx: TyCtxt<'tcx>, - key: ( - Ty<'tcx>, // trait object type whose trait owning vtable - Ty<'tcx>, // trait object for supertrait - ), -) -> Option<usize> { - let (source, target) = key; - assert!(matches!(&source.kind(), &ty::Dynamic(..)) && !source.needs_infer()); - assert!(matches!(&target.kind(), &ty::Dynamic(..)) && !target.needs_infer()); - - // this has been typecked-before, so diagnostics is not really needed. - let unsize_trait_did = tcx.require_lang_item(LangItem::Unsize, None); - - let trait_ref = tcx.mk_trait_ref(unsize_trait_did, [source, target]); - - match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), ty::Binder::dummy(trait_ref))) { - Ok(ImplSource::TraitUpcasting(implsrc_traitcasting)) => { - implsrc_traitcasting.vtable_vptr_slot - } - otherwise => bug!("expected TraitUpcasting candidate, got {otherwise:?}"), - } -} - pub fn provide(providers: &mut ty::query::Providers) { object_safety::provide(providers); - structural_match::provide(providers); + vtable::provide(providers); *providers = ty::query::Providers { specialization_graph_of: specialize::specialization_graph_provider, specializes: specialize::specializes, - codegen_select_candidate: codegen::codegen_select_candidate, - own_existential_vtable_entries, - vtable_entries, - vtable_trait_upcasting_coercion_new_vptr_slot, subst_and_check_impossible_predicates, is_impossible_method, ..*providers diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index 22cd700dcb5..fda415155c4 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -19,6 +19,10 @@ use rustc_span::def_id::DefId; use crate::traits::project::{normalize_with_depth, normalize_with_depth_to}; use crate::traits::util::{self, closure_trait_ref_and_return_type, predicate_for_trait_def}; +use crate::traits::vtable::{ + count_own_vtable_entries, prepare_vtable_segments, vtable_trait_first_method_offset, + VtblSegment, +}; use crate::traits::{ BuiltinDerivedObligation, ImplDerivedObligation, ImplDerivedObligationCause, ImplSource, ImplSourceAutoImplData, ImplSourceBuiltinData, ImplSourceClosureData, @@ -26,7 +30,7 @@ use crate::traits::{ ImplSourceGeneratorData, ImplSourceObjectData, ImplSourceTraitAliasData, ImplSourceTraitUpcastingData, ImplSourceUserDefinedData, Normalized, ObjectCastObligation, Obligation, ObligationCause, OutputTypeParameterMismatch, PredicateObligation, Selection, - SelectionError, TraitNotObjectSafe, TraitObligation, Unimplemented, VtblSegment, + SelectionError, TraitNotObjectSafe, TraitObligation, Unimplemented, }; use super::BuiltinImplConditions; @@ -583,7 +587,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { debug!(?nested, "object nested obligations"); - let vtable_base = super::super::vtable_trait_first_method_offset( + let vtable_base = vtable_trait_first_method_offset( tcx, (unnormalized_upcast_trait_ref, ty::Binder::dummy(object_trait_ref)), ); @@ -904,7 +908,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { vptr_offset += TyCtxt::COMMON_VTABLE_ENTRIES.len(); } VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { - vptr_offset += util::count_own_vtable_entries(tcx, trait_ref); + vptr_offset += count_own_vtable_entries(tcx, trait_ref); if trait_ref == upcast_trait_ref { if emit_vptr { return ControlFlow::Break(Some(vptr_offset)); @@ -923,8 +927,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { }; let vtable_vptr_slot = - super::super::prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback) - .unwrap(); + prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback).unwrap(); Ok(ImplSourceTraitUpcastingData { upcast_trait_ref, vtable_vptr_slot, nested }) } diff --git a/compiler/rustc_trait_selection/src/traits/structural_match.rs b/compiler/rustc_trait_selection/src/traits/structural_match.rs index 40dbe0b3ff0..4dc08e0f9da 100644 --- a/compiler/rustc_trait_selection/src/traits/structural_match.rs +++ b/compiler/rustc_trait_selection/src/traits/structural_match.rs @@ -1,10 +1,5 @@ -use crate::infer::{InferCtxt, TyCtxtInferExt}; -use crate::traits::{ObligationCause, ObligationCtxt}; - use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; -use rustc_hir::lang_items::LangItem; -use rustc_middle::ty::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; use rustc_span::Span; use std::ops::ControlFlow; @@ -59,41 +54,6 @@ pub fn search_for_adt_const_param_violation<'tcx>( .break_value() } -/// This method returns true if and only if `adt_ty` itself has been marked as -/// eligible for structural-match: namely, if it implements both -/// `StructuralPartialEq` and `StructuralEq` (which are respectively injected by -/// `#[derive(PartialEq)]` and `#[derive(Eq)]`). -/// -/// Note that this does *not* recursively check if the substructure of `adt_ty` -/// implements the traits. -fn type_marked_structural<'tcx>( - infcx: &InferCtxt<'tcx>, - adt_ty: Ty<'tcx>, - cause: ObligationCause<'tcx>, -) -> bool { - let ocx = ObligationCtxt::new(infcx); - // require `#[derive(PartialEq)]` - let structural_peq_def_id = - infcx.tcx.require_lang_item(LangItem::StructuralPeq, Some(cause.span)); - ocx.register_bound(cause.clone(), ty::ParamEnv::empty(), adt_ty, structural_peq_def_id); - // for now, require `#[derive(Eq)]`. (Doing so is a hack to work around - // the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.) - let structural_teq_def_id = - infcx.tcx.require_lang_item(LangItem::StructuralTeq, Some(cause.span)); - ocx.register_bound(cause, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id); - - // We deliberately skip *reporting* fulfillment errors (via - // `report_fulfillment_errors`), for two reasons: - // - // 1. The error messages would mention `std::marker::StructuralPartialEq` - // (a trait which is solely meant as an implementation detail - // for now), and - // - // 2. We are sometimes doing future-incompatibility lints for - // now, so we do not want unconditional errors here. - ocx.select_all_or_error().is_empty() -} - /// This implements the traversal over the structure of a given type to try to /// find instances of ADTs (specifically structs or enums) that do not implement /// the structural-match traits (`StructuralPartialEq` and `StructuralEq`). @@ -249,11 +209,3 @@ impl<'tcx> TypeVisitor<'tcx> for Search<'tcx> { }) } } - -pub fn provide(providers: &mut Providers) { - providers.has_structural_eq_impls = |tcx, ty| { - let infcx = tcx.infer_ctxt().build(); - let cause = ObligationCause::dummy(); - type_marked_structural(&infcx, ty, cause) - }; -} diff --git a/compiler/rustc_trait_selection/src/traits/util.rs b/compiler/rustc_trait_selection/src/traits/util.rs index 51968c2d7a1..f3ca6a6c779 100644 --- a/compiler/rustc_trait_selection/src/traits/util.rs +++ b/compiler/rustc_trait_selection/src/traits/util.rs @@ -261,16 +261,6 @@ pub fn upcast_choices<'tcx>( supertraits(tcx, source_trait_ref).filter(|r| r.def_id() == target_trait_def_id).collect() } -/// Given a trait `trait_ref`, returns the number of vtable entries -/// that come from `trait_ref`, excluding its supertraits. Used in -/// computing the vtable base for an upcast trait of a trait object. -pub fn count_own_vtable_entries<'tcx>( - tcx: TyCtxt<'tcx>, - trait_ref: ty::PolyTraitRef<'tcx>, -) -> usize { - tcx.own_existential_vtable_entries(trait_ref.def_id()).len() -} - /// Given an upcast trait object described by `object`, returns the /// index of the method `method_def_id` (which should be part of /// `object.upcast_trait_ref`) within the vtable for `object`. diff --git a/compiler/rustc_trait_selection/src/traits/vtable.rs b/compiler/rustc_trait_selection/src/traits/vtable.rs new file mode 100644 index 00000000000..41ce6cdf789 --- /dev/null +++ b/compiler/rustc_trait_selection/src/traits/vtable.rs @@ -0,0 +1,386 @@ +use crate::errors::DumpVTableEntries; +use crate::traits::{impossible_predicates, is_vtable_safe_method}; +use rustc_hir::def_id::DefId; +use rustc_hir::lang_items::LangItem; +use rustc_infer::traits::util::PredicateSet; +use rustc_infer::traits::ImplSource; +use rustc_middle::ty::visit::TypeVisitable; +use rustc_middle::ty::InternalSubsts; +use rustc_middle::ty::{self, GenericParamDefKind, ToPredicate, Ty, TyCtxt, VtblEntry}; +use rustc_span::{sym, Span}; +use smallvec::SmallVec; + +use std::fmt::Debug; +use std::ops::ControlFlow; + +#[derive(Clone, Debug)] +pub(super) enum VtblSegment<'tcx> { + MetadataDSA, + TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool }, +} + +/// Prepare the segments for a vtable +pub(super) fn prepare_vtable_segments<'tcx, T>( + tcx: TyCtxt<'tcx>, + trait_ref: ty::PolyTraitRef<'tcx>, + mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow<T>, +) -> Option<T> { + // The following constraints holds for the final arrangement. + // 1. The whole virtual table of the first direct super trait is included as the + // the prefix. If this trait doesn't have any super traits, then this step + // consists of the dsa metadata. + // 2. Then comes the proper pointer metadata(vptr) and all own methods for all + // other super traits except those already included as part of the first + // direct super trait virtual table. + // 3. finally, the own methods of this trait. + + // This has the advantage that trait upcasting to the first direct super trait on each level + // is zero cost, and to another trait includes only replacing the pointer with one level indirection, + // while not using too much extra memory. + + // For a single inheritance relationship like this, + // D --> C --> B --> A + // The resulting vtable will consists of these segments: + // DSA, A, B, C, D + + // For a multiple inheritance relationship like this, + // D --> C --> A + // \-> B + // The resulting vtable will consists of these segments: + // DSA, A, B, B-vptr, C, D + + // For a diamond inheritance relationship like this, + // D --> B --> A + // \-> C -/ + // The resulting vtable will consists of these segments: + // DSA, A, B, C, C-vptr, D + + // For a more complex inheritance relationship like this: + // O --> G --> C --> A + // \ \ \-> B + // | |-> F --> D + // | \-> E + // |-> N --> J --> H + // \ \-> I + // |-> M --> K + // \-> L + // The resulting vtable will consists of these segments: + // DSA, A, B, B-vptr, C, D, D-vptr, E, E-vptr, F, F-vptr, G, + // H, H-vptr, I, I-vptr, J, J-vptr, K, K-vptr, L, L-vptr, M, M-vptr, + // N, N-vptr, O + + // emit dsa segment first. + if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::MetadataDSA) { + return Some(v); + } + + let mut emit_vptr_on_new_entry = false; + let mut visited = PredicateSet::new(tcx); + let predicate = trait_ref.without_const().to_predicate(tcx); + let mut stack: SmallVec<[(ty::PolyTraitRef<'tcx>, _, _); 5]> = + smallvec![(trait_ref, emit_vptr_on_new_entry, None)]; + visited.insert(predicate); + + // the main traversal loop: + // basically we want to cut the inheritance directed graph into a few non-overlapping slices of nodes + // that each node is emitted after all its descendents have been emitted. + // so we convert the directed graph into a tree by skipping all previously visited nodes using a visited set. + // this is done on the fly. + // Each loop run emits a slice - it starts by find a "childless" unvisited node, backtracking upwards, and it + // stops after it finds a node that has a next-sibling node. + // This next-sibling node will used as the starting point of next slice. + + // Example: + // For a diamond inheritance relationship like this, + // D#1 --> B#0 --> A#0 + // \-> C#1 -/ + + // Starting point 0 stack [D] + // Loop run #0: Stack after diving in is [D B A], A is "childless" + // after this point, all newly visited nodes won't have a vtable that equals to a prefix of this one. + // Loop run #0: Emitting the slice [B A] (in reverse order), B has a next-sibling node, so this slice stops here. + // Loop run #0: Stack after exiting out is [D C], C is the next starting point. + // Loop run #1: Stack after diving in is [D C], C is "childless", since its child A is skipped(already emitted). + // Loop run #1: Emitting the slice [D C] (in reverse order). No one has a next-sibling node. + // Loop run #1: Stack after exiting out is []. Now the function exits. + + loop { + // dive deeper into the stack, recording the path + 'diving_in: loop { + if let Some((inner_most_trait_ref, _, _)) = stack.last() { + let inner_most_trait_ref = *inner_most_trait_ref; + let mut direct_super_traits_iter = tcx + .super_predicates_of(inner_most_trait_ref.def_id()) + .predicates + .into_iter() + .filter_map(move |(pred, _)| { + pred.subst_supertrait(tcx, &inner_most_trait_ref).to_opt_poly_trait_pred() + }); + + 'diving_in_skip_visited_traits: loop { + if let Some(next_super_trait) = direct_super_traits_iter.next() { + if visited.insert(next_super_trait.to_predicate(tcx)) { + // We're throwing away potential constness of super traits here. + // FIXME: handle ~const super traits + let next_super_trait = next_super_trait.map_bound(|t| t.trait_ref); + stack.push(( + next_super_trait, + emit_vptr_on_new_entry, + Some(direct_super_traits_iter), + )); + break 'diving_in_skip_visited_traits; + } else { + continue 'diving_in_skip_visited_traits; + } + } else { + break 'diving_in; + } + } + } + } + + // Other than the left-most path, vptr should be emitted for each trait. + emit_vptr_on_new_entry = true; + + // emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level. + 'exiting_out: loop { + if let Some((inner_most_trait_ref, emit_vptr, siblings_opt)) = stack.last_mut() { + if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::TraitOwnEntries { + trait_ref: *inner_most_trait_ref, + emit_vptr: *emit_vptr, + }) { + return Some(v); + } + + 'exiting_out_skip_visited_traits: loop { + if let Some(siblings) = siblings_opt { + if let Some(next_inner_most_trait_ref) = siblings.next() { + if visited.insert(next_inner_most_trait_ref.to_predicate(tcx)) { + // We're throwing away potential constness of super traits here. + // FIXME: handle ~const super traits + let next_inner_most_trait_ref = + next_inner_most_trait_ref.map_bound(|t| t.trait_ref); + *inner_most_trait_ref = next_inner_most_trait_ref; + *emit_vptr = emit_vptr_on_new_entry; + break 'exiting_out; + } else { + continue 'exiting_out_skip_visited_traits; + } + } + } + stack.pop(); + continue 'exiting_out; + } + } + // all done + return None; + } + } +} + +fn dump_vtable_entries<'tcx>( + tcx: TyCtxt<'tcx>, + sp: Span, + trait_ref: ty::PolyTraitRef<'tcx>, + entries: &[VtblEntry<'tcx>], +) { + tcx.sess.emit_err(DumpVTableEntries { + span: sp, + trait_ref, + entries: format!("{:#?}", entries), + }); +} + +fn own_existential_vtable_entries<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> &'tcx [DefId] { + let trait_methods = tcx + .associated_items(trait_def_id) + .in_definition_order() + .filter(|item| item.kind == ty::AssocKind::Fn); + // Now list each method's DefId (for within its trait). + let own_entries = trait_methods.filter_map(move |trait_method| { + debug!("own_existential_vtable_entry: trait_method={:?}", trait_method); + let def_id = trait_method.def_id; + + // Some methods cannot be called on an object; skip those. + if !is_vtable_safe_method(tcx, trait_def_id, &trait_method) { + debug!("own_existential_vtable_entry: not vtable safe"); + return None; + } + + Some(def_id) + }); + + tcx.arena.alloc_from_iter(own_entries.into_iter()) +} + +/// Given a trait `trait_ref`, iterates the vtable entries +/// that come from `trait_ref`, including its supertraits. +fn vtable_entries<'tcx>( + tcx: TyCtxt<'tcx>, + trait_ref: ty::PolyTraitRef<'tcx>, +) -> &'tcx [VtblEntry<'tcx>] { + debug!("vtable_entries({:?})", trait_ref); + + let mut entries = vec![]; + + let vtable_segment_callback = |segment| -> ControlFlow<()> { + match segment { + VtblSegment::MetadataDSA => { + entries.extend(TyCtxt::COMMON_VTABLE_ENTRIES); + } + VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { + let existential_trait_ref = trait_ref + .map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)); + + // Lookup the shape of vtable for the trait. + let own_existential_entries = + tcx.own_existential_vtable_entries(existential_trait_ref.def_id()); + + let own_entries = own_existential_entries.iter().copied().map(|def_id| { + debug!("vtable_entries: trait_method={:?}", def_id); + + // The method may have some early-bound lifetimes; add regions for those. + let substs = trait_ref.map_bound(|trait_ref| { + InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind { + GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(), + GenericParamDefKind::Type { .. } + | GenericParamDefKind::Const { .. } => { + trait_ref.substs[param.index as usize] + } + }) + }); + + // The trait type may have higher-ranked lifetimes in it; + // erase them if they appear, so that we get the type + // at some particular call site. + let substs = tcx + .normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs); + + // It's possible that the method relies on where-clauses that + // do not hold for this particular set of type parameters. + // Note that this method could then never be called, so we + // do not want to try and codegen it, in that case (see #23435). + let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs); + if impossible_predicates(tcx, predicates.predicates) { + debug!("vtable_entries: predicates do not hold"); + return VtblEntry::Vacant; + } + + let instance = ty::Instance::resolve_for_vtable( + tcx, + ty::ParamEnv::reveal_all(), + def_id, + substs, + ) + .expect("resolution failed during building vtable representation"); + VtblEntry::Method(instance) + }); + + entries.extend(own_entries); + + if emit_vptr { + entries.push(VtblEntry::TraitVPtr(trait_ref)); + } + } + } + + ControlFlow::Continue(()) + }; + + let _ = prepare_vtable_segments(tcx, trait_ref, vtable_segment_callback); + + if tcx.has_attr(trait_ref.def_id(), sym::rustc_dump_vtable) { + let sp = tcx.def_span(trait_ref.def_id()); + dump_vtable_entries(tcx, sp, trait_ref, &entries); + } + + tcx.arena.alloc_from_iter(entries.into_iter()) +} + +/// Find slot base for trait methods within vtable entries of another trait +pub(super) fn vtable_trait_first_method_offset<'tcx>( + tcx: TyCtxt<'tcx>, + key: ( + ty::PolyTraitRef<'tcx>, // trait_to_be_found + ty::PolyTraitRef<'tcx>, // trait_owning_vtable + ), +) -> usize { + let (trait_to_be_found, trait_owning_vtable) = key; + + // #90177 + let trait_to_be_found_erased = tcx.erase_regions(trait_to_be_found); + + let vtable_segment_callback = { + let mut vtable_base = 0; + + move |segment| { + match segment { + VtblSegment::MetadataDSA => { + vtable_base += TyCtxt::COMMON_VTABLE_ENTRIES.len(); + } + VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { + if tcx.erase_regions(trait_ref) == trait_to_be_found_erased { + return ControlFlow::Break(vtable_base); + } + vtable_base += count_own_vtable_entries(tcx, trait_ref); + if emit_vptr { + vtable_base += 1; + } + } + } + ControlFlow::Continue(()) + } + }; + + if let Some(vtable_base) = + prepare_vtable_segments(tcx, trait_owning_vtable, vtable_segment_callback) + { + vtable_base + } else { + bug!("Failed to find info for expected trait in vtable"); + } +} + +/// Find slot offset for trait vptr within vtable entries of another trait +pub(crate) fn vtable_trait_upcasting_coercion_new_vptr_slot<'tcx>( + tcx: TyCtxt<'tcx>, + key: ( + Ty<'tcx>, // trait object type whose trait owning vtable + Ty<'tcx>, // trait object for supertrait + ), +) -> Option<usize> { + let (source, target) = key; + assert!(matches!(&source.kind(), &ty::Dynamic(..)) && !source.needs_infer()); + assert!(matches!(&target.kind(), &ty::Dynamic(..)) && !target.needs_infer()); + + // this has been typecked-before, so diagnostics is not really needed. + let unsize_trait_did = tcx.require_lang_item(LangItem::Unsize, None); + + let trait_ref = tcx.mk_trait_ref(unsize_trait_did, [source, target]); + + match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), ty::Binder::dummy(trait_ref))) { + Ok(ImplSource::TraitUpcasting(implsrc_traitcasting)) => { + implsrc_traitcasting.vtable_vptr_slot + } + otherwise => bug!("expected TraitUpcasting candidate, got {otherwise:?}"), + } +} + +/// Given a trait `trait_ref`, returns the number of vtable entries +/// that come from `trait_ref`, excluding its supertraits. Used in +/// computing the vtable base for an upcast trait of a trait object. +pub(crate) fn count_own_vtable_entries<'tcx>( + tcx: TyCtxt<'tcx>, + trait_ref: ty::PolyTraitRef<'tcx>, +) -> usize { + tcx.own_existential_vtable_entries(trait_ref.def_id()).len() +} + +pub(super) fn provide(providers: &mut ty::query::Providers) { + *providers = ty::query::Providers { + own_existential_vtable_entries, + vtable_entries, + vtable_trait_upcasting_coercion_new_vptr_slot, + ..*providers + }; +} diff --git a/compiler/rustc_trait_selection/src/traits/codegen.rs b/compiler/rustc_traits/src/codegen.rs index 0102d268b42..f8f74b732ef 100644 --- a/compiler/rustc_trait_selection/src/traits/codegen.rs +++ b/compiler/rustc_traits/src/codegen.rs @@ -3,15 +3,15 @@ // seems likely that they should eventually be merged into more // general routines. -use crate::infer::{DefiningAnchor, TyCtxtInferExt}; -use crate::traits::error_reporting::TypeErrCtxtExt; -use crate::traits::{ - ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt, - Unimplemented, -}; +use rustc_infer::infer::{DefiningAnchor, TyCtxtInferExt}; use rustc_infer::traits::FulfillmentErrorCode; use rustc_middle::traits::CodegenObligationError; use rustc_middle::ty::{self, TyCtxt}; +use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt; +use rustc_trait_selection::traits::{ + ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt, + Unimplemented, +}; /// Attempts to resolve an obligation to an `ImplSource`. The result is /// a shallow `ImplSource` resolution, meaning that we do not diff --git a/compiler/rustc_traits/src/lib.rs b/compiler/rustc_traits/src/lib.rs index 0ffa92f1ad5..9aa26667e7b 100644 --- a/compiler/rustc_traits/src/lib.rs +++ b/compiler/rustc_traits/src/lib.rs @@ -12,6 +12,7 @@ extern crate tracing; extern crate rustc_middle; mod chalk; +mod codegen; mod dropck_outlives; mod evaluate_obligation; mod implied_outlives_bounds; @@ -31,4 +32,5 @@ pub fn provide(p: &mut Providers) { normalize_projection_ty::provide(p); normalize_erasing_regions::provide(p); type_op::provide(p); + p.codegen_select_candidate = codegen::codegen_select_candidate; } diff --git a/compiler/rustc_ty_utils/src/lib.rs b/compiler/rustc_ty_utils/src/lib.rs index cce5a79ddc8..7ad5cbc01cc 100644 --- a/compiler/rustc_ty_utils/src/lib.rs +++ b/compiler/rustc_ty_utils/src/lib.rs @@ -29,6 +29,7 @@ mod layout; mod layout_sanity_check; mod needs_drop; pub mod representability; +mod structural_match; mod ty; pub fn provide(providers: &mut Providers) { @@ -42,4 +43,5 @@ pub fn provide(providers: &mut Providers) { representability::provide(providers); ty::provide(providers); instance::provide(providers); + structural_match::provide(providers); } diff --git a/compiler/rustc_ty_utils/src/structural_match.rs b/compiler/rustc_ty_utils/src/structural_match.rs new file mode 100644 index 00000000000..a55bb7e7e90 --- /dev/null +++ b/compiler/rustc_ty_utils/src/structural_match.rs @@ -0,0 +1,44 @@ +use rustc_hir::lang_items::LangItem; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{self, Ty, TyCtxt}; + +use rustc_infer::infer::TyCtxtInferExt; +use rustc_trait_selection::traits::{ObligationCause, ObligationCtxt}; + +/// This method returns true if and only if `adt_ty` itself has been marked as +/// eligible for structural-match: namely, if it implements both +/// `StructuralPartialEq` and `StructuralEq` (which are respectively injected by +/// `#[derive(PartialEq)]` and `#[derive(Eq)]`). +/// +/// Note that this does *not* recursively check if the substructure of `adt_ty` +/// implements the traits. +fn has_structural_eq_impls<'tcx>(tcx: TyCtxt<'tcx>, adt_ty: Ty<'tcx>) -> bool { + let ref infcx = tcx.infer_ctxt().build(); + let cause = ObligationCause::dummy(); + + let ocx = ObligationCtxt::new(infcx); + // require `#[derive(PartialEq)]` + let structural_peq_def_id = + infcx.tcx.require_lang_item(LangItem::StructuralPeq, Some(cause.span)); + ocx.register_bound(cause.clone(), ty::ParamEnv::empty(), adt_ty, structural_peq_def_id); + // for now, require `#[derive(Eq)]`. (Doing so is a hack to work around + // the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.) + let structural_teq_def_id = + infcx.tcx.require_lang_item(LangItem::StructuralTeq, Some(cause.span)); + ocx.register_bound(cause, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id); + + // We deliberately skip *reporting* fulfillment errors (via + // `report_fulfillment_errors`), for two reasons: + // + // 1. The error messages would mention `std::marker::StructuralPartialEq` + // (a trait which is solely meant as an implementation detail + // for now), and + // + // 2. We are sometimes doing future-incompatibility lints for + // now, so we do not want unconditional errors here. + ocx.select_all_or_error().is_empty() +} + +pub fn provide(providers: &mut Providers) { + providers.has_structural_eq_impls = has_structural_eq_impls; +} diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index b2328b001de..ac7f579ebb5 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -4,12 +4,14 @@ use crate::ascii; use crate::convert::TryInto; -use crate::error::Error; use crate::intrinsics; use crate::mem; use crate::ops::{Add, Mul, Sub}; use crate::str::FromStr; +#[cfg(not(no_fp_fmt_parse))] +use crate::error::Error; + // Used because the `?` operator is not allowed in a const context. macro_rules! try_opt { ($e:expr) => { diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 04486ed2d14..3d7a7cfaf35 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -3468,10 +3468,11 @@ impl<T> [T] { /// maintained. /// /// This method splits the slice into three distinct slices: prefix, correctly aligned middle - /// slice of a new type, and the suffix slice. The method may make the middle slice the greatest - /// length possible for a given type and input slice, but only your algorithm's performance - /// should depend on that, not its correctness. It is permissible for all of the input data to - /// be returned as the prefix or suffix slice. + /// slice of a new type, and the suffix slice. How exactly the slice is split up is not + /// specified; the middle part may be smaller than necessary. However, if this fails to return a + /// maximal middle part, that is because code is running in a context where performance does not + /// matter, such as a sanitizer attempting to find alignment bugs. Regular code running + /// in a default (debug or release) execution *will* return a maximal middle part. /// /// This method has no purpose when either input element `T` or output element `U` are /// zero-sized and will return the original slice without splitting anything. @@ -3529,10 +3530,11 @@ impl<T> [T] { /// types is maintained. /// /// This method splits the slice into three distinct slices: prefix, correctly aligned middle - /// slice of a new type, and the suffix slice. The method may make the middle slice the greatest - /// length possible for a given type and input slice, but only your algorithm's performance - /// should depend on that, not its correctness. It is permissible for all of the input data to - /// be returned as the prefix or suffix slice. + /// slice of a new type, and the suffix slice. How exactly the slice is split up is not + /// specified; the middle part may be smaller than necessary. However, if this fails to return a + /// maximal middle part, that is because code is running in a context where performance does not + /// matter, such as a sanitizer attempting to find alignment bugs. Regular code running + /// in a default (debug or release) execution *will* return a maximal middle part. /// /// This method has no purpose when either input element `T` or output element `U` are /// zero-sized and will return the original slice without splitting anything. diff --git a/library/std/src/sys/itron/condvar.rs b/library/std/src/sys/itron/condvar.rs index f70aa434e48..7a47cc6696a 100644 --- a/library/std/src/sys/itron/condvar.rs +++ b/library/std/src/sys/itron/condvar.rs @@ -71,7 +71,7 @@ impl Condvar { } } - unsafe { mutex.lock() }; + mutex.lock(); } pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool { @@ -109,7 +109,7 @@ impl Condvar { // we woke up because of `notify_*`. let success = self.waiters.with_locked(|waiters| unsafe { !waiters.remove(waiter) }); - unsafe { mutex.lock() }; + mutex.lock(); success } } diff --git a/library/std/src/sys/itron/mutex.rs b/library/std/src/sys/itron/mutex.rs index f2eed8e771c..1f6cc419476 100644 --- a/library/std/src/sys/itron/mutex.rs +++ b/library/std/src/sys/itron/mutex.rs @@ -72,7 +72,7 @@ pub(super) struct MutexGuard<'a>(&'a Mutex); impl<'a> MutexGuard<'a> { #[inline] pub(super) fn lock(x: &'a Mutex) -> Self { - unsafe { x.lock() }; + x.lock(); Self(x) } } diff --git a/library/std/src/sys/itron/thread.rs b/library/std/src/sys/itron/thread.rs index d28f57f33be..c2b36680872 100644 --- a/library/std/src/sys/itron/thread.rs +++ b/library/std/src/sys/itron/thread.rs @@ -11,18 +11,25 @@ use crate::{ ffi::CStr, hint, io, mem::ManuallyDrop, + ptr::NonNull, sync::atomic::{AtomicUsize, Ordering}, sys::thread_local_dtor::run_dtors, time::Duration, }; pub struct Thread { - inner: ManuallyDrop<Box<ThreadInner>>, + p_inner: NonNull<ThreadInner>, /// The ID of the underlying task. task: abi::ID, } +// Safety: There's nothing in `Thread` that ties it to the original creator. It +// can be dropped by any threads. +unsafe impl Send for Thread {} +// Safety: `Thread` provides no methods that take `&self`. +unsafe impl Sync for Thread {} + /// State data shared between a parent thread and child thread. It's dropped on /// a transition to one of the final states. struct ThreadInner { @@ -90,8 +97,9 @@ impl Thread { }); unsafe extern "C" fn trampoline(exinf: isize) { + let p_inner: *mut ThreadInner = crate::ptr::from_exposed_addr_mut(exinf as usize); // Safety: `ThreadInner` is alive at this point - let inner = unsafe { &*(exinf as *const ThreadInner) }; + let inner = unsafe { &*p_inner }; // Safety: Since `trampoline` is called only once for each // `ThreadInner` and only `trampoline` touches `start`, @@ -119,13 +127,13 @@ impl Thread { // No one will ever join, so we'll ask the collector task to // delete the task. - // In this case, `inner`'s ownership has been moved to us, - // And we are responsible for dropping it. The acquire + // In this case, `*p_inner`'s ownership has been moved to + // us, and we are responsible for dropping it. The acquire // ordering is not necessary because the parent thread made // no memory access needing synchronization since the call // to `acre_tsk`. // Safety: See above. - let _ = unsafe { Box::from_raw(inner as *const _ as *mut ThreadInner) }; + let _ = unsafe { Box::from_raw(p_inner) }; // Safety: There are no pinned references to the stack unsafe { terminate_and_delete_current_task() }; @@ -162,13 +170,14 @@ impl Thread { } } - let inner_ptr = (&*inner) as *const ThreadInner; + // Safety: `Box::into_raw` returns a non-null pointer + let p_inner = unsafe { NonNull::new_unchecked(Box::into_raw(inner)) }; let new_task = ItronError::err_if_negative(unsafe { abi::acre_tsk(&abi::T_CTSK { // Activate this task immediately tskatr: abi::TA_ACT, - exinf: inner_ptr as abi::EXINF, + exinf: p_inner.as_ptr().expose_addr() as abi::EXINF, // The entry point task: Some(trampoline), // Inherit the calling task's base priority @@ -180,7 +189,7 @@ impl Thread { }) .map_err(|e| e.as_io_error())?; - Ok(Self { inner: ManuallyDrop::new(inner), task: new_task }) + Ok(Self { p_inner, task: new_task }) } pub fn yield_now() { @@ -197,8 +206,9 @@ impl Thread { } } - pub fn join(mut self) { - let inner = &*self.inner; + pub fn join(self) { + // Safety: `ThreadInner` is alive at this point + let inner = unsafe { self.p_inner.as_ref() }; // Get the current task ID. Panicking here would cause a resource leak, // so just abort on failure. let current_task = task::current_task_id_aborting(); @@ -243,8 +253,8 @@ impl Thread { unsafe { terminate_and_delete_task(self.task) }; // In either case, we are responsible for dropping `inner`. - // Safety: The contents of `self.inner` will not be accessed hereafter - let _inner = unsafe { ManuallyDrop::take(&mut self.inner) }; + // Safety: The contents of `*p_inner` will not be accessed hereafter + let _inner = unsafe { Box::from_raw(self.p_inner.as_ptr()) }; // Skip the destructor (because it would attempt to detach the thread) crate::mem::forget(self); @@ -253,13 +263,16 @@ impl Thread { impl Drop for Thread { fn drop(&mut self) { + // Safety: `ThreadInner` is alive at this point + let inner = unsafe { self.p_inner.as_ref() }; + // Detach the thread. - match self.inner.lifecycle.swap(LIFECYCLE_DETACHED_OR_JOINED, Ordering::Acquire) { + match inner.lifecycle.swap(LIFECYCLE_DETACHED_OR_JOINED, Ordering::Acquire) { LIFECYCLE_INIT => { // [INIT → DETACHED] // When the time comes, the child will figure out that no // one will ever join it. - // The ownership of `self.inner` is moved to the child thread. + // The ownership of `*p_inner` is moved to the child thread. // However, the release ordering is not necessary because we // made no memory access needing synchronization since the call // to `acre_tsk`. @@ -278,10 +291,9 @@ impl Drop for Thread { // delete by entering the `FINISHED` state. unsafe { terminate_and_delete_task(self.task) }; - // Wwe are responsible for dropping `inner`. - // Safety: The contents of `self.inner` will not be accessed - // hereafter - unsafe { ManuallyDrop::drop(&mut self.inner) }; + // Wwe are responsible for dropping `*p_inner`. + // Safety: The contents of `*p_inner` will not be accessed hereafter + let _ = unsafe { Box::from_raw(self.p_inner.as_ptr()) }; } _ => unsafe { hint::unreachable_unchecked() }, } diff --git a/library/std/src/sys/solid/io.rs b/library/std/src/sys/solid/io.rs index 9eb17a10daa..a862bb78702 100644 --- a/library/std/src/sys/solid/io.rs +++ b/library/std/src/sys/solid/io.rs @@ -75,3 +75,7 @@ impl<'a> IoSliceMut<'a> { unsafe { slice::from_raw_parts_mut(self.vec.iov_base as *mut u8, self.vec.iov_len) } } } + +pub fn is_terminal<T>(_: &T) -> bool { + false +} diff --git a/library/std/src/sys/solid/os.rs b/library/std/src/sys/solid/os.rs index 4906c62689d..6135921f0b5 100644 --- a/library/std/src/sys/solid/os.rs +++ b/library/std/src/sys/solid/os.rs @@ -1,7 +1,6 @@ use super::unsupported; -use crate::convert::TryFrom; use crate::error::Error as StdError; -use crate::ffi::{CStr, CString, OsStr, OsString}; +use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt; use crate::io; use crate::os::{ diff --git a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile index 9a290edd561..6b7b32a8bc7 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile @@ -24,5 +24,5 @@ ENV \ ENV HOSTS=powerpc64le-unknown-linux-gnu -ENV RUST_CONFIGURE_ARGS --enable-extended --disable-docs +ENV RUST_CONFIGURE_ARGS --enable-extended --enable-profiler --disable-docs ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-13/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-13/Dockerfile index c3b47c3510d..06f15bd1211 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-13/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-13/Dockerfile @@ -25,7 +25,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ zlib1g-dev \ xz-utils \ nodejs \ - + \ # Install powershell so we can test x.ps1 on Linux apt-transport-https software-properties-common && \ curl -s "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb" > packages-microsoft-prod.deb && \ diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile index 501d2781646..58c0c5db1a5 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/Dockerfile @@ -15,7 +15,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ sudo \ xz-utils \ tidy \ - + \ # Install dependencies for chromium browser gconf-service \ libasound2 \ diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index e0cdb86d9d1..56b40d8c66b 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -69,6 +69,8 @@ pub(crate) struct Options { pub(crate) input: PathBuf, /// The name of the crate being documented. pub(crate) crate_name: Option<String>, + /// Whether or not this is a bin crate + pub(crate) bin_crate: bool, /// Whether or not this is a proc-macro crate pub(crate) proc_macro_crate: bool, /// How to format errors and warnings. @@ -176,6 +178,7 @@ impl fmt::Debug for Options { f.debug_struct("Options") .field("input", &self.input) .field("crate_name", &self.crate_name) + .field("bin_crate", &self.bin_crate) .field("proc_macro_crate", &self.proc_macro_crate) .field("error_format", &self.error_format) .field("libs", &self.libs) @@ -667,6 +670,7 @@ impl Options { None => OutputFormat::default(), }; let crate_name = matches.opt_str("crate-name"); + let bin_crate = crate_types.contains(&CrateType::Executable); let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); let playground_url = matches.opt_str("playground-url"); let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from); @@ -718,6 +722,7 @@ impl Options { rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); let options = Options { input, + bin_crate, proc_macro_crate, error_format, diagnostic_width, diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 08f8096b07b..36d15ec3b86 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -2957,14 +2957,23 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite // The call locations are output in sequence, so that sequence needs to be determined. // Ideally the most "relevant" examples would be shown first, but there's no general algorithm - // for determining relevance. Instead, we prefer the smallest examples being likely the easiest to - // understand at a glance. + // for determining relevance. We instead proxy relevance with the following heuristics: + // 1. Code written to be an example is better than code not written to be an example, e.g. + // a snippet from examples/foo.rs is better than src/lib.rs. We don't know the Cargo + // directory structure in Rustdoc, so we proxy this by prioritizing code that comes from + // a --crate-type bin. + // 2. Smaller examples are better than large examples. So we prioritize snippets that have + // the smallest number of lines in their enclosing item. + // 3. Finally we sort by the displayed file name, which is arbitrary but prevents the + // ordering of examples from randomly changing between Rustdoc invocations. let ordered_locations = { - let sort_criterion = |(_, call_data): &(_, &CallData)| { + fn sort_criterion<'a>( + (_, call_data): &(&PathBuf, &'a CallData), + ) -> (bool, u32, &'a String) { // Use the first location because that's what the user will see initially let (lo, hi) = call_data.locations[0].enclosing_item.byte_span; - hi - lo - }; + (!call_data.is_bin, hi - lo, &call_data.display_name) + } let mut locs = call_locations.iter().collect::<Vec<_>>(); locs.sort_by_key(sort_criterion); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 11136179607..afcb4022402 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -630,22 +630,16 @@ pre, .rustdoc.source .example-wrap { .docblock table { margin: .5em 0; - width: calc(100% - 2px); - overflow-x: auto; - display: block; border-collapse: collapse; } -.docblock table td { +.docblock table td, .docblock table th { padding: .5em; - border: 1px dashed var(--border-color); - vertical-align: top; + border: 1px solid var(--border-color); } -.docblock table th { - padding: .5em; - text-align: left; - border: 1px solid var(--border-color); +.docblock table tbody tr:nth-child(2n) { + background: var(--table-alt-row-background-color); } /* Shift "where ..." part of method or fn definition down a line */ @@ -1813,6 +1807,22 @@ in storage.js } } +/* Should have min-width: (N + 1)px where N is the mobile breakpoint above. */ +@media (min-width: 701px) { + /* Places file-link for a scraped example on top of the example to save space. + We only do this on large screens so the file-link doesn't overlap too much + with the example's content. */ + .scraped-example-title { + position: absolute; + z-index: 10; + background: var(--main-background-color); + bottom: 8px; + right: 5px; + padding: 2px 4px; + box-shadow: 0 0 4px var(--main-background-color); + } +} + @media print { nav.sidebar, nav.sub, .out-of-band, a.srclink, #copy-path, details.rustdoc-toggle[open] > summary::before, details.rustdoc-toggle > summary::before, @@ -1899,6 +1909,11 @@ in storage.js border-radius: 50px; } +.scraped-example { + /* So .scraped-example-title can be positioned absolutely */ + position: relative; +} + .scraped-example .code-wrapper { position: relative; display: flex; @@ -1908,18 +1923,30 @@ in storage.js } .scraped-example:not(.expanded) .code-wrapper { - max-height: 240px; + /* scrape-examples.js has a constant DEFAULT_MAX_LINES (call it N) for the number + * of lines shown in the un-expanded example code viewer. This pre needs to have + * a max-height equal to line-height * N. The line-height is currently 1.5em, + * and we include additional 10px for padding. */ + max-height: calc(1.5em * 5 + 10px); } .scraped-example:not(.expanded) .code-wrapper pre { overflow-y: hidden; - max-height: 240px; padding-bottom: 0; + /* See above comment, should be the same max-height. */ + max-height: calc(1.5em * 5 + 10px); +} + +.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper, +.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre { + /* See above comment, except this height is based on HIDDEN_MAX_LINES. */ + max-height: calc(1.5em * 10 + 10px); } .scraped-example .code-wrapper .next, .scraped-example .code-wrapper .prev, .scraped-example .code-wrapper .expand { + color: var(--main-color); position: absolute; top: 0.25em; z-index: 1; diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index 6e0905e730d..0436fe0138e 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -87,6 +87,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) --crate-search-hover-border: #e0e0e0; --source-sidebar-background-selected: #14191f; --source-sidebar-background-hover: #14191f; + --table-alt-row-background-color: #191f26; } h1, h2, h3, h4 { diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index 334fc3de561..d945e956c53 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -82,6 +82,7 @@ --crate-search-hover-border: #2196f3; --source-sidebar-background-selected: #333; --source-sidebar-background-hover: #444; + --table-alt-row-background-color: #2A2A2A; } .content .item-info::before { color: #ccc; } diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index 453e7508af4..58955a79316 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -79,6 +79,7 @@ --crate-search-hover-border: #717171; --source-sidebar-background-selected: #fff; --source-sidebar-background-hover: #e0e0e0; + --table-alt-row-background-color: #F5F5F5; } .content .item-info::before { color: #ccc; } diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 623f46b1096..152116089c7 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -622,7 +622,7 @@ function loadCss(cssUrl) { const innerToggle = document.getElementById(toggleAllDocsId); removeClass(innerToggle, "will-expand"); onEachLazy(document.getElementsByClassName("rustdoc-toggle"), e => { - if (!hasClass(e, "type-contents-toggle")) { + if (!hasClass(e, "type-contents-toggle") && !hasClass(e, "more-examples-toggle")) { e.open = true; } }); diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index e328e656edd..7a3a9c5f340 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -3,25 +3,33 @@ "use strict"; (function() { - // Number of lines shown when code viewer is not expanded - const MAX_LINES = 10; + // Number of lines shown when code viewer is not expanded. + // DEFAULT is the first example shown by default, while HIDDEN is + // the examples hidden beneath the "More examples" toggle. + // + // NOTE: these values MUST be synchronized with certain rules in rustdoc.css! + const DEFAULT_MAX_LINES = 5; + const HIDDEN_MAX_LINES = 10; // Scroll code block to the given code location - function scrollToLoc(elt, loc) { + function scrollToLoc(elt, loc, isHidden) { const lines = elt.querySelector(".src-line-numbers"); let scrollOffset; // If the block is greater than the size of the viewer, // then scroll to the top of the block. Otherwise scroll // to the middle of the block. - if (loc[1] - loc[0] > MAX_LINES) { + const maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES; + if (loc[1] - loc[0] > maxLines) { const line = Math.max(0, loc[0] - 1); scrollOffset = lines.children[line].offsetTop; } else { const wrapper = elt.querySelector(".code-wrapper"); const halfHeight = wrapper.offsetHeight / 2; - const offsetMid = (lines.children[loc[0]].offsetTop - + lines.children[loc[1]].offsetTop) / 2; + const offsetTop = lines.children[loc[0]].offsetTop; + const lastLine = lines.children[loc[1]]; + const offsetBot = lastLine.offsetTop + lastLine.offsetHeight; + const offsetMid = (offsetTop + offsetBot) / 2; scrollOffset = offsetMid - halfHeight; } @@ -29,7 +37,7 @@ elt.querySelector(".rust").scrollTo(0, scrollOffset); } - function updateScrapedExample(example) { + function updateScrapedExample(example, isHidden) { const locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent); let locIndex = 0; const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight")); @@ -40,7 +48,7 @@ const onChangeLoc = changeIndex => { removeClass(highlights[locIndex], "focus"); changeIndex(); - scrollToLoc(example, locs[locIndex][0]); + scrollToLoc(example, locs[locIndex][0], isHidden); addClass(highlights[locIndex], "focus"); const url = locs[locIndex][1]; @@ -70,7 +78,7 @@ expandButton.addEventListener("click", () => { if (hasClass(example, "expanded")) { removeClass(example, "expanded"); - scrollToLoc(example, locs[0][0]); + scrollToLoc(example, locs[0][0], isHidden); } else { addClass(example, "expanded"); } @@ -78,11 +86,11 @@ } // Start with the first example in view - scrollToLoc(example, locs[0][0]); + scrollToLoc(example, locs[0][0], isHidden); } const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example"); - onEachLazy(firstExamples, updateScrapedExample); + onEachLazy(firstExamples, el => updateScrapedExample(el, false)); onEachLazy(document.querySelectorAll(".more-examples-toggle"), toggle => { // Allow users to click the left border of the <details> section to close it, // since the section can be large and finding the [+] button is annoying. @@ -99,7 +107,7 @@ // depends on offsetHeight, a property that requires an element to be visible to // compute correctly. setTimeout(() => { - onEachLazy(moreExamples, updateScrapedExample); + onEachLazy(moreExamples, el => updateScrapedExample(el, true)); }); }, {once: true}); }); diff --git a/src/librustdoc/html/static/scrape-examples-help.md b/src/librustdoc/html/static/scrape-examples-help.md index 035b2e18b00..002d19ec9b6 100644 --- a/src/librustdoc/html/static/scrape-examples-help.md +++ b/src/librustdoc/html/static/scrape-examples-help.md @@ -1,4 +1,4 @@ -Rustdoc will automatically scrape examples of documented items from the `examples/` directory of a project. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: +Rustdoc will automatically scrape examples of documented items from a project's source code. These examples will be included within the generated documentation for that item. For example, if your library contains a public function: ```rust // src/lib.rs @@ -16,6 +16,7 @@ fn main() { Then this code snippet will be included in the documentation for `a_func`. + ## How to read scraped examples Scraped examples are shown as blocks of code from a given file. The relevant item will be highlighted. If the file is larger than a couple lines, only a small window will be shown which you can expand by clicking ↕ in the top-right. If a file contains multiple instances of an item, you can use the ≺ and ≻ buttons to toggle through each instance. @@ -25,7 +26,7 @@ If there is more than one file that contains examples, then you should click "Mo ## How Rustdoc scrapes examples -When you run `cargo doc`, Rustdoc will analyze all the crates that match Cargo's `--examples` filter for instances of items that occur in the crates being documented. Then Rustdoc will include the source code of these instances in the generated documentation. +When you run `cargo doc -Zunstable-options -Zrustdoc-scrape-examples`, Rustdoc will analyze all the documented crates for uses of documented items. Then Rustdoc will include the source code of these instances in the generated documentation. Rustdoc has a few techniques to ensure this doesn't overwhelm documentation readers, and that it doesn't blow up the page size: diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 6d34f484754..ef1d7da5a34 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -674,7 +674,7 @@ type MainResult = Result<(), ErrorGuaranteed>; fn wrap_return(diag: &rustc_errors::Handler, res: Result<(), String>) -> MainResult { match res { - Ok(()) => Ok(()), + Ok(()) => diag.has_errors().map_or(Ok(()), Err), Err(err) => { let reported = diag.struct_err(&err).emit(); Err(reported) @@ -689,7 +689,7 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>( tcx: TyCtxt<'tcx>, ) -> MainResult { match formats::run_format::<T>(krate, renderopts, cache, tcx) { - Ok(_) => Ok(()), + Ok(_) => tcx.sess.has_errors().map_or(Ok(()), Err), Err(e) => { let mut msg = tcx.sess.struct_err(&format!("couldn't generate documentation: {}", e.error)); @@ -774,6 +774,7 @@ fn main_args(at_args: &[String]) -> MainResult { let output_format = options.output_format; let externs = options.externs.clone(); let scrape_examples_options = options.scrape_examples_options.clone(); + let bin_crate = options.bin_crate; let config = core::create_config(options); @@ -832,7 +833,14 @@ fn main_args(at_args: &[String]) -> MainResult { info!("finished with rustc"); if let Some(options) = scrape_examples_options { - return scrape_examples::run(krate, render_opts, cache, tcx, options); + return scrape_examples::run( + krate, + render_opts, + cache, + tcx, + options, + bin_crate, + ); } cache.crate_version = crate_version; diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index dfa6ba38b88..f2ee99cd9d4 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -110,6 +110,7 @@ pub(crate) struct CallData { pub(crate) url: String, pub(crate) display_name: String, pub(crate) edition: Edition, + pub(crate) is_bin: bool, } pub(crate) type FnCallLocations = FxHashMap<PathBuf, CallData>; @@ -122,6 +123,7 @@ struct FindCalls<'a, 'tcx> { cx: Context<'tcx>, target_crates: Vec<CrateNum>, calls: &'a mut AllCallLocations, + bin_crate: bool, } impl<'a, 'tcx> Visitor<'tcx> for FindCalls<'a, 'tcx> @@ -245,7 +247,9 @@ where let mk_call_data = || { let display_name = file_path.display().to_string(); let edition = call_span.edition(); - CallData { locations: Vec::new(), url, display_name, edition } + let is_bin = self.bin_crate; + + CallData { locations: Vec::new(), url, display_name, edition, is_bin } }; let fn_key = tcx.def_path_hash(*def_id); @@ -274,6 +278,7 @@ pub(crate) fn run( cache: formats::cache::Cache, tcx: TyCtxt<'_>, options: ScrapeExamplesOptions, + bin_crate: bool, ) -> interface::Result<()> { let inner = move || -> Result<(), String> { // Generates source files for examples @@ -300,7 +305,8 @@ pub(crate) fn run( // Run call-finder on all items let mut calls = FxHashMap::default(); - let mut finder = FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates }; + let mut finder = + FindCalls { calls: &mut calls, tcx, map: tcx.hir(), cx, target_crates, bin_crate }; tcx.hir().visit_all_item_likes_in_crate(&mut finder); // The visitor might have found a type error, which we need to diff --git a/src/llvm-project b/src/llvm-project -Subproject a1232c451fc27173f8718e05d174b2503ca0b60 +Subproject 3dfd4d93fa013e1c0578d3ceac5c8f4ebba4b6e diff --git a/src/test/mir-opt/inline/inline_retag.bar.Inline.after.mir b/src/test/mir-opt/inline/inline_retag.bar.Inline.after.mir index 75af20d482d..60149ff3606 100644 --- a/src/test/mir-opt/inline/inline_retag.bar.Inline.after.mir +++ b/src/test/mir-opt/inline/inline_retag.bar.Inline.after.mir @@ -38,9 +38,7 @@ fn bar() -> bool { // + literal: Const { ty: &i32, val: Unevaluated(bar, [], Some(promoted[1])) } Retag(_10); // scope 1 at $DIR/inline_retag.rs:+2:7: +2:9 _4 = &(*_10); // scope 1 at $DIR/inline_retag.rs:+2:7: +2:9 - Retag(_4); // scope 1 at $DIR/inline_retag.rs:+2:7: +2:9 _3 = &(*_4); // scope 1 at $DIR/inline_retag.rs:+2:7: +2:9 - Retag(_3); // scope 1 at $DIR/inline_retag.rs:+2:7: +2:9 StorageLive(_6); // scope 1 at $DIR/inline_retag.rs:+2:11: +2:14 StorageLive(_7); // scope 1 at $DIR/inline_retag.rs:+2:11: +2:14 _9 = const _; // scope 1 at $DIR/inline_retag.rs:+2:11: +2:14 @@ -49,9 +47,7 @@ fn bar() -> bool { // + literal: Const { ty: &i32, val: Unevaluated(bar, [], Some(promoted[0])) } Retag(_9); // scope 1 at $DIR/inline_retag.rs:+2:11: +2:14 _7 = &(*_9); // scope 1 at $DIR/inline_retag.rs:+2:11: +2:14 - Retag(_7); // scope 1 at $DIR/inline_retag.rs:+2:11: +2:14 _6 = &(*_7); // scope 1 at $DIR/inline_retag.rs:+2:11: +2:14 - Retag(_6); // scope 1 at $DIR/inline_retag.rs:+2:11: +2:14 Retag(_3); // scope 2 at $DIR/inline_retag.rs:16:8: 16:9 Retag(_6); // scope 2 at $DIR/inline_retag.rs:16:17: 16:18 StorageLive(_11); // scope 2 at $DIR/inline_retag.rs:17:5: 17:7 diff --git a/src/test/mir-opt/retag.array_casts.SimplifyCfg-elaborate-drops.after.mir b/src/test/mir-opt/retag.array_casts.SimplifyCfg-elaborate-drops.after.mir index fe57e32a7ac..7b69b3e07d6 100644 --- a/src/test/mir-opt/retag.array_casts.SimplifyCfg-elaborate-drops.after.mir +++ b/src/test/mir-opt/retag.array_casts.SimplifyCfg-elaborate-drops.after.mir @@ -68,9 +68,7 @@ fn array_casts() -> () { StorageLive(_3); // scope 1 at $DIR/retag.rs:+2:13: +2:19 StorageLive(_4); // scope 1 at $DIR/retag.rs:+2:13: +2:19 _4 = &mut _1; // scope 1 at $DIR/retag.rs:+2:13: +2:19 - Retag(_4); // scope 1 at $DIR/retag.rs:+2:13: +2:19 _3 = &raw mut (*_4); // scope 1 at $DIR/retag.rs:+2:13: +2:19 - Retag([raw] _3); // scope 1 at $DIR/retag.rs:+2:13: +2:19 _2 = move _3 as *mut usize (Pointer(ArrayToPointer)); // scope 1 at $DIR/retag.rs:+2:13: +2:33 StorageDead(_3); // scope 1 at $DIR/retag.rs:+2:32: +2:33 StorageDead(_4); // scope 1 at $DIR/retag.rs:+2:33: +2:34 @@ -96,9 +94,7 @@ fn array_casts() -> () { StorageLive(_10); // scope 4 at $DIR/retag.rs:+6:13: +6:15 StorageLive(_11); // scope 4 at $DIR/retag.rs:+6:13: +6:15 _11 = &_8; // scope 4 at $DIR/retag.rs:+6:13: +6:15 - Retag(_11); // scope 4 at $DIR/retag.rs:+6:13: +6:15 _10 = &raw const (*_11); // scope 4 at $DIR/retag.rs:+6:13: +6:15 - Retag([raw] _10); // scope 4 at $DIR/retag.rs:+6:13: +6:15 _9 = move _10 as *const usize (Pointer(ArrayToPointer)); // scope 4 at $DIR/retag.rs:+6:13: +6:31 StorageDead(_10); // scope 4 at $DIR/retag.rs:+6:30: +6:31 StorageDead(_11); // scope 4 at $DIR/retag.rs:+6:31: +6:32 @@ -119,7 +115,6 @@ fn array_casts() -> () { StorageDead(_17); // scope 6 at $DIR/retag.rs:+7:33: +7:34 _15 = (*_16); // scope 6 at $DIR/retag.rs:+7:25: +7:34 _14 = &_15; // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL - Retag(_14); // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_18); // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _35 = const _; // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL // mir::Constant @@ -127,7 +122,6 @@ fn array_casts() -> () { // + literal: Const { ty: &usize, val: Unevaluated(array_casts, [], Some(promoted[0])) } Retag(_35); // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _18 = &(*_35); // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL - Retag(_18); // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL Deinit(_13); // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL (_13.0: &usize) = move _14; // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL (_13.1: &usize) = move _18; // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL @@ -164,15 +158,11 @@ fn array_casts() -> () { StorageLive(_30); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_31); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _31 = &(*_20); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL - Retag(_31); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _30 = &(*_31); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL - Retag(_30); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_32); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_33); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _33 = &(*_21); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL - Retag(_33); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _32 = &(*_33); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL - Retag(_32); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_34); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL Deinit(_34); // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL discriminant(_34) = 0; // scope 8 at $SRC_DIR/core/src/macros/mod.rs:LL:COL diff --git a/src/test/mir-opt/retag.core.ptr-drop_in_place.Test.SimplifyCfg-make_shim.after.mir b/src/test/mir-opt/retag.core.ptr-drop_in_place.Test.SimplifyCfg-make_shim.after.mir index cdc413c568f..14f297e948b 100644 --- a/src/test/mir-opt/retag.core.ptr-drop_in_place.Test.SimplifyCfg-make_shim.after.mir +++ b/src/test/mir-opt/retag.core.ptr-drop_in_place.Test.SimplifyCfg-make_shim.after.mir @@ -6,7 +6,6 @@ fn std::ptr::drop_in_place(_1: *mut Test) -> () { let mut _3: (); // in scope 0 at $SRC_DIR/core/src/ptr/mod.rs:+0:1: +0:56 bb0: { - Retag([raw] _1); // scope 0 at $SRC_DIR/core/src/ptr/mod.rs:+0:1: +0:56 _2 = &mut (*_1); // scope 0 at $SRC_DIR/core/src/ptr/mod.rs:+0:1: +0:56 _3 = <Test as Drop>::drop(move _2) -> bb1; // scope 0 at $SRC_DIR/core/src/ptr/mod.rs:+0:1: +0:56 // mir::Constant diff --git a/src/test/mir-opt/retag.main-{closure#0}.SimplifyCfg-elaborate-drops.after.mir b/src/test/mir-opt/retag.main-{closure#0}.SimplifyCfg-elaborate-drops.after.mir index 96fc7e6493a..9e5c119a2b2 100644 --- a/src/test/mir-opt/retag.main-{closure#0}.SimplifyCfg-elaborate-drops.after.mir +++ b/src/test/mir-opt/retag.main-{closure#0}.SimplifyCfg-elaborate-drops.after.mir @@ -15,7 +15,6 @@ fn main::{closure#0}(_1: &[closure@main::{closure#0}], _2: &i32) -> &i32 { _3 = _2; // scope 0 at $DIR/retag.rs:+1:18: +1:19 Retag(_3); // scope 0 at $DIR/retag.rs:+1:18: +1:19 _0 = &(*_2); // scope 1 at $DIR/retag.rs:+2:9: +2:10 - Retag(_0); // scope 1 at $DIR/retag.rs:+2:9: +2:10 StorageDead(_3); // scope 0 at $DIR/retag.rs:+3:5: +3:6 return; // scope 0 at $DIR/retag.rs:+3:6: +3:6 } diff --git a/src/test/mir-opt/retag.main.SimplifyCfg-elaborate-drops.after.mir b/src/test/mir-opt/retag.main.SimplifyCfg-elaborate-drops.after.mir index 81225b44ebf..b853e450541 100644 --- a/src/test/mir-opt/retag.main.SimplifyCfg-elaborate-drops.after.mir +++ b/src/test/mir-opt/retag.main.SimplifyCfg-elaborate-drops.after.mir @@ -65,13 +65,10 @@ fn main() -> () { Deinit(_5); // scope 1 at $DIR/retag.rs:+3:17: +3:24 (_5.0: i32) = const 0_i32; // scope 1 at $DIR/retag.rs:+3:17: +3:24 _4 = &_5; // scope 1 at $DIR/retag.rs:+3:17: +3:36 - Retag(_4); // scope 1 at $DIR/retag.rs:+3:17: +3:36 StorageLive(_6); // scope 1 at $DIR/retag.rs:+3:29: +3:35 StorageLive(_7); // scope 1 at $DIR/retag.rs:+3:29: +3:35 _7 = &mut _1; // scope 1 at $DIR/retag.rs:+3:29: +3:35 - Retag(_7); // scope 1 at $DIR/retag.rs:+3:29: +3:35 _6 = &mut (*_7); // scope 1 at $DIR/retag.rs:+3:29: +3:35 - Retag([2phase] _6); // scope 1 at $DIR/retag.rs:+3:29: +3:35 _3 = Test::foo(move _4, move _6) -> [return: bb1, unwind: bb8]; // scope 1 at $DIR/retag.rs:+3:17: +3:36 // mir::Constant // + span: $DIR/retag.rs:33:25: 33:28 @@ -93,7 +90,6 @@ fn main() -> () { _9 = move _3; // scope 2 at $DIR/retag.rs:+4:19: +4:20 Retag(_9); // scope 2 at $DIR/retag.rs:+4:19: +4:20 _8 = &mut (*_9); // scope 2 at $DIR/retag.rs:+4:19: +4:20 - Retag(_8); // scope 2 at $DIR/retag.rs:+4:19: +4:20 StorageDead(_9); // scope 2 at $DIR/retag.rs:+4:22: +4:23 StorageLive(_10); // scope 3 at $DIR/retag.rs:+5:13: +5:14 _10 = move _8; // scope 3 at $DIR/retag.rs:+5:17: +5:18 @@ -101,7 +97,6 @@ fn main() -> () { StorageLive(_11); // scope 4 at $DIR/retag.rs:+7:13: +7:15 StorageLive(_12); // scope 4 at $DIR/retag.rs:+7:18: +7:29 _12 = &raw mut (*_10); // scope 4 at $DIR/retag.rs:+7:18: +7:19 - Retag([raw] _12); // scope 4 at $DIR/retag.rs:+7:18: +7:19 _11 = _12; // scope 4 at $DIR/retag.rs:+7:18: +7:29 StorageDead(_12); // scope 4 at $DIR/retag.rs:+7:29: +7:30 _2 = const (); // scope 1 at $DIR/retag.rs:+2:5: +8:6 @@ -122,9 +117,7 @@ fn main() -> () { StorageLive(_17); // scope 6 at $DIR/retag.rs:+15:16: +15:18 StorageLive(_18); // scope 6 at $DIR/retag.rs:+15:16: +15:18 _18 = &_1; // scope 6 at $DIR/retag.rs:+15:16: +15:18 - Retag(_18); // scope 6 at $DIR/retag.rs:+15:16: +15:18 _17 = &(*_18); // scope 6 at $DIR/retag.rs:+15:16: +15:18 - Retag(_17); // scope 6 at $DIR/retag.rs:+15:16: +15:18 _15 = move _16(move _17) -> bb3; // scope 6 at $DIR/retag.rs:+15:14: +15:19 } @@ -139,7 +132,6 @@ fn main() -> () { Deinit(_21); // scope 7 at $DIR/retag.rs:+18:5: +18:12 (_21.0: i32) = const 0_i32; // scope 7 at $DIR/retag.rs:+18:5: +18:12 _20 = &_21; // scope 7 at $DIR/retag.rs:+18:5: +18:24 - Retag(_20); // scope 7 at $DIR/retag.rs:+18:5: +18:24 StorageLive(_22); // scope 7 at $DIR/retag.rs:+18:21: +18:23 StorageLive(_23); // scope 7 at $DIR/retag.rs:+18:21: +18:23 _28 = const _; // scope 7 at $DIR/retag.rs:+18:21: +18:23 @@ -148,9 +140,7 @@ fn main() -> () { // + literal: Const { ty: &i32, val: Unevaluated(main, [], Some(promoted[0])) } Retag(_28); // scope 7 at $DIR/retag.rs:+18:21: +18:23 _23 = &(*_28); // scope 7 at $DIR/retag.rs:+18:21: +18:23 - Retag(_23); // scope 7 at $DIR/retag.rs:+18:21: +18:23 _22 = &(*_23); // scope 7 at $DIR/retag.rs:+18:21: +18:23 - Retag(_22); // scope 7 at $DIR/retag.rs:+18:21: +18:23 _19 = Test::foo_shr(move _20, move _22) -> [return: bb4, unwind: bb7]; // scope 7 at $DIR/retag.rs:+18:5: +18:24 // mir::Constant // + span: $DIR/retag.rs:48:13: 48:20 @@ -171,7 +161,6 @@ fn main() -> () { StorageLive(_25); // scope 7 at $DIR/retag.rs:+21:9: +21:11 StorageLive(_26); // scope 7 at $DIR/retag.rs:+21:14: +21:28 _26 = &raw const (*_15); // scope 7 at $DIR/retag.rs:+21:14: +21:16 - Retag([raw] _26); // scope 7 at $DIR/retag.rs:+21:14: +21:16 _25 = _26; // scope 7 at $DIR/retag.rs:+21:14: +21:28 StorageDead(_26); // scope 7 at $DIR/retag.rs:+21:28: +21:29 StorageLive(_27); // scope 8 at $DIR/retag.rs:+23:5: +23:18 diff --git a/src/test/mir-opt/retag.{impl#0}-foo.SimplifyCfg-elaborate-drops.after.mir b/src/test/mir-opt/retag.{impl#0}-foo.SimplifyCfg-elaborate-drops.after.mir index 08fd655ae29..4b50205fa80 100644 --- a/src/test/mir-opt/retag.{impl#0}-foo.SimplifyCfg-elaborate-drops.after.mir +++ b/src/test/mir-opt/retag.{impl#0}-foo.SimplifyCfg-elaborate-drops.after.mir @@ -11,9 +11,7 @@ fn <impl at $DIR/retag.rs:12:1: 12:10>::foo(_1: &Test, _2: &mut i32) -> &mut i32 Retag([fn entry] _2); // scope 0 at $DIR/retag.rs:+0:23: +0:24 StorageLive(_3); // scope 0 at $DIR/retag.rs:+1:9: +1:10 _3 = &mut (*_2); // scope 0 at $DIR/retag.rs:+1:9: +1:10 - Retag(_3); // scope 0 at $DIR/retag.rs:+1:9: +1:10 _0 = &mut (*_3); // scope 0 at $DIR/retag.rs:+1:9: +1:10 - Retag(_0); // scope 0 at $DIR/retag.rs:+1:9: +1:10 StorageDead(_3); // scope 0 at $DIR/retag.rs:+2:5: +2:6 return; // scope 0 at $DIR/retag.rs:+2:6: +2:6 } diff --git a/src/test/run-make-fulldeps/hotplug_codegen_backend/the_backend.rs b/src/test/run-make-fulldeps/hotplug_codegen_backend/the_backend.rs index 2c4309fbe66..3aa57d58908 100644 --- a/src/test/run-make-fulldeps/hotplug_codegen_backend/the_backend.rs +++ b/src/test/run-make-fulldeps/hotplug_codegen_backend/the_backend.rs @@ -67,7 +67,7 @@ impl CodegenBackend for TheBackend { if crate_type != CrateType::Rlib { sess.fatal(&format!("Crate type is {:?}", crate_type)); } - let output_name = out_filename(sess, crate_type, &outputs, &*crate_name.as_str()); + let output_name = out_filename(sess, crate_type, &outputs, crate_name); let mut out_file = ::std::fs::File::create(output_name).unwrap(); write!(out_file, "This has been \"compiled\" successfully.").unwrap(); } diff --git a/src/test/run-make/coverage-reports/Makefile b/src/test/run-make/coverage-reports/Makefile index 436aebf1174..d06cd9c6a54 100644 --- a/src/test/run-make/coverage-reports/Makefile +++ b/src/test/run-make/coverage-reports/Makefile @@ -80,7 +80,7 @@ ifdef RUSTC_BLESS_TEST rm -f expected_* endif -include clear_expected_if_blessed +-include clear_expected_if_blessed %: $(SOURCEDIR)/lib/%.rs # Compile the test library with coverage instrumentation diff --git a/src/test/rustdoc-gui/basic.goml b/src/test/rustdoc-gui/basic.goml deleted file mode 100644 index 60292835bc0..00000000000 --- a/src/test/rustdoc-gui/basic.goml +++ /dev/null @@ -1,4 +0,0 @@ -goto: "file://" + |DOC_PATH| + "/test_docs/index.html" -assert: ("#functions") -goto: "./struct.Foo.html" -assert: ("div.item-decl") diff --git a/src/test/rustdoc-gui/docblock-table.goml b/src/test/rustdoc-gui/docblock-table.goml index 4e316ce0bcb..8645c1b1949 100644 --- a/src/test/rustdoc-gui/docblock-table.goml +++ b/src/test/rustdoc-gui/docblock-table.goml @@ -2,3 +2,50 @@ goto: "file://" + |DOC_PATH| + "/test_docs/doc_block_table/struct.DocBlockTable. compare-elements-css: (".impl-items .docblock table th", ".top-doc .docblock table th", ["border"]) compare-elements-css: (".impl-items .docblock table td", ".top-doc .docblock table td", ["border"]) + +define-function: ( + "check-colors", + (theme, border_color, zebra_stripe_color), + [ + ("local-storage", {"rustdoc-use-system-theme": "false", "rustdoc-theme": |theme|}), + ("reload"), + ("assert-css", (".top-doc .docblock table tbody tr:nth-child(1)", { + "background-color": "rgba(0, 0, 0, 0)", + })), + ("assert-css", (".top-doc .docblock table tbody tr:nth-child(2)", { + "background-color": |zebra_stripe_color|, + })), + ("assert-css", (".top-doc .docblock table tbody tr:nth-child(3)", { + "background-color": "rgba(0, 0, 0, 0)", + })), + ("assert-css", (".top-doc .docblock table tbody tr:nth-child(4)", { + "background-color": |zebra_stripe_color|, + })), + ("assert-css", (".top-doc .docblock table td", { + "border-style": "solid", + "border-width": "1px", + "border-color": |border_color|, + })), + ("assert-css", (".top-doc .docblock table th", { + "border-style": "solid", + "border-width": "1px", + "border-color": |border_color|, + })), + ] +) + +call-function: ("check-colors", { + "theme": "dark", + "border_color": "rgb(224, 224, 224)", + "zebra_stripe_color": "rgb(42, 42, 42)", +}) +call-function: ("check-colors", { + "theme": "ayu", + "border_color": "rgb(92, 103, 115)", + "zebra_stripe_color": "rgb(25, 31, 38)", +}) +call-function: ("check-colors", { + "theme": "light", + "border_color": "rgb(224, 224, 224)", + "zebra_stripe_color": "rgb(245, 245, 245)", +}) diff --git a/src/test/rustdoc-gui/scrape-examples-button-focus.goml b/src/test/rustdoc-gui/scrape-examples-button-focus.goml index a222139f1dc..bba518db099 100644 --- a/src/test/rustdoc-gui/scrape-examples-button-focus.goml +++ b/src/test/rustdoc-gui/scrape-examples-button-focus.goml @@ -1,5 +1,6 @@ goto: "file://" + |DOC_PATH| + "/scrape_examples/fn.test.html" +// The next/prev buttons vertically scroll the code viewport between examples store-property: (initialScrollTop, ".scraped-example-list > .scraped-example pre", "scrollTop") focus: ".scraped-example-list > .scraped-example .next" press-key: "Enter" @@ -12,6 +13,7 @@ assert-property: (".scraped-example-list > .scraped-example pre", { "scrollTop": |initialScrollTop| }) +// The expand button increases the scrollHeight of the minimized code viewport store-property: (smallOffsetHeight, ".scraped-example-list > .scraped-example pre", "offsetHeight") assert-property-false: (".scraped-example-list > .scraped-example pre", { "scrollHeight": |smallOffsetHeight| diff --git a/src/test/rustdoc-gui/scrape-examples-toggle.goml b/src/test/rustdoc-gui/scrape-examples-toggle.goml new file mode 100644 index 00000000000..ee720afb788 --- /dev/null +++ b/src/test/rustdoc-gui/scrape-examples-toggle.goml @@ -0,0 +1,14 @@ +goto: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html" + +// Clicking "More examples..." will open additional examples +assert-attribute-false: (".more-examples-toggle", {"open": ""}) +click: ".more-examples-toggle" +assert-attribute: (".more-examples-toggle", {"open": ""}) + +// Toggling all docs will close additional examples +click: "#toggle-all-docs" +assert-attribute-false: (".more-examples-toggle", {"open": ""}) + +// After re-opening the docs, the additional examples should stay closed +click: "#toggle-all-docs" +assert-attribute-false: (".more-examples-toggle", {"open": ""}) diff --git a/src/test/rustdoc-gui/src/test_docs/lib.rs b/src/test/rustdoc-gui/src/test_docs/lib.rs index 1340511d472..d6eeab803df 100644 --- a/src/test/rustdoc-gui/src/test_docs/lib.rs +++ b/src/test/rustdoc-gui/src/test_docs/lib.rs @@ -343,6 +343,9 @@ pub mod doc_block_table { /// | header1 | header2 | /// |--------------------------|--------------------------| /// | Lorem Ipsum, Lorem Ipsum | Lorem Ipsum, Lorem Ipsum | + /// | Lorem Ipsum, Lorem Ipsum | Lorem Ipsum, Lorem Ipsum | + /// | Lorem Ipsum, Lorem Ipsum | Lorem Ipsum, Lorem Ipsum | + /// | Lorem Ipsum, Lorem Ipsum | Lorem Ipsum, Lorem Ipsum | pub struct DocBlockTable {} impl DocBlockTableTrait for DocBlockTable { diff --git a/src/test/rustdoc/const-evalutation-ice.rs b/src/test/rustdoc-ui/const-evalutation-ice.rs index 68c7f9c5686..0dd3bcaa289 100644 --- a/src/test/rustdoc/const-evalutation-ice.rs +++ b/src/test/rustdoc-ui/const-evalutation-ice.rs @@ -7,4 +7,5 @@ pub struct S { s: Cell<usize> } -pub const N: usize = 0 - (mem::size_of::<S>() != 4) as usize; +pub const N: usize = 0 - (mem::size_of::<S>() != 400) as usize; +//~^ ERROR evaluation of constant value failed diff --git a/src/test/rustdoc-ui/const-evalutation-ice.stderr b/src/test/rustdoc-ui/const-evalutation-ice.stderr new file mode 100644 index 00000000000..5d9c16c0765 --- /dev/null +++ b/src/test/rustdoc-ui/const-evalutation-ice.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/const-evalutation-ice.rs:10:22 + | +LL | pub const N: usize = 0 - (mem::size_of::<S>() != 400) as usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `0_usize - 1_usize`, which would overflow + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/src/test/ui/async-await/in-trait/nested-rpit.rs b/src/test/ui/async-await/in-trait/nested-rpit.rs new file mode 100644 index 00000000000..ae8e0aed0cc --- /dev/null +++ b/src/test/ui/async-await/in-trait/nested-rpit.rs @@ -0,0 +1,17 @@ +// check-pass +// edition: 2021 + +#![feature(async_fn_in_trait)] +#![feature(return_position_impl_trait_in_trait)] +#![allow(incomplete_features)] + +use std::future::Future; +use std::marker::PhantomData; + +trait Lockable<K, V> { + async fn lock_all_entries(&self) -> impl Future<Output = Guard<'_>>; +} + +struct Guard<'a>(PhantomData<&'a ()>); + +fn main() {} diff --git a/src/test/ui/diagnostic-width/long-E0308.rs b/src/test/ui/diagnostic-width/long-E0308.rs new file mode 100644 index 00000000000..3fd7a7110fd --- /dev/null +++ b/src/test/ui/diagnostic-width/long-E0308.rs @@ -0,0 +1,86 @@ +// compile-flags: --diagnostic-width=60 +// normalize-stderr-test: "long-type-\d+" -> "long-type-hash" + +struct Atype<T, K>(T, K); +struct Btype<T, K>(T, K); +struct Ctype<T, K>(T, K); + +fn main() { + let x: Atype< + Btype< + Ctype< + Atype< + Btype< + Ctype< + Atype< + Btype< + Ctype<i32, i32>, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + > = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok("") + )))))))))))))))))))))))))))))) + )))))))))))))))))))))))))))))); + //~^^^^^ ERROR E0308 + + let _ = Some(Ok(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some( + Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some( + Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some( + Some(Some(Some(Some(Some(Some(Some(Some(Some(""))))))))) + ))))))))))))))))) + )))))))))))))))))) + ))))))))))))))))) == Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(""))))))) + )))))))))))))))))))))))))))))) + )))))))))))))))))))))))); + //~^^^^^ ERROR E0308 + + let x: Atype< + Btype< + Ctype< + Atype< + Btype< + Ctype< + Atype< + Btype< + Ctype<i32, i32>, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + > = (); + //~^ ERROR E0308 + + let _: () = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(""))))))) + )))))))))))))))))))))))))))))) + )))))))))))))))))))))))); + //~^^^^^ ERROR E0308 +} diff --git a/src/test/ui/diagnostic-width/long-E0308.stderr b/src/test/ui/diagnostic-width/long-E0308.stderr new file mode 100644 index 00000000000..487ab23a1c1 --- /dev/null +++ b/src/test/ui/diagnostic-width/long-E0308.stderr @@ -0,0 +1,80 @@ +error[E0308]: mismatched types + --> $DIR/long-E0308.rs:33:9 + | +LL | let x: Atype< + | _____________- +LL | | Btype< +LL | | Ctype< +LL | | Atype< +... | +LL | | i32 +LL | | > = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok... + | | _____-___^ + | ||_____| + | | expected due to this +LL | | Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok... +LL | | Ok("") +LL | | )))))))))))))))))))))))))))))) +LL | | )))))))))))))))))))))))))))))); + | |__________________________________^ expected struct `Atype`, found enum `Result` + | + = note: expected struct `Atype<Btype<..., ...>, ...>` + the full type name has been written to '$TEST_BUILD_DIR/diagnostic-width/long-E0308/long-E0308.long-type-hash.txt' + found enum `Result<Result<..., ...>, ...>` + the full type name has been written to '$TEST_BUILD_DIR/diagnostic-width/long-E0308/long-E0308.long-type-hash.txt' + +error[E0308]: mismatched types + --> $DIR/long-E0308.rs:46:26 + | +LL | ))))))))))))))))) == Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(O... + | __________________________^ +LL | | Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(... +LL | | Ok(Ok(Ok(Ok(Ok(Ok(Ok(""))))))) +LL | | )))))))))))))))))))))))))))))) +LL | | )))))))))))))))))))))))); + | |____________________________^ expected enum `Option`, found enum `Result` + | + = note: expected enum `Option<Result<..., ...>>` + the full type name has been written to '$TEST_BUILD_DIR/diagnostic-width/long-E0308/long-E0308.long-type-hash.txt' + found enum `Result<Result<..., ...>, ...>` + the full type name has been written to '$TEST_BUILD_DIR/diagnostic-width/long-E0308/long-E0308.long-type-hash.txt' + +error[E0308]: mismatched types + --> $DIR/long-E0308.rs:77:9 + | +LL | let x: Atype< + | ____________- +LL | | Btype< +LL | | Ctype< +LL | | Atype< +... | +LL | | i32 +LL | | > = (); + | | - ^^ expected struct `Atype`, found `()` + | |_____| + | expected due to this + | + = note: expected struct `Atype<Btype<..., ...>, ...>` + the full type name has been written to '$TEST_BUILD_DIR/diagnostic-width/long-E0308/long-E0308.long-type-hash.txt' + found unit type `()` + +error[E0308]: mismatched types + --> $DIR/long-E0308.rs:80:17 + | +LL | let _: () = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(O... + | ____________--___^ + | | | + | | expected due to this +LL | | Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(... +LL | | Ok(Ok(Ok(Ok(Ok(Ok(Ok(""))))))) +LL | | )))))))))))))))))))))))))))))) +LL | | )))))))))))))))))))))))); + | |____________________________^ expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result<Result<..., ...>, ...>` + the full type name has been written to '$TEST_BUILD_DIR/diagnostic-width/long-E0308/long-E0308.long-type-hash.txt' + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/error-codes/E0275.stderr b/src/test/ui/error-codes/E0275.stderr index 49a4d984af9..451a683ac8a 100644 --- a/src/test/ui/error-codes/E0275.stderr +++ b/src/test/ui/error-codes/E0275.stderr @@ -5,7 +5,7 @@ LL | impl<T> Foo for T where Bar<T>: Foo {} | ^^^ | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`E0275`) -note: required for `Bar<Bar<Bar<Bar<Bar<Bar<...>>>>>>` to implement `Foo` +note: required for `Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<Bar<...>>>>>>>>>>>>>>>>>>>>>` to implement `Foo` --> $DIR/E0275.rs:6:9 | LL | impl<T> Foo for T where Bar<T>: Foo {} diff --git a/src/test/ui/feature-gates/feature-gate-impl_trait_in_fn_trait_return.stderr b/src/test/ui/feature-gates/feature-gate-impl_trait_in_fn_trait_return.stderr index c485bc5c3ab..760dcb615c8 100644 --- a/src/test/ui/feature-gates/feature-gate-impl_trait_in_fn_trait_return.stderr +++ b/src/test/ui/feature-gates/feature-gate-impl_trait_in_fn_trait_return.stderr @@ -3,12 +3,18 @@ error[E0562]: `impl Trait` only allowed in function and inherent method return t | LL | fn f() -> impl Fn() -> impl Sized { || () } | ^^^^^^^^^^ + | + = note: see issue #99697 <https://github.com/rust-lang/rust/issues/99697> for more information + = help: add `#![feature(impl_trait_in_fn_trait_return)]` to the crate attributes to enable error[E0562]: `impl Trait` only allowed in function and inherent method return types, not in `Fn` trait return --> $DIR/feature-gate-impl_trait_in_fn_trait_return.rs:3:32 | LL | fn g() -> &'static dyn Fn() -> impl Sized { &|| () } | ^^^^^^^^^^ + | + = note: see issue #99697 <https://github.com/rust-lang/rust/issues/99697> for more information + = help: add `#![feature(impl_trait_in_fn_trait_return)]` to the crate attributes to enable error: aborting due to 2 previous errors diff --git a/src/test/ui/issues/issue-20413.stderr b/src/test/ui/issues/issue-20413.stderr index 91509ceace8..78df445972c 100644 --- a/src/test/ui/issues/issue-20413.stderr +++ b/src/test/ui/issues/issue-20413.stderr @@ -14,7 +14,7 @@ LL | impl<T> Foo for T where NoData<T>: Foo { | ^^^ | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_20413`) -note: required for `NoData<NoData<NoData<NoData<NoData<NoData<...>>>>>>` to implement `Foo` +note: required for `NoData<NoData<NoData<NoData<NoData<NoData<NoData<NoData<NoData<NoData<NoData<NoData<NoData<...>>>>>>>>>>>>>` to implement `Foo` --> $DIR/issue-20413.rs:9:9 | LL | impl<T> Foo for T where NoData<T>: Foo { @@ -30,13 +30,13 @@ LL | impl<T> Bar for T where EvenLessData<T>: Baz { | ^^^ | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_20413`) -note: required for `AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<...>>>>>>` to implement `Bar` +note: required for `AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<...>>>>>>>` to implement `Bar` --> $DIR/issue-20413.rs:28:9 | LL | impl<T> Bar for T where EvenLessData<T>: Baz { | ^^^ ^ = note: the full type name has been written to '$TEST_BUILD_DIR/issues/issue-20413/issue-20413.long-type-hash.txt' -note: required for `EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<...>>>>>>` to implement `Baz` +note: required for `EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<...>>>>>>>` to implement `Baz` --> $DIR/issue-20413.rs:35:9 | LL | impl<T> Baz for T where AlmostNoData<T>: Bar { @@ -52,13 +52,13 @@ LL | impl<T> Baz for T where AlmostNoData<T>: Bar { | ^^^ | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_20413`) -note: required for `EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<...>>>>>>` to implement `Baz` +note: required for `EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<...>>>>>>>` to implement `Baz` --> $DIR/issue-20413.rs:35:9 | LL | impl<T> Baz for T where AlmostNoData<T>: Bar { | ^^^ ^ = note: the full type name has been written to '$TEST_BUILD_DIR/issues/issue-20413/issue-20413.long-type-hash.txt' -note: required for `AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<...>>>>>>` to implement `Bar` +note: required for `AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<EvenLessData<AlmostNoData<...>>>>>>>` to implement `Bar` --> $DIR/issue-20413.rs:28:9 | LL | impl<T> Bar for T where EvenLessData<T>: Baz { diff --git a/src/test/ui/issues/issue-23122-2.stderr b/src/test/ui/issues/issue-23122-2.stderr index 5828e027b59..1f50b06a0e4 100644 --- a/src/test/ui/issues/issue-23122-2.stderr +++ b/src/test/ui/issues/issue-23122-2.stderr @@ -5,7 +5,7 @@ LL | type Next = <GetNext<T::Next> as Next>::Next; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_23122_2`) -note: required for `GetNext<<<<<<... as Next>::Next as Next>::Next as Next>::Next as Next>::Next as Next>::Next>` to implement `Next` +note: required for `GetNext<<<<<<<... as Next>::Next as Next>::Next as Next>::Next as Next>::Next as Next>::Next as Next>::Next>` to implement `Next` --> $DIR/issue-23122-2.rs:10:15 | LL | impl<T: Next> Next for GetNext<T> { diff --git a/src/test/ui/recursion/issue-83150.stderr b/src/test/ui/recursion/issue-83150.stderr index 4d00a708313..dde8ad1b6b3 100644 --- a/src/test/ui/recursion/issue-83150.stderr +++ b/src/test/ui/recursion/issue-83150.stderr @@ -12,7 +12,7 @@ LL | func(&mut iter.map(|x| x + 1)) error[E0275]: overflow evaluating the requirement `Map<&mut Map<&mut Map<&mut Map<..., ...>, ...>, ...>, ...>: Iterator` | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_83150`) - = note: required for `&mut Map<&mut Map<&mut Map<..., ...>, ...>, ...>` to implement `Iterator` + = note: required for `&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<..., ...>, ...>, ...>, ...>, ...>, ...>, ...>` to implement `Iterator` = note: the full type name has been written to '$TEST_BUILD_DIR/recursion/issue-83150/issue-83150.long-type-hash.txt' error: aborting due to previous error; 1 warning emitted diff --git a/src/test/ui/regions/issue-102374.rs b/src/test/ui/regions/issue-102374.rs index e0a1164211a..fd71248d9cb 100644 --- a/src/test/ui/regions/issue-102374.rs +++ b/src/test/ui/regions/issue-102374.rs @@ -1,3 +1,4 @@ +// normalize-stderr-test: "long-type-\d+" -> "long-type-hash" use std::cell::Cell; #[rustfmt::skip] diff --git a/src/test/ui/regions/issue-102374.stderr b/src/test/ui/regions/issue-102374.stderr index 31b855c36be..157850693ab 100644 --- a/src/test/ui/regions/issue-102374.stderr +++ b/src/test/ui/regions/issue-102374.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/issue-102374.rs:16:5 + --> $DIR/issue-102374.rs:17:5 | LL | ) -> i32 { | --- expected `i32` because of return type @@ -7,7 +7,8 @@ LL | f | ^ expected `i32`, found fn pointer | = note: expected type `i32` - found fn pointer `for<'z1, 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, 'm, 'n, 'o, 'p, 'q, 'r, 's, 't, 'u, 'v, 'w, 'x, 'y, 'z, 'z0> fn(Cell<(&'z1 i32, &'a i32, &'b i32, &'c i32, &'d i32, &'e i32, &'f i32, &'g i32, &'h i32, &'i i32, &'j i32, &'k i32, &'l i32, &'m i32, &'n i32, &'o i32, &'p i32, &'q i32, &'r i32, &'s i32, &'t i32, &'u i32, &'v i32, &'w i32, &'x i32, &'y i32, &'z i32, &'z0 i32)>)` + found fn pointer `for<'z1, 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, 'm, 'n, 'o, 'p, 'q, 'r, 's, 't, 'u, 'v, 'w, 'x, 'y, 'z, 'z0> fn(Cell<...>)` + the full type name has been written to '$TEST_BUILD_DIR/regions/issue-102374/issue-102374.long-type-hash.txt' error: aborting due to previous error diff --git a/src/test/ui/traits/issue-91949-hangs-on-recursion.stderr b/src/test/ui/traits/issue-91949-hangs-on-recursion.stderr index a74d2524996..1f18c5daf66 100644 --- a/src/test/ui/traits/issue-91949-hangs-on-recursion.stderr +++ b/src/test/ui/traits/issue-91949-hangs-on-recursion.stderr @@ -17,7 +17,7 @@ error[E0275]: overflow evaluating the requirement `(): Sized` = help: consider increasing the recursion limit by adding a `#![recursion_limit = "512"]` attribute to your crate (`issue_91949_hangs_on_recursion`) = note: required for `std::iter::Empty<()>` to implement `Iterator` = note: 171 redundant requirements hidden - = note: required for `IteratorOfWrapped<(), Map<IteratorOfWrapped<(), Map<..., ...>>, ...>>` to implement `Iterator` + = note: required for `IteratorOfWrapped<(), Map<IteratorOfWrapped<(), Map<IteratorOfWrapped<(), Map<..., ...>>, ...>>, ...>>` to implement `Iterator` = note: the full type name has been written to '$TEST_BUILD_DIR/traits/issue-91949-hangs-on-recursion/issue-91949-hangs-on-recursion.long-type-hash.txt' error: aborting due to previous error; 1 warning emitted diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs index 10e6e252e94..f896a337f42 100644 --- a/src/tools/miri/src/borrow_tracker/mod.rs +++ b/src/tools/miri/src/borrow_tracker/mod.rs @@ -11,7 +11,6 @@ use rustc_target::abi::Size; use crate::*; pub mod stacked_borrows; -use stacked_borrows::diagnostics::RetagCause; pub type CallId = NonZeroU64; @@ -265,11 +264,19 @@ impl GlobalStateInner { impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { - fn retag(&mut self, kind: RetagKind, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + fn retag_ptr_value(&mut self, kind: RetagKind, val: &ImmTy<'tcx, Provenance>) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { let this = self.eval_context_mut(); let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method; match method { - BorrowTrackerMethod::StackedBorrows => this.sb_retag(kind, place), + BorrowTrackerMethod::StackedBorrows => this.sb_retag_ptr_value(kind, val), + } + } + + fn retag_place_contents(&mut self, kind: RetagKind, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method; + match method { + BorrowTrackerMethod::StackedBorrows => this.sb_retag_place_contents(kind, place), } } diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index 9a7b38b13a3..24b3489e0d1 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -459,10 +459,10 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { Operation::Dealloc(_) => format!(" due to deallocation"), Operation::Access(AccessOp { kind, tag, .. }) => format!(" due to {kind:?} access for {tag:?}"), - Operation::Retag(RetagOp { orig_tag, permission, .. }) => { + Operation::Retag(RetagOp { orig_tag, permission, new_tag, .. }) => { let permission = permission .expect("start_grant should set the current permission before popping a tag"); - format!(" due to {permission:?} retag from {orig_tag:?}") + format!(" due to {permission:?} retag from {orig_tag:?} (that retag created {new_tag:?})") } }; diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index bcac873251f..ffbc0086402 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -1,9 +1,13 @@ //! Implements "Stacked Borrows". See <https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md> //! for further information. +pub mod diagnostics; +mod item; +mod stack; + use log::trace; use std::cmp; -use std::fmt::{self, Write}; +use std::fmt::Write; use rustc_data_structures::fx::FxHashSet; use rustc_middle::mir::{Mutability, RetagKind}; @@ -15,15 +19,13 @@ use rustc_target::abi::{Abi, Size}; use crate::borrow_tracker::{ stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder, TagHistory}, - AccessKind, GlobalStateInner, ProtectorKind, RetagCause, RetagFields, + AccessKind, GlobalStateInner, ProtectorKind, RetagFields, }; use crate::*; -mod item; +use diagnostics::RetagCause; pub use item::{Item, Permission}; -mod stack; pub use stack::Stack; -pub mod diagnostics; pub type AllocState = Stacks; @@ -40,30 +42,104 @@ pub struct Stacks { modified_since_last_gc: bool, } -/// Indicates which kind of reference is being created. -/// Used by high-level `reborrow` to compute which permissions to grant to the -/// new pointer. -#[derive(Copy, Clone, Hash, PartialEq, Eq)] -enum RefKind { - /// `Box`. - Box, - /// `&mut`. - Unique { two_phase: bool }, - /// `&` with or without interior mutability. - Shared, - /// `*mut`/`*const` (raw pointers). - Raw { mutable: bool }, +/// Indicates which permissions to grant to the retagged pointer. +#[derive(Clone, Debug)] +enum NewPermission { + Uniform { + perm: Permission, + access: Option<AccessKind>, + protector: Option<ProtectorKind>, + }, + FreezeSensitive { + freeze_perm: Permission, + freeze_access: Option<AccessKind>, + freeze_protector: Option<ProtectorKind>, + nonfreeze_perm: Permission, + nonfreeze_access: Option<AccessKind>, + // nonfreeze_protector must always be None + }, } -impl fmt::Display for RefKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl NewPermission { + /// A key function: determine the permissions to grant at a retag for the given kind of + /// reference/pointer. + fn from_ref_ty<'tcx>( + ty: ty::Ty<'tcx>, + kind: RetagKind, + cx: &crate::MiriInterpCx<'_, 'tcx>, + ) -> Self { + let protector = (kind == RetagKind::FnEntry).then_some(ProtectorKind::StrongProtector); + match ty.kind() { + ty::Ref(_, pointee, Mutability::Mut) => { + if kind == RetagKind::TwoPhase { + // We mostly just give up on 2phase-borrows, and treat these exactly like raw pointers. + assert!(protector.is_none()); // RetagKind can't be both FnEntry and TwoPhase. + NewPermission::Uniform { + perm: Permission::SharedReadWrite, + access: None, + protector: None, + } + } else if pointee.is_unpin(*cx.tcx, cx.param_env()) { + // A regular full mutable reference. + NewPermission::Uniform { + perm: Permission::Unique, + access: Some(AccessKind::Write), + protector, + } + } else { + NewPermission::Uniform { + perm: Permission::SharedReadWrite, + // FIXME: We emit `dereferenceable` for `!Unpin` mutable references, so we + // should do fake accesses here. But then we run into + // <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>, so for now + // we don't do that. + access: None, + protector, + } + } + } + ty::RawPtr(ty::TypeAndMut { mutbl: Mutability::Mut, .. }) => { + assert!(protector.is_none()); // RetagKind can't be both FnEntry and Raw. + // Mutable raw pointer. No access, not protected. + NewPermission::Uniform { + perm: Permission::SharedReadWrite, + access: None, + protector: None, + } + } + ty::Ref(_, _pointee, Mutability::Not) => { + NewPermission::FreezeSensitive { + freeze_perm: Permission::SharedReadOnly, + freeze_access: Some(AccessKind::Read), + freeze_protector: protector, + nonfreeze_perm: Permission::SharedReadWrite, + // Inside UnsafeCell, this does *not* count as an access, as there + // might actually be mutable references further up the stack that + // we have to keep alive. + nonfreeze_access: None, + // We do not protect inside UnsafeCell. + // This fixes https://github.com/rust-lang/rust/issues/55005. + } + } + ty::RawPtr(ty::TypeAndMut { mutbl: Mutability::Not, .. }) => { + assert!(protector.is_none()); // RetagKind can't be both FnEntry and Raw. + // `*const T`, when freshly created, are read-only in the frozen part. + NewPermission::FreezeSensitive { + freeze_perm: Permission::SharedReadOnly, + freeze_access: Some(AccessKind::Read), + freeze_protector: None, + nonfreeze_perm: Permission::SharedReadWrite, + nonfreeze_access: None, + } + } + _ => unreachable!(), + } + } + + fn protector(&self) -> Option<ProtectorKind> { match self { - RefKind::Box => write!(f, "Box"), - RefKind::Unique { two_phase: false } => write!(f, "unique reference"), - RefKind::Unique { two_phase: true } => write!(f, "unique reference (two-phase)"), - RefKind::Shared => write!(f, "shared reference"), - RefKind::Raw { mutable: true } => write!(f, "raw (mutable) pointer"), - RefKind::Raw { mutable: false } => write!(f, "raw (constant) pointer"), + NewPermission::Uniform { protector, .. } => *protector, + NewPermission::FreezeSensitive { freeze_protector, .. } => *freeze_protector, } } } @@ -518,10 +594,9 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' &mut self, place: &MPlaceTy<'tcx, Provenance>, size: Size, - kind: RefKind, - retag_cause: RetagCause, // What caused this retag, for diagnostics only + new_perm: NewPermission, new_tag: BorTag, - protect: Option<ProtectorKind>, + retag_cause: RetagCause, // What caused this retag, for diagnostics only ) -> InterpResult<'tcx, Option<AllocId>> { let this = self.eval_context_mut(); @@ -532,20 +607,16 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' let global = this.machine.borrow_tracker.as_ref().unwrap().borrow(); let ty = place.layout.ty; if global.tracked_pointer_tags.contains(&new_tag) { - let mut kind_str = format!("{kind}"); - match kind { - RefKind::Unique { two_phase: false } - if !ty.is_unpin(*this.tcx, this.param_env()) => - { - write!(kind_str, " (!Unpin pointee type {ty})").unwrap() - }, - RefKind::Shared - if !ty.is_freeze(*this.tcx, this.param_env()) => - { - write!(kind_str, " (!Freeze pointee type {ty})").unwrap() - }, - _ => write!(kind_str, " (pointee type {ty})").unwrap(), - }; + let mut kind_str = String::new(); + match new_perm { + NewPermission::Uniform { perm, .. } => + write!(kind_str, "{perm:?} permission").unwrap(), + NewPermission::FreezeSensitive { freeze_perm, .. } if ty.is_freeze(*this.tcx, this.param_env()) => + write!(kind_str, "{freeze_perm:?} permission").unwrap(), + NewPermission::FreezeSensitive { freeze_perm, nonfreeze_perm, .. } => + write!(kind_str, "{freeze_perm:?}/{nonfreeze_perm:?} permission for frozen/non-frozen parts").unwrap(), + } + write!(kind_str, " (pointee type {ty})").unwrap(); this.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag( new_tag.inner(), Some(kind_str), @@ -579,7 +650,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' ); let mut dcx = dcx.build(&mut stacked_borrows.history, base_offset); dcx.log_creation(); - if protect.is_some() { + if new_perm.protector().is_some() { dcx.log_protector(); } }, @@ -592,8 +663,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' if size == Size::ZERO { trace!( - "reborrow of size 0: {} reference {:?} derived from {:?} (pointee {})", - kind, + "reborrow of size 0: reference {:?} derived from {:?} (pointee {})", new_tag, place.ptr, place.layout.ty, @@ -630,8 +700,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' } trace!( - "reborrow: {} reference {:?} derived from {:?} (pointee {}): {:?}, size {}", - kind, + "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}", new_tag, orig_tag, place.layout.ty, @@ -639,7 +708,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' size.bytes() ); - if let Some(protect) = protect { + if let Some(protect) = new_perm.protector() { // See comment in `Stack::item_invalidated` for why we store the tag twice. this.frame_mut().extra.borrow_tracker.as_mut().unwrap().protected_tags.push(new_tag); this.machine @@ -651,30 +720,45 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' .insert(new_tag, protect); } - // Update the stacks. - // Make sure that raw pointers and mutable shared references are reborrowed "weak": - // There could be existing unique pointers reborrowed from them that should remain valid! - let (perm, access) = match kind { - RefKind::Unique { two_phase } => { - // Permission is Unique only if the type is `Unpin` and this is not twophase - if !two_phase && place.layout.ty.is_unpin(*this.tcx, this.param_env()) { - (Permission::Unique, Some(AccessKind::Write)) - } else { - // FIXME: We emit `dereferenceable` for `!Unpin` mutable references, so we - // should do fake accesses here. But then we run into - // <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>, so for now - // we don't do that. - (Permission::SharedReadWrite, None) + // Update the stacks, according to the new permission information we are given. + match new_perm { + NewPermission::Uniform { perm, access, protector } => { + assert!(perm != Permission::SharedReadOnly); + // Here we can avoid `borrow()` calls because we have mutable references. + // Note that this asserts that the allocation is mutable -- but since we are creating a + // mutable pointer, that seems reasonable. + let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?; + let stacked_borrows = alloc_extra.borrow_tracker_sb_mut().get_mut(); + let item = Item::new(new_tag, perm, protector.is_some()); + let range = alloc_range(base_offset, size); + let global = machine.borrow_tracker.as_ref().unwrap().borrow(); + let dcx = DiagnosticCxBuilder::retag( + machine, + retag_cause, + new_tag, + orig_tag, + alloc_range(base_offset, size), + ); + stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| { + stack.grant(orig_tag, item, access, &global, dcx, exposed_tags) + })?; + drop(global); + if let Some(access) = access { + assert_eq!(access, AccessKind::Write); + // Make sure the data race model also knows about this. + if let Some(data_race) = alloc_extra.data_race.as_mut() { + data_race.write(alloc_id, range, machine)?; + } } } - RefKind::Box => (Permission::Unique, Some(AccessKind::Write)), - RefKind::Raw { mutable: true } => { - // Creating a raw ptr does not count as an access - (Permission::SharedReadWrite, None) - } - RefKind::Shared | RefKind::Raw { mutable: false } => { - // Shared references and *const are a whole different kind of game, the - // permission is not uniform across the entire range! + NewPermission::FreezeSensitive { + freeze_perm, + freeze_access, + freeze_protector, + nonfreeze_perm, + nonfreeze_access, + } => { + // The permission is not uniform across the entire range! // We need a frozen-sensitive reborrow. // We have to use shared references to alloc/memory_extra here since // `visit_freeze_sensitive` needs to access the global state. @@ -684,22 +768,12 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' // Adjust range. range.start += base_offset; // We are only ever `SharedReadOnly` inside the frozen bits. - let (perm, access) = if frozen { - (Permission::SharedReadOnly, Some(AccessKind::Read)) - } else { - // Inside UnsafeCell, this does *not* count as an access, as there - // might actually be mutable references further up the stack that - // we have to keep alive. - (Permission::SharedReadWrite, None) - }; - let protected = if frozen { - protect.is_some() + let (perm, access, protector) = if frozen { + (freeze_perm, freeze_access, freeze_protector) } else { - // We do not protect inside UnsafeCell. - // This fixes https://github.com/rust-lang/rust/issues/55005. - false + (nonfreeze_perm, nonfreeze_access, None) }; - let item = Item::new(new_tag, perm, protected); + let item = Item::new(new_tag, perm, protector.is_some()); let global = this.machine.borrow_tracker.as_ref().unwrap().borrow(); let dcx = DiagnosticCxBuilder::retag( &this.machine, @@ -721,34 +795,6 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' } Ok(()) })?; - return Ok(Some(alloc_id)); - } - }; - - // Here we can avoid `borrow()` calls because we have mutable references. - // Note that this asserts that the allocation is mutable -- but since we are creating a - // mutable pointer, that seems reasonable. - let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?; - let stacked_borrows = alloc_extra.borrow_tracker_sb_mut().get_mut(); - let item = Item::new(new_tag, perm, protect.is_some()); - let range = alloc_range(base_offset, size); - let global = machine.borrow_tracker.as_ref().unwrap().borrow(); - let dcx = DiagnosticCxBuilder::retag( - machine, - retag_cause, - new_tag, - orig_tag, - alloc_range(base_offset, size), - ); - stacked_borrows.for_each(range, dcx, |stack, dcx, exposed_tags| { - stack.grant(orig_tag, item, access, &global, dcx, exposed_tags) - })?; - drop(global); - if let Some(access) = access { - assert_eq!(access, AccessKind::Write); - // Make sure the data race model also knows about this. - if let Some(data_race) = alloc_extra.data_race.as_mut() { - data_race.write(alloc_id, range, machine)?; } } @@ -760,9 +806,8 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' fn sb_retag_reference( &mut self, val: &ImmTy<'tcx, Provenance>, - kind: RefKind, - retag_cause: RetagCause, // What caused this retag, for diagnostics only - protect: Option<ProtectorKind>, + new_perm: NewPermission, + cause: RetagCause, // What caused this retag, for diagnostics only ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { let this = self.eval_context_mut(); // We want a place for where the ptr *points to*, so we get one. @@ -780,7 +825,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr(); // Reborrow. - let alloc_id = this.sb_reborrow(&place, size, kind, retag_cause, new_tag, protect)?; + let alloc_id = this.sb_reborrow(&place, size, new_perm, new_tag, cause)?; // Adjust pointer. let new_place = place.map_provenance(|p| { @@ -807,7 +852,22 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { - fn sb_retag( + fn sb_retag_ptr_value( + &mut self, + kind: RetagKind, + val: &ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { + let this = self.eval_context_mut(); + let new_perm = NewPermission::from_ref_ty(val.layout.ty, kind, this); + let retag_cause = match kind { + RetagKind::TwoPhase { .. } => RetagCause::TwoPhase, + RetagKind::FnEntry => unreachable!(), + RetagKind::Raw | RetagKind::Default => RetagCause::Normal, + }; + this.sb_retag_reference(&val, new_perm, retag_cause) + } + + fn sb_retag_place_contents( &mut self, kind: RetagKind, place: &PlaceTy<'tcx, Provenance>, @@ -815,9 +875,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields; let retag_cause = match kind { - RetagKind::TwoPhase { .. } => RetagCause::TwoPhase, + RetagKind::Raw | RetagKind::TwoPhase { .. } => unreachable!(), // these can only happen in `retag_ptr_value` RetagKind::FnEntry => RetagCause::FnEntry, - RetagKind::Raw | RetagKind::Default => RetagCause::Normal, + RetagKind::Default => RetagCause::Normal, }; let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, retag_fields }; return visitor.visit_value(place); @@ -831,15 +891,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> { #[inline(always)] // yes this helps in our benchmarks - fn retag_place( + fn retag_ptr_inplace( &mut self, place: &PlaceTy<'tcx, Provenance>, - ref_kind: RefKind, + new_perm: NewPermission, retag_cause: RetagCause, - protector: Option<ProtectorKind>, ) -> InterpResult<'tcx> { let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?; - let val = self.ecx.sb_retag_reference(&val, ref_kind, retag_cause, protector)?; + let val = self.ecx.sb_retag_reference(&val, new_perm, retag_cause)?; self.ecx.write_immediate(*val, place)?; Ok(()) } @@ -856,13 +915,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { // Boxes get a weak protectors, since they may be deallocated. - self.retag_place( - place, - RefKind::Box, - self.retag_cause, - /*protector*/ - (self.kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector), - ) + let new_perm = NewPermission::Uniform { + perm: Permission::Unique, + access: Some(AccessKind::Write), + protector: (self.kind == RetagKind::FnEntry) + .then_some(ProtectorKind::WeakProtector), + }; + self.retag_ptr_inplace(place, new_perm, self.retag_cause) } fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { @@ -876,36 +935,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Check the type of this value to see what to do with it (retag, or recurse). match place.layout.ty.kind() { - ty::Ref(_, _, mutbl) => { - let ref_kind = match mutbl { - Mutability::Mut => - RefKind::Unique { two_phase: self.kind == RetagKind::TwoPhase }, - Mutability::Not => RefKind::Shared, - }; - self.retag_place( - place, - ref_kind, - self.retag_cause, - /*protector*/ - (self.kind == RetagKind::FnEntry) - .then_some(ProtectorKind::StrongProtector), - )?; + ty::Ref(..) => { + let new_perm = + NewPermission::from_ref_ty(place.layout.ty, self.kind, self.ecx); + self.retag_ptr_inplace(place, new_perm, self.retag_cause)?; } - ty::RawPtr(tym) => { - // We definitely do *not* want to recurse into raw pointers -- wide raw - // pointers have fields, and for dyn Trait pointees those can have reference - // type! - if self.kind == RetagKind::Raw { - // Raw pointers need to be enabled. - self.retag_place( - place, - RefKind::Raw { mutable: tym.mutbl == Mutability::Mut }, - self.retag_cause, - /*protector*/ None, - )?; - } + ty::RawPtr(..) => { + // We do *not* want to recurse into raw pointers -- wide raw pointers have + // fields, and for dyn Trait pointees those can have reference type! } - _ if place.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_box()) => { + ty::Adt(adt, _) if adt.is_box() => { // Recurse for boxes, they require some tricky handling and will end up in `visit_box` above. // (Yes this means we technically also recursively retag the allocator itself // even if field retagging is not enabled. *shrug*) @@ -953,12 +992,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?; let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout); // Reborrow it. With protection! That is part of the point. - let val = this.sb_retag_reference( - &val, - RefKind::Unique { two_phase: false }, - RetagCause::FnReturn, - /*protector*/ Some(ProtectorKind::StrongProtector), - )?; + let new_perm = NewPermission::Uniform { + perm: Permission::Unique, + access: Some(AccessKind::Write), + protector: Some(ProtectorKind::StrongProtector), + }; + let val = this.sb_retag_reference(&val, new_perm, RetagCause::FnReturn)?; // And use reborrowed pointer for return place. let return_place = this.ref_to_mplace(&val)?; this.frame_mut().return_place = return_place.into(); diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 074fa032dcc..d0fb9f9b0b5 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -63,9 +63,9 @@ impl MachineStopType for TerminationInfo {} /// Miri specific diagnostics pub enum NonHaltingDiagnostic { - /// (new_tag, new_kind, (alloc_id, base_offset, orig_tag)) + /// (new_tag, new_perm, (alloc_id, base_offset, orig_tag)) /// - /// new_kind is `None` for base tags. + /// new_perm is `None` for base tags. CreatedPointerTag(NonZeroU64, Option<String>, Option<(AllocId, AllocRange, ProvenanceExtra)>), /// This `Item` was popped from the borrow stack. The string explains the reason. PoppedPointerTag(Item, String), @@ -393,10 +393,10 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { let msg = match &e { CreatedPointerTag(tag, None, _) => format!("created base tag {tag:?}"), - CreatedPointerTag(tag, Some(kind), None) => format!("created {tag:?} for {kind}"), - CreatedPointerTag(tag, Some(kind), Some((alloc_id, range, orig_tag))) => + CreatedPointerTag(tag, Some(perm), None) => format!("created {tag:?} with {perm} derived from unknown tag"), + CreatedPointerTag(tag, Some(perm), Some((alloc_id, range, orig_tag))) => format!( - "created tag {tag:?} for {kind} at {alloc_id:?}{range:?} derived from {orig_tag:?}" + "created tag {tag:?} with {perm} at {alloc_id:?}{range:?} derived from {orig_tag:?}" ), PoppedPointerTag(item, cause) => format!("popped tracked tag for item {item:?}{cause}"), CreatedCallId(id) => format!("function call with id {id}"), diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index c110229c985..e5b1eb2e487 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -967,8 +967,9 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { ptr: Pointer<Self::Provenance>, ) -> InterpResult<'tcx> { match ptr.provenance { - Provenance::Concrete { alloc_id, tag } => - intptrcast::GlobalStateInner::expose_ptr(ecx, alloc_id, tag), + Provenance::Concrete { alloc_id, tag } => { + intptrcast::GlobalStateInner::expose_ptr(ecx, alloc_id, tag) + } Provenance::Wildcard => { // No need to do anything for wildcard pointers as // their provenances have already been previously exposed. @@ -1055,13 +1056,26 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { } #[inline(always)] - fn retag( + fn retag_ptr_value( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + kind: mir::RetagKind, + val: &ImmTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { + if ecx.machine.borrow_tracker.is_some() { + ecx.retag_ptr_value(kind, val) + } else { + Ok(val.clone()) + } + } + + #[inline(always)] + fn retag_place_contents( ecx: &mut InterpCx<'mir, 'tcx, Self>, kind: mir::RetagKind, place: &PlaceTy<'tcx, Provenance>, ) -> InterpResult<'tcx> { if ecx.machine.borrow_tracker.is_some() { - ecx.retag(kind, place)?; + ecx.retag_place_contents(kind, place)?; } Ok(()) } |
