diff options
| author | The Miri Cronjob Bot <miri@cron.bot> | 2025-06-06 05:02:49 +0000 |
|---|---|---|
| committer | The Miri Cronjob Bot <miri@cron.bot> | 2025-06-06 05:02:49 +0000 |
| commit | c44bc10b67939cc96a2e1c32c352beaaedb95d1b (patch) | |
| tree | 8e2d7a7fe4590bf066323a503a4a17290a353f47 /compiler | |
| parent | 8c410ca291a68bf23ca3694b1bbda631323b12cd (diff) | |
| parent | d945c8562b49df98af25ed43b41bdbc59b8385cc (diff) | |
| download | rust-c44bc10b67939cc96a2e1c32c352beaaedb95d1b.tar.gz rust-c44bc10b67939cc96a2e1c32c352beaaedb95d1b.zip | |
Merge from rustc
Diffstat (limited to 'compiler')
29 files changed, 1165 insertions, 585 deletions
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index f41627e479f..64d4a00ea4d 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1406,7 +1406,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { }; let span = self.tcx.sess.source_map().start_point(t.span).shrink_to_hi(); let region = Lifetime { ident: Ident::new(kw::UnderscoreLifetime, span), id }; - (region, LifetimeSyntax::Hidden) + (region, LifetimeSyntax::Implicit) } }; self.lower_lifetime(®ion, LifetimeSource::Reference, syntax) @@ -1790,7 +1790,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { id, Ident::new(kw::UnderscoreLifetime, span), LifetimeSource::Path { angle_brackets }, - LifetimeSyntax::Hidden, + LifetimeSyntax::Implicit, ) } @@ -2422,7 +2422,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { Ident::new(kw::UnderscoreLifetime, self.lower_span(span)), hir::LifetimeKind::ImplicitObjectLifetimeDefault, LifetimeSource::Other, - LifetimeSyntax::Hidden, + LifetimeSyntax::Implicit, ); debug!("elided_dyn_bound: r={:?}", r); self.arena.alloc(r) diff --git a/compiler/rustc_borrowck/src/constraints/mod.rs b/compiler/rustc_borrowck/src/constraints/mod.rs index 514bbfe359b..99ddccabd15 100644 --- a/compiler/rustc_borrowck/src/constraints/mod.rs +++ b/compiler/rustc_borrowck/src/constraints/mod.rs @@ -5,11 +5,9 @@ use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::mir::ConstraintCategory; use rustc_middle::ty::{RegionVid, TyCtxt, VarianceDiagInfo}; use rustc_span::Span; -use tracing::{debug, instrument}; +use tracing::debug; -use crate::region_infer::{AnnotatedSccs, ConstraintSccs, RegionDefinition, SccAnnotations}; use crate::type_check::Locations; -use crate::universal_regions::UniversalRegions; pub(crate) mod graph; @@ -53,112 +51,6 @@ impl<'tcx> OutlivesConstraintSet<'tcx> { ) -> &IndexSlice<OutlivesConstraintIndex, OutlivesConstraint<'tcx>> { &self.outlives } - - /// Computes cycles (SCCs) in the graph of regions. In particular, - /// find all regions R1, R2 such that R1: R2 and R2: R1 and group - /// them into an SCC, and find the relationships between SCCs. - pub(crate) fn compute_sccs( - &self, - static_region: RegionVid, - definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>, - ) -> AnnotatedSccs { - let constraint_graph = self.graph(definitions.len()); - let region_graph = &constraint_graph.region_graph(self, static_region); - let mut annotation_visitor = SccAnnotations::new(definitions); - ( - ConstraintSccs::new_with_annotation(®ion_graph, &mut annotation_visitor), - annotation_visitor.scc_to_annotation, - ) - } - - /// This method handles Universe errors by rewriting the constraint - /// graph. For each strongly connected component in the constraint - /// graph such that there is a series of constraints - /// A: B: C: ... : X where - /// A's universe is smaller than X's and A is a placeholder, - /// add a constraint that A: 'static. This is a safe upper bound - /// in the face of borrow checker/trait solver limitations that will - /// eventually go away. - /// - /// For a more precise definition, see the documentation for - /// [`crate::region_infer::RegionTracker`]. - /// - /// This edge case used to be handled during constraint propagation - /// by iterating over the strongly connected components in the constraint - /// graph while maintaining a set of bookkeeping mappings similar - /// to what is stored in `RegionTracker` and manually adding 'static as - /// needed. - /// - /// It was rewritten as part of the Polonius project with the goal of moving - /// higher-kindedness concerns out of the path of the borrow checker, - /// for two reasons: - /// - /// 1. Implementing Polonius is difficult enough without also - /// handling them. - /// 2. The long-term goal is to handle higher-kinded concerns - /// in the trait solver, where they belong. This avoids - /// logic duplication and allows future trait solvers - /// to compute better bounds than for example our - /// "must outlive 'static" here. - /// - /// This code is a stop-gap measure in preparation for the future trait solver. - /// - /// Every constraint added by this method is an - /// internal `IllegalUniverse` constraint. - #[instrument(skip(self, universal_regions, definitions))] - pub(crate) fn add_outlives_static( - &mut self, - universal_regions: &UniversalRegions<'tcx>, - definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>, - ) -> AnnotatedSccs { - let fr_static = universal_regions.fr_static; - let (sccs, annotations) = self.compute_sccs(fr_static, definitions); - - // Changed to `true` if we added any constraints to `self` and need to - // recompute SCCs. - let mut added_constraints = false; - - for scc in sccs.all_sccs() { - // No point in adding 'static: 'static! - // This micro-optimisation makes somewhat sense - // because static outlives *everything*. - if scc == sccs.scc(fr_static) { - continue; - } - - let annotation = annotations[scc]; - - // If this SCC participates in a universe violation, - // e.g. if it reaches a region with a universe smaller than - // the largest region reached, add a requirement that it must - // outlive `'static`. - if annotation.has_incompatible_universes() { - // Optimisation opportunity: this will add more constraints than - // needed for correctness, since an SCC upstream of another with - // a universe violation will "infect" its downstream SCCs to also - // outlive static. - added_constraints = true; - let scc_representative_outlives_static = OutlivesConstraint { - sup: annotation.representative, - sub: fr_static, - category: ConstraintCategory::IllegalUniverse, - locations: Locations::All(rustc_span::DUMMY_SP), - span: rustc_span::DUMMY_SP, - variance_info: VarianceDiagInfo::None, - from_closure: false, - }; - self.push(scc_representative_outlives_static); - } - } - - if added_constraints { - // We changed the constraint set and so must recompute SCCs. - self.compute_sccs(fr_static, definitions) - } else { - // If we didn't add any back-edges; no more work needs doing - (sccs, annotations) - } - } } impl<'tcx> Index<OutlivesConstraintIndex> for OutlivesConstraintSet<'tcx> { diff --git a/compiler/rustc_borrowck/src/handle_placeholders.rs b/compiler/rustc_borrowck/src/handle_placeholders.rs new file mode 100644 index 00000000000..aaaf2f45c86 --- /dev/null +++ b/compiler/rustc_borrowck/src/handle_placeholders.rs @@ -0,0 +1,348 @@ +//! Logic for lowering higher-kinded outlives constraints +//! (with placeholders and universes) and turn them into regular +//! outlives constraints. + +use rustc_data_structures::frozen::Frozen; +use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::graph::scc; +use rustc_data_structures::graph::scc::Sccs; +use rustc_index::IndexVec; +use rustc_infer::infer::RegionVariableOrigin; +use rustc_middle::mir::ConstraintCategory; +use rustc_middle::ty::{RegionVid, UniverseIndex}; +use tracing::debug; + +use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet}; +use crate::consumers::OutlivesConstraint; +use crate::diagnostics::UniverseInfo; +use crate::member_constraints::MemberConstraintSet; +use crate::region_infer::values::{LivenessValues, PlaceholderIndices}; +use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest}; +use crate::ty::VarianceDiagInfo; +use crate::type_check::free_region_relations::UniversalRegionRelations; +use crate::type_check::{Locations, MirTypeckRegionConstraints}; +use crate::universal_regions::UniversalRegions; +use crate::{BorrowckInferCtxt, NllRegionVariableOrigin}; + +/// A set of outlives constraints after rewriting to remove +/// higher-kinded constraints. +pub(crate) struct LoweredConstraints<'tcx> { + pub(crate) constraint_sccs: Sccs<RegionVid, ConstraintSccIndex>, + pub(crate) definitions: Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>, + pub(crate) scc_annotations: IndexVec<ConstraintSccIndex, RegionTracker>, + pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>, + pub(crate) outlives_constraints: Frozen<OutlivesConstraintSet<'tcx>>, + pub(crate) type_tests: Vec<TypeTest<'tcx>>, + pub(crate) liveness_constraints: LivenessValues, + pub(crate) universe_causes: FxIndexMap<UniverseIndex, UniverseInfo<'tcx>>, + pub(crate) placeholder_indices: PlaceholderIndices, +} + +impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> { + pub(crate) fn init(definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>) -> Self { + Self { scc_to_annotation: IndexVec::new(), definitions } + } +} + +/// A Visitor for SCC annotation construction. +pub(crate) struct SccAnnotations<'d, 'tcx, A: scc::Annotation> { + pub(crate) scc_to_annotation: IndexVec<ConstraintSccIndex, A>, + definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>, +} + +impl scc::Annotations<RegionVid> for SccAnnotations<'_, '_, RegionTracker> { + fn new(&self, element: RegionVid) -> RegionTracker { + RegionTracker::new(element, &self.definitions[element]) + } + + fn annotate_scc(&mut self, scc: ConstraintSccIndex, annotation: RegionTracker) { + let idx = self.scc_to_annotation.push(annotation); + assert!(idx == scc); + } + + type Ann = RegionTracker; + type SccIdx = ConstraintSccIndex; +} + +/// An annotation for region graph SCCs that tracks +/// the values of its elements. This annotates a single SCC. +#[derive(Copy, Debug, Clone)] +pub(crate) struct RegionTracker { + /// The largest universe of a placeholder reached from this SCC. + /// This includes placeholders within this SCC. + max_placeholder_universe_reached: UniverseIndex, + + /// The largest universe nameable from this SCC. + /// It is the smallest nameable universes of all + /// existential regions reachable from it. + max_nameable_universe: UniverseIndex, + + /// The representative Region Variable Id for this SCC. + pub(crate) representative: Representative, +} + +impl RegionTracker { + pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self { + let placeholder_universe = + if matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)) { + definition.universe + } else { + UniverseIndex::ROOT + }; + + Self { + max_placeholder_universe_reached: placeholder_universe, + max_nameable_universe: definition.universe, + representative: Representative::new(rvid, definition), + } + } + + /// The largest universe this SCC can name. It's the smallest + /// largest nameable uninverse of any reachable region. + pub(crate) fn max_nameable_universe(self) -> UniverseIndex { + self.max_nameable_universe + } + + fn merge_min_max_seen(&mut self, other: &Self) { + self.max_placeholder_universe_reached = std::cmp::max( + self.max_placeholder_universe_reached, + other.max_placeholder_universe_reached, + ); + + self.max_nameable_universe = + std::cmp::min(self.max_nameable_universe, other.max_nameable_universe); + } + + /// Returns `true` if during the annotated SCC reaches a placeholder + /// with a universe larger than the smallest nameable universe of any + /// reachable existential region. + pub(crate) fn has_incompatible_universes(&self) -> bool { + self.max_nameable_universe().cannot_name(self.max_placeholder_universe_reached) + } + + /// Determine if the tracked universes of the two SCCs are compatible. + pub(crate) fn universe_compatible_with(&self, other: Self) -> bool { + self.max_nameable_universe().can_name(other.max_nameable_universe()) + || self.max_nameable_universe().can_name(other.max_placeholder_universe_reached) + } +} + +impl scc::Annotation for RegionTracker { + fn merge_scc(mut self, other: Self) -> Self { + self.representative = self.representative.merge_scc(other.representative); + self.merge_min_max_seen(&other); + self + } + + fn merge_reached(mut self, other: Self) -> Self { + // No update to in-component values, only add seen values. + self.merge_min_max_seen(&other); + self + } +} + +/// Determines if the region variable definitions contain +/// placeholders, and compute them for later use. +fn region_definitions<'tcx>( + universal_regions: &UniversalRegions<'tcx>, + infcx: &BorrowckInferCtxt<'tcx>, +) -> (Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>, bool) { + let var_infos = infcx.get_region_var_infos(); + // Create a RegionDefinition for each inference variable. This happens here because + // it allows us to sneak in a cheap check for placeholders. Otherwise, its proper home + // is in `RegionInferenceContext::new()`, probably. + let mut definitions = IndexVec::with_capacity(var_infos.len()); + let mut has_placeholders = false; + + for info in var_infos.iter() { + let origin = match info.origin { + RegionVariableOrigin::Nll(origin) => origin, + _ => NllRegionVariableOrigin::Existential { from_forall: false }, + }; + + let definition = RegionDefinition { origin, universe: info.universe, external_name: None }; + + has_placeholders |= matches!(origin, NllRegionVariableOrigin::Placeholder(_)); + definitions.push(definition); + } + + // Add external names from universal regions in fun function definitions. + // FIXME: this two-step method is annoying, but I don't know how to avoid it. + for (external_name, variable) in universal_regions.named_universal_regions_iter() { + debug!("region {:?} has external name {:?}", variable, external_name); + definitions[variable].external_name = Some(external_name); + } + (Frozen::freeze(definitions), has_placeholders) +} + +/// This method handles placeholders by rewriting the constraint +/// graph. For each strongly connected component in the constraint +/// graph such that there is a series of constraints +/// A: B: C: ... : X where +/// A contains a placeholder whose universe cannot be named by X, +/// add a constraint that A: 'static. This is a safe upper bound +/// in the face of borrow checker/trait solver limitations that will +/// eventually go away. +/// +/// For a more precise definition, see the documentation for +/// [`RegionTracker`] and its methods! +/// +/// This edge case used to be handled during constraint propagation. +/// It was rewritten as part of the Polonius project with the goal of moving +/// higher-kindedness concerns out of the path of the borrow checker, +/// for two reasons: +/// +/// 1. Implementing Polonius is difficult enough without also +/// handling them. +/// 2. The long-term goal is to handle higher-kinded concerns +/// in the trait solver, where they belong. This avoids +/// logic duplication and allows future trait solvers +/// to compute better bounds than for example our +/// "must outlive 'static" here. +/// +/// This code is a stop-gap measure in preparation for the future trait solver. +/// +/// Every constraint added by this method is an internal `IllegalUniverse` constraint. +pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( + constraints: MirTypeckRegionConstraints<'tcx>, + universal_region_relations: &Frozen<UniversalRegionRelations<'tcx>>, + infcx: &BorrowckInferCtxt<'tcx>, +) -> LoweredConstraints<'tcx> { + let universal_regions = &universal_region_relations.universal_regions; + let (definitions, has_placeholders) = region_definitions(universal_regions, infcx); + + let MirTypeckRegionConstraints { + placeholder_indices, + placeholder_index_to_region: _, + liveness_constraints, + mut outlives_constraints, + mut member_constraints, + universe_causes, + type_tests, + } = constraints; + + if let Some(guar) = universal_regions.tainted_by_errors() { + debug!("Universal regions tainted by errors; removing constraints!"); + // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all + // outlives bounds that we may end up checking. + outlives_constraints = Default::default(); + member_constraints = Default::default(); + + // Also taint the entire scope. + infcx.set_tainted_by_errors(guar); + } + + let fr_static = universal_regions.fr_static; + let compute_sccs = + |constraints: &OutlivesConstraintSet<'tcx>, + annotations: &mut SccAnnotations<'_, 'tcx, RegionTracker>| { + ConstraintSccs::new_with_annotation( + &constraints.graph(definitions.len()).region_graph(constraints, fr_static), + annotations, + ) + }; + + let mut scc_annotations = SccAnnotations::init(&definitions); + let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations); + + // This code structure is a bit convoluted because it allows for a planned + // future change where the early return here has a different type of annotation + // that does much less work. + if !has_placeholders { + debug!("No placeholder regions found; skipping rewriting logic!"); + + return LoweredConstraints { + type_tests, + member_constraints, + constraint_sccs, + scc_annotations: scc_annotations.scc_to_annotation, + definitions, + outlives_constraints: Frozen::freeze(outlives_constraints), + liveness_constraints, + universe_causes, + placeholder_indices, + }; + } + debug!("Placeholders present; activating placeholder handling logic!"); + + let added_constraints = rewrite_placeholder_outlives( + &constraint_sccs, + &scc_annotations, + fr_static, + &mut outlives_constraints, + ); + + let (constraint_sccs, scc_annotations) = if added_constraints { + let mut annotations = SccAnnotations::init(&definitions); + + // We changed the constraint set and so must recompute SCCs. + // Optimisation opportunity: if we can add them incrementally (and that's + // possible because edges to 'static always only merge SCCs into 'static), + // we would potentially save a lot of work here. + (compute_sccs(&outlives_constraints, &mut annotations), annotations.scc_to_annotation) + } else { + // If we didn't add any back-edges; no more work needs doing + debug!("No constraints rewritten!"); + (constraint_sccs, scc_annotations.scc_to_annotation) + }; + + LoweredConstraints { + constraint_sccs, + definitions, + scc_annotations, + member_constraints, + outlives_constraints: Frozen::freeze(outlives_constraints), + type_tests, + liveness_constraints, + universe_causes, + placeholder_indices, + } +} + +fn rewrite_placeholder_outlives<'tcx>( + sccs: &Sccs<RegionVid, ConstraintSccIndex>, + annotations: &SccAnnotations<'_, '_, RegionTracker>, + fr_static: RegionVid, + outlives_constraints: &mut OutlivesConstraintSet<'tcx>, +) -> bool { + // Changed to `true` if we added any constraints and need to + // recompute SCCs. + let mut added_constraints = false; + + let annotations = &annotations.scc_to_annotation; + + for scc in sccs.all_sccs() { + // No point in adding 'static: 'static! + // This micro-optimisation makes somewhat sense + // because static outlives *everything*. + if scc == sccs.scc(fr_static) { + continue; + } + + let annotation = annotations[scc]; + + // If this SCC participates in a universe violation, + // e.g. if it reaches a region with a universe smaller than + // the largest region reached, add a requirement that it must + // outlive `'static`. + if annotation.has_incompatible_universes() { + // Optimisation opportunity: this will add more constraints than + // needed for correctness, since an SCC upstream of another with + // a universe violation will "infect" its downstream SCCs to also + // outlive static. + let scc_representative_outlives_static = OutlivesConstraint { + sup: annotation.representative.rvid(), + sub: fr_static, + category: ConstraintCategory::IllegalUniverse, + locations: Locations::All(rustc_span::DUMMY_SP), + span: rustc_span::DUMMY_SP, + variance_info: VarianceDiagInfo::None, + from_closure: false, + }; + outlives_constraints.push(scc_representative_outlives_static); + added_constraints = true; + debug!("Added {:?}: 'static!", annotation.representative.rvid()); + } + } + added_constraints +} diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 2bd0ffd143b..e6eae7d4f5a 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -72,6 +72,7 @@ mod constraints; mod dataflow; mod def_use; mod diagnostics; +mod handle_placeholders; mod member_constraints; mod nll; mod path_utils; diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 8664e99cae3..1b011d73385 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -20,6 +20,7 @@ use tracing::{debug, instrument}; use crate::borrow_set::BorrowSet; use crate::consumers::ConsumerOptions; use crate::diagnostics::RegionErrors; +use crate::handle_placeholders::compute_sccs_applying_placeholder_outlives_constraints; use crate::polonius::PoloniusDiagnosticsContext; use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, @@ -113,6 +114,12 @@ pub(crate) fn compute_regions<'tcx>( Rc::clone(&location_map), ); + let lowered_constraints = compute_sccs_applying_placeholder_outlives_constraints( + constraints, + &universal_region_relations, + infcx, + ); + // If requested, emit legacy polonius facts. polonius::legacy::emit_facts( &mut polonius_facts, @@ -122,11 +129,15 @@ pub(crate) fn compute_regions<'tcx>( borrow_set, move_data, &universal_region_relations, - &constraints, + &lowered_constraints, ); - let mut regioncx = - RegionInferenceContext::new(infcx, constraints, universal_region_relations, location_map); + let mut regioncx = RegionInferenceContext::new( + infcx, + lowered_constraints, + universal_region_relations, + location_map, + ); // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints // and use them to compute loan liveness. diff --git a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs index 95820c07a02..05fd6e39476 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/mod.rs @@ -13,7 +13,7 @@ use tracing::debug; use crate::borrow_set::BorrowSet; use crate::constraints::OutlivesConstraint; -use crate::type_check::MirTypeckRegionConstraints; +use crate::handle_placeholders::LoweredConstraints; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -43,7 +43,7 @@ pub(crate) fn emit_facts<'tcx>( borrow_set: &BorrowSet<'tcx>, move_data: &MoveData<'tcx>, universal_region_relations: &UniversalRegionRelations<'tcx>, - constraints: &MirTypeckRegionConstraints<'tcx>, + constraints: &LoweredConstraints<'tcx>, ) { let Some(facts) = facts else { // We don't do anything if there are no facts to fill. @@ -203,7 +203,7 @@ pub(crate) fn emit_drop_facts<'tcx>( fn emit_outlives_facts<'tcx>( facts: &mut PoloniusFacts, location_table: &PoloniusLocationTable, - constraints: &MirTypeckRegionConstraints<'tcx>, + constraints: &LoweredConstraints<'tcx>, ) { facts.subset_base.extend(constraints.outlives_constraints.outlives().iter().flat_map( |constraint: &OutlivesConstraint<'_>| { diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index ef3d6309c19..a9ab30fd8fa 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -46,7 +46,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { "| {r:rw$?} | {ui:4?} | {v}", r = region, rw = REGION_WIDTH, - ui = self.region_universe(region), + ui = self.max_nameable_universe(self.constraint_sccs.scc(region)), v = self.region_value_str(region), )?; } diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index aa584713593..5f1b655c6b6 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -11,7 +11,7 @@ use rustc_hir::def_id::CRATE_DEF_ID; use rustc_index::IndexVec; use rustc_infer::infer::outlives::test_type_match; use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound, VerifyIfEq}; -use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin}; +use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin}; use rustc_middle::bug; use rustc_middle::mir::{ AnnotationSource, BasicBlock, Body, ConstraintCategory, Local, Location, ReturnConstraint, @@ -28,13 +28,14 @@ use crate::constraints::graph::{self, NormalConstraintGraph, RegionGraph}; use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet}; use crate::dataflow::BorrowIndex; use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo}; +use crate::handle_placeholders::{LoweredConstraints, RegionTracker}; use crate::member_constraints::{MemberConstraintSet, NllMemberConstraintIndex}; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; use crate::region_infer::reverse_sccs::ReverseSccGraph; use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues, ToElementIndex}; +use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; -use crate::type_check::{Locations, MirTypeckRegionConstraints}; use crate::universal_regions::UniversalRegions; use crate::{ BorrowckInferCtxt, ClosureOutlivesRequirement, ClosureOutlivesSubject, @@ -48,125 +49,48 @@ mod reverse_sccs; pub(crate) mod values; -pub(crate) type ConstraintSccs = Sccs<RegionVid, ConstraintSccIndex>; -pub(crate) type AnnotatedSccs = (ConstraintSccs, IndexVec<ConstraintSccIndex, RegionTracker>); - -/// An annotation for region graph SCCs that tracks -/// the values of its elements. This annotates a single SCC. -#[derive(Copy, Debug, Clone)] -pub(crate) struct RegionTracker { - /// The largest universe of a placeholder reached from this SCC. - /// This includes placeholders within this SCC. - max_placeholder_universe_reached: UniverseIndex, - - /// The smallest universe index reachable form the nodes of this SCC. - min_reachable_universe: UniverseIndex, - - /// The representative Region Variable Id for this SCC. We prefer - /// placeholders over existentially quantified variables, otherwise - /// it's the one with the smallest Region Variable ID. - pub(crate) representative: RegionVid, - - /// Is the current representative a placeholder? - representative_is_placeholder: bool, - - /// Is the current representative existentially quantified? - representative_is_existential: bool, +/// The representative region variable for an SCC, tagged by its origin. +/// We prefer placeholders over existentially quantified variables, otherwise +/// it's the one with the smallest Region Variable ID. In other words, +/// the order of this enumeration really matters! +#[derive(Copy, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub(crate) enum Representative { + FreeRegion(RegionVid), + Placeholder(RegionVid), + Existential(RegionVid), } -impl scc::Annotation for RegionTracker { - fn merge_scc(mut self, mut other: Self) -> Self { - // Prefer any placeholder over any existential - if other.representative_is_placeholder && self.representative_is_existential { - other.merge_min_max_seen(&self); - return other; - } - - if self.representative_is_placeholder && other.representative_is_existential - || (self.representative <= other.representative) - { - self.merge_min_max_seen(&other); - return self; +impl Representative { + pub(crate) fn rvid(self) -> RegionVid { + match self { + Representative::FreeRegion(region_vid) + | Representative::Placeholder(region_vid) + | Representative::Existential(region_vid) => region_vid, } - other.merge_min_max_seen(&self); - other - } - - fn merge_reached(mut self, other: Self) -> Self { - // No update to in-component values, only add seen values. - self.merge_min_max_seen(&other); - self } -} - -/// A Visitor for SCC annotation construction. -pub(crate) struct SccAnnotations<'d, 'tcx, A: scc::Annotation> { - pub(crate) scc_to_annotation: IndexVec<ConstraintSccIndex, A>, - definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>, -} -impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> { - pub(crate) fn new(definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>) -> Self { - Self { scc_to_annotation: IndexVec::new(), definitions } + pub(crate) fn new(r: RegionVid, definition: &RegionDefinition<'_>) -> Self { + match definition.origin { + NllRegionVariableOrigin::FreeRegion => Representative::FreeRegion(r), + NllRegionVariableOrigin::Placeholder(_) => Representative::Placeholder(r), + NllRegionVariableOrigin::Existential { .. } => Representative::Existential(r), + } } } -impl scc::Annotations<RegionVid> for SccAnnotations<'_, '_, RegionTracker> { - fn new(&self, element: RegionVid) -> RegionTracker { - RegionTracker::new(element, &self.definitions[element]) +impl scc::Annotation for Representative { + fn merge_scc(self, other: Self) -> Self { + // Just pick the smallest one. Note that we order by tag first! + std::cmp::min(self, other) } - fn annotate_scc(&mut self, scc: ConstraintSccIndex, annotation: RegionTracker) { - let idx = self.scc_to_annotation.push(annotation); - assert!(idx == scc); + // For reachability, we do nothing since the representative doesn't change. + fn merge_reached(self, _other: Self) -> Self { + self } - - type Ann = RegionTracker; - type SccIdx = ConstraintSccIndex; } -impl RegionTracker { - pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self { - let (representative_is_placeholder, representative_is_existential) = match definition.origin - { - NllRegionVariableOrigin::FreeRegion => (false, false), - NllRegionVariableOrigin::Placeholder(_) => (true, false), - NllRegionVariableOrigin::Existential { .. } => (false, true), - }; - - let placeholder_universe = - if representative_is_placeholder { definition.universe } else { UniverseIndex::ROOT }; - - Self { - max_placeholder_universe_reached: placeholder_universe, - min_reachable_universe: definition.universe, - representative: rvid, - representative_is_placeholder, - representative_is_existential, - } - } - - /// The smallest-indexed universe reachable from and/or in this SCC. - fn min_universe(self) -> UniverseIndex { - self.min_reachable_universe - } - - fn merge_min_max_seen(&mut self, other: &Self) { - self.max_placeholder_universe_reached = std::cmp::max( - self.max_placeholder_universe_reached, - other.max_placeholder_universe_reached, - ); - - self.min_reachable_universe = - std::cmp::min(self.min_reachable_universe, other.min_reachable_universe); - } - - /// Returns `true` if during the annotated SCC reaches a placeholder - /// with a universe larger than the smallest reachable one, `false` otherwise. - pub(crate) fn has_incompatible_universes(&self) -> bool { - self.min_universe().cannot_name(self.max_placeholder_universe_reached) - } -} +pub(crate) type ConstraintSccs = Sccs<RegionVid, ConstraintSccIndex>; pub struct RegionInferenceContext<'tcx> { /// Contains the definition for every region variable. Region @@ -414,26 +338,6 @@ fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { debug!("SCC edges {:#?}", scc_node_to_edges); } -fn create_definitions<'tcx>( - infcx: &BorrowckInferCtxt<'tcx>, - universal_regions: &UniversalRegions<'tcx>, -) -> Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>> { - // Create a RegionDefinition for each inference variable. - let mut definitions: IndexVec<_, _> = infcx - .get_region_var_infos() - .iter() - .map(|info| RegionDefinition::new(info.universe, info.origin)) - .collect(); - - // Add the external name for all universal regions. - for (external_name, variable) in universal_regions.named_universal_regions_iter() { - debug!("region {variable:?} has external name {external_name:?}"); - definitions[variable].external_name = Some(external_name); - } - - Frozen::freeze(definitions) -} - impl<'tcx> RegionInferenceContext<'tcx> { /// Creates a new region inference context with a total of /// `num_region_variables` valid inference variables; the first N @@ -444,42 +348,30 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// of constraints produced by the MIR type check. pub(crate) fn new( infcx: &BorrowckInferCtxt<'tcx>, - constraints: MirTypeckRegionConstraints<'tcx>, + lowered_constraints: LoweredConstraints<'tcx>, universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>, location_map: Rc<DenseLocationMap>, ) -> Self { let universal_regions = &universal_region_relations.universal_regions; - let MirTypeckRegionConstraints { - placeholder_indices, - placeholder_index_to_region: _, + + let LoweredConstraints { + constraint_sccs, + definitions, + outlives_constraints, + scc_annotations, + type_tests, liveness_constraints, - mut outlives_constraints, - mut member_constraints, universe_causes, - type_tests, - } = constraints; + placeholder_indices, + member_constraints, + } = lowered_constraints; debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); debug!("outlives constraints: {:#?}", outlives_constraints); debug!("placeholder_indices: {:#?}", placeholder_indices); debug!("type tests: {:#?}", type_tests); - if let Some(guar) = universal_region_relations.universal_regions.tainted_by_errors() { - // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all - // outlives bounds that we may end up checking. - outlives_constraints = Default::default(); - member_constraints = Default::default(); - - // Also taint the entire scope. - infcx.set_tainted_by_errors(guar); - } - - let definitions = create_definitions(infcx, &universal_regions); - - let (constraint_sccs, scc_annotations) = - outlives_constraints.add_outlives_static(&universal_regions, &definitions); - let constraints = Frozen::freeze(outlives_constraints); - let constraint_graph = Frozen::freeze(constraints.graph(definitions.len())); + let constraint_graph = Frozen::freeze(outlives_constraints.graph(definitions.len())); if cfg!(debug_assertions) { sccs_info(infcx, &constraint_sccs); @@ -499,7 +391,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let mut result = Self { definitions, liveness_constraints, - constraints, + constraints: outlives_constraints, constraint_graph, constraint_sccs, scc_annotations, @@ -658,11 +550,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.scc_values.placeholders_contained_in(scc) } - /// Returns access to the value of `r` for debugging purposes. - pub(crate) fn region_universe(&self, r: RegionVid) -> ty::UniverseIndex { - self.scc_universe(self.constraint_sccs.scc(r)) - } - /// Once region solving has completed, this function will return the member constraints that /// were applied to the value of a given SCC `scc`. See `AppliedMemberConstraint`. pub(crate) fn applied_member_constraints( @@ -826,7 +713,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // If the member region lives in a higher universe, we currently choose // the most conservative option by leaving it unchanged. - if !self.scc_universe(scc).is_root() { + if !self.max_nameable_universe(scc).is_root() { return; } @@ -902,20 +789,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// in `scc_a`. Used during constraint propagation, and only once /// the value of `scc_b` has been computed. fn universe_compatible(&self, scc_b: ConstraintSccIndex, scc_a: ConstraintSccIndex) -> bool { - let a_annotation = self.scc_annotations[scc_a]; - let b_annotation = self.scc_annotations[scc_b]; - let a_universe = a_annotation.min_universe(); - - // If scc_b's declared universe is a subset of - // scc_a's declared universe (typically, both are ROOT), then - // it cannot contain any problematic universe elements. - if a_universe.can_name(b_annotation.min_universe()) { - return true; - } - - // Otherwise, there can be no placeholder in `b` with a too high - // universe index to name from `a`. - a_universe.can_name(b_annotation.max_placeholder_universe_reached) + self.scc_annotations[scc_a].universe_compatible_with(self.scc_annotations[scc_b]) } /// Once regions have been propagated, this method is used to see @@ -1019,7 +893,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { "lower_bound = {:?} r_scc={:?} universe={:?}", lower_bound, r_scc, - self.scc_universe(r_scc) + self.max_nameable_universe(r_scc) ); // If the type test requires that `T: 'a` where `'a` is a // placeholder from another universe, that effectively requires @@ -1497,10 +1371,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } - /// The minimum universe of any variable reachable from this - /// SCC, inside or outside of it. - fn scc_universe(&self, scc: ConstraintSccIndex) -> UniverseIndex { - self.scc_annotations[scc].min_universe() + /// The largest universe of any region nameable from this SCC. + fn max_nameable_universe(&self, scc: ConstraintSccIndex) -> UniverseIndex { + self.scc_annotations[scc].max_nameable_universe() } /// Checks the final value for the free region `fr` to see if it @@ -1522,7 +1395,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // Because this free region must be in the ROOT universe, we // know it cannot contain any bound universes. - assert!(self.scc_universe(longer_fr_scc).is_root()); + assert!(self.max_nameable_universe(longer_fr_scc).is_root()); // Only check all of the relations for the main representative of each // SCC, otherwise just check that we outlive said representative. This @@ -1913,7 +1786,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { #[instrument(skip(self), level = "trace", ret)] pub(crate) fn find_sub_region_live_at(&self, fr1: RegionVid, location: Location) -> RegionVid { trace!(scc = ?self.constraint_sccs.scc(fr1)); - trace!(universe = ?self.region_universe(fr1)); + trace!(universe = ?self.max_nameable_universe(self.constraint_sccs.scc(fr1))); self.find_constraint_paths_between_regions(fr1, |r| { // First look for some `r` such that `fr1: r` and `r` is live at `location` trace!(?r, liveness_constraints=?self.liveness_constraints.pretty_print_live_points(r)); @@ -2244,7 +2117,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// they *must* be equal (though not having the same repr does not /// mean they are unequal). fn scc_representative(&self, scc: ConstraintSccIndex) -> RegionVid { - self.scc_annotations[scc].representative + self.scc_annotations[scc].representative.rvid() } pub(crate) fn liveness_constraints(&self) -> &LivenessValues { @@ -2266,21 +2139,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } -impl<'tcx> RegionDefinition<'tcx> { - fn new(universe: ty::UniverseIndex, rv_origin: RegionVariableOrigin) -> Self { - // Create a new region definition. Note that, for free - // regions, the `external_name` field gets updated later in - // `init_free_and_bound_regions`. - - let origin = match rv_origin { - RegionVariableOrigin::Nll(origin) => origin, - _ => NllRegionVariableOrigin::Existential { from_forall: false }, - }; - - Self { origin, universe, external_name: None } - } -} - #[derive(Clone, Debug)] pub(crate) struct BlameConstraint<'tcx> { pub category: ConstraintCategory<'tcx>, diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs index f0d72085c40..6270e6d9a60 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs @@ -191,7 +191,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let scc = self.constraint_sccs.scc(vid); // Special handling of higher-ranked regions. - if !self.scc_universe(scc).is_root() { + if !self.max_nameable_universe(scc).is_root() { match self.scc_values.placeholders_contained_in(scc).enumerate().last() { // If the region contains a single placeholder then they're equal. Some((0, placeholder)) => { diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index 4234352c93a..73def2711dc 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -541,12 +541,12 @@ impl<'ll> CodegenCx<'ll, '_> { // in the handling of `.init_array` (the static constructor list) in versions of // the gold linker (prior to the one released with binutils 2.36). // - // That said, we only ever emit these when compiling for ELF targets, unless - // `#[used(compiler)]` is explicitly requested. This is to avoid similar breakage - // on other targets, in particular MachO targets have *their* static constructor - // lists broken if `llvm.compiler.used` is emitted rather than `llvm.used`. However, - // that check happens when assigning the `CodegenFnAttrFlags` in - // `rustc_hir_analysis`, so we don't need to take care of it here. + // That said, we only ever emit these when `#[used(compiler)]` is explicitly + // requested. This is to avoid similar breakage on other targets, in particular + // MachO targets have *their* static constructor lists broken if `llvm.compiler.used` + // is emitted rather than `llvm.used`. However, that check happens when assigning + // the `CodegenFnAttrFlags` in the `codegen_fn_attrs` query, so we don't need to + // take care of it here. self.add_compiler_used_global(g); } if attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) { diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 1a39a0c3fda..168077260a6 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -1961,7 +1961,7 @@ fn add_post_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor /// This method creates a synthetic object file, which contains undefined references to all symbols /// that are necessary for the linking. They are only present in symbol table but not actually /// used in any sections, so the linker will therefore pick relevant rlibs for linking, but -/// unused `#[no_mangle]` or `#[used]` can still be discard by GC sections. +/// unused `#[no_mangle]` or `#[used(compiler)]` can still be discard by GC sections. /// /// There's a few internal crates in the standard library (aka libcore and /// libstd) which actually have a circular dependence upon one another. This @@ -1995,7 +1995,8 @@ fn add_linked_symbol_object( if file.format() == object::BinaryFormat::MachO { // Divide up the sections into sub-sections via symbols for dead code stripping. - // Without this flag, unused `#[no_mangle]` or `#[used]` cannot be discard on MachO targets. + // Without this flag, unused `#[no_mangle]` or `#[used(compiler)]` cannot be + // discard on MachO targets. file.set_subsections_via_symbols(); } diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 4bcafa3be36..0b31fa8fa88 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -195,35 +195,10 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { tcx.dcx().emit_err(errors::ExpectedUsedSymbol { span: attr.span() }); } None => { - // Unfortunately, unconditionally using `llvm.used` causes - // issues in handling `.init_array` with the gold linker, - // but using `llvm.compiler.used` caused a nontrivial amount - // of unintentional ecosystem breakage -- particularly on - // Mach-O targets. - // - // As a result, we emit `llvm.compiler.used` only on ELF - // targets. This is somewhat ad-hoc, but actually follows - // our pre-LLVM 13 behavior (prior to the ecosystem - // breakage), and seems to match `clang`'s behavior as well - // (both before and after LLVM 13), possibly because they - // have similar compatibility concerns to us. See - // https://github.com/rust-lang/rust/issues/47384#issuecomment-1019080146 - // and following comments for some discussion of this, as - // well as the comments in `rustc_codegen_llvm` where these - // flags are handled. - // - // Anyway, to be clear: this is still up in the air - // somewhat, and is subject to change in the future (which - // is a good thing, because this would ideally be a bit - // more firmed up). - let is_like_elf = !(tcx.sess.target.is_like_darwin - || tcx.sess.target.is_like_windows - || tcx.sess.target.is_like_wasm); - codegen_fn_attrs.flags |= if is_like_elf { - CodegenFnAttrFlags::USED_COMPILER - } else { - CodegenFnAttrFlags::USED_LINKER - }; + // Unconditionally using `llvm.used` causes issues in handling + // `.init_array` with the gold linker. Luckily gold has been + // deprecated with GCC 15 and rustc now warns about using gold. + codegen_fn_attrs.flags |= CodegenFnAttrFlags::USED_LINKER } } } diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 01625b91353..2556e57a58f 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -454,11 +454,12 @@ fn report_eval_error<'tcx>( // FIXME(oli-obk): figure out how to use structured diagnostics again. diag.code(E0080); diag.span_label(span, crate::fluent_generated::const_eval_error); - diag.arg("instance", instance); - diag.arg("error_kind", kind); for frame in frames { diag.subdiagnostic(frame); } + // Add after the frame rendering above, as it adds its own `instance` args. + diag.arg("instance", instance); + diag.arg("error_kind", kind); }, ) } diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index e926040e9ba..9c867cc615e 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -15,15 +15,20 @@ fn alloc_caller_location<'tcx>( line: u32, col: u32, ) -> MPlaceTy<'tcx> { + // Ensure that the filename itself does not contain nul bytes. + // This isn't possible via POSIX or Windows, but we should ensure no one + // ever does such a thing. + assert!(!filename.as_str().as_bytes().contains(&0)); + let loc_details = ecx.tcx.sess.opts.unstable_opts.location_detail; - // This can fail if rustc runs out of memory right here. Trying to emit an error would be - // pointless, since that would require allocating more memory than these short strings. - let file = if loc_details.file { - ecx.allocate_str_dedup(filename.as_str()).unwrap() - } else { - ecx.allocate_str_dedup("<redacted>").unwrap() + let file_wide_ptr = { + let filename = if loc_details.file { filename.as_str() } else { "<redacted>" }; + let filename_with_nul = filename.to_owned() + "\0"; + // This can fail if rustc runs out of memory right here. Trying to emit an error would be + // pointless, since that would require allocating more memory than these short strings. + let file_ptr = ecx.allocate_bytes_dedup(filename_with_nul.as_bytes()).unwrap(); + Immediate::new_slice(file_ptr.into(), filename_with_nul.len().try_into().unwrap(), ecx) }; - let file = file.map_provenance(CtfeProvenance::as_immutable); let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) }; let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) }; @@ -36,7 +41,7 @@ fn alloc_caller_location<'tcx>( let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap(); // Initialize fields. - ecx.write_immediate(file.to_ref(ecx), &ecx.project_field(&location, 0).unwrap()) + ecx.write_immediate(file_wide_ptr, &ecx.project_field(&location, 0).unwrap()) .expect("writing to memory we just allocated cannot fail"); ecx.write_scalar(line, &ecx.project_field(&location, 1).unwrap()) .expect("writing to memory we just allocated cannot fail"); diff --git a/compiler/rustc_hir/src/def.rs b/compiler/rustc_hir/src/def.rs index 98ec1ccd6ba..459fe5935e0 100644 --- a/compiler/rustc_hir/src/def.rs +++ b/compiler/rustc_hir/src/def.rs @@ -852,12 +852,7 @@ pub enum LifetimeRes { /// late resolution. Those lifetimes will be inferred by typechecking. Infer, /// `'static` lifetime. - Static { - /// We do not want to emit `elided_named_lifetimes` - /// when we are inside of a const item or a static, - /// because it would get too annoying. - suppress_elision_warning: bool, - }, + Static, /// Resolution failure. Error, /// HACK: This is used to recover the NodeId of an elided lifetime. diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 433d5f98829..6f288bb39b9 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -72,13 +72,13 @@ pub enum LifetimeSource { #[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable_Generic)] pub enum LifetimeSyntax { /// E.g. `&Type`, `ContainsLifetime` - Hidden, + Implicit, /// E.g. `&'_ Type`, `ContainsLifetime<'_>`, `impl Trait + '_`, `impl Trait + use<'_>` - Anonymous, + ExplicitAnonymous, /// E.g. `&'a Type`, `ContainsLifetime<'a>`, `impl Trait + 'a`, `impl Trait + use<'a>` - Named, + ExplicitBound, } impl From<Ident> for LifetimeSyntax { @@ -88,10 +88,10 @@ impl From<Ident> for LifetimeSyntax { if name == sym::empty { unreachable!("A lifetime name should never be empty"); } else if name == kw::UnderscoreLifetime { - LifetimeSyntax::Anonymous + LifetimeSyntax::ExplicitAnonymous } else { debug_assert!(name.as_str().starts_with('\'')); - LifetimeSyntax::Named + LifetimeSyntax::ExplicitBound } } } @@ -102,48 +102,48 @@ impl From<Ident> for LifetimeSyntax { /// /// ``` /// #[repr(C)] -/// struct S<'a>(&'a u32); // res=Param, name='a, source=Reference, syntax=Named +/// struct S<'a>(&'a u32); // res=Param, name='a, source=Reference, syntax=ExplicitBound /// unsafe extern "C" { -/// fn f1(s: S); // res=Param, name='_, source=Path, syntax=Hidden -/// fn f2(s: S<'_>); // res=Param, name='_, source=Path, syntax=Anonymous -/// fn f3<'a>(s: S<'a>); // res=Param, name='a, source=Path, syntax=Named +/// fn f1(s: S); // res=Param, name='_, source=Path, syntax=Implicit +/// fn f2(s: S<'_>); // res=Param, name='_, source=Path, syntax=ExplicitAnonymous +/// fn f3<'a>(s: S<'a>); // res=Param, name='a, source=Path, syntax=ExplicitBound /// } /// -/// struct St<'a> { x: &'a u32 } // res=Param, name='a, source=Reference, syntax=Named +/// struct St<'a> { x: &'a u32 } // res=Param, name='a, source=Reference, syntax=ExplicitBound /// fn f() { -/// _ = St { x: &0 }; // res=Infer, name='_, source=Path, syntax=Hidden -/// _ = St::<'_> { x: &0 }; // res=Infer, name='_, source=Path, syntax=Anonymous +/// _ = St { x: &0 }; // res=Infer, name='_, source=Path, syntax=Implicit +/// _ = St::<'_> { x: &0 }; // res=Infer, name='_, source=Path, syntax=ExplicitAnonymous /// } /// -/// struct Name<'a>(&'a str); // res=Param, name='a, source=Reference, syntax=Named -/// const A: Name = Name("a"); // res=Static, name='_, source=Path, syntax=Hidden -/// const B: &str = ""; // res=Static, name='_, source=Reference, syntax=Hidden -/// static C: &'_ str = ""; // res=Static, name='_, source=Reference, syntax=Anonymous -/// static D: &'static str = ""; // res=Static, name='static, source=Reference, syntax=Named +/// struct Name<'a>(&'a str); // res=Param, name='a, source=Reference, syntax=ExplicitBound +/// const A: Name = Name("a"); // res=Static, name='_, source=Path, syntax=Implicit +/// const B: &str = ""; // res=Static, name='_, source=Reference, syntax=Implicit +/// static C: &'_ str = ""; // res=Static, name='_, source=Reference, syntax=ExplicitAnonymous +/// static D: &'static str = ""; // res=Static, name='static, source=Reference, syntax=ExplicitBound /// /// trait Tr {} -/// fn tr(_: Box<dyn Tr>) {} // res=ImplicitObjectLifetimeDefault, name='_, source=Other, syntax=Hidden +/// fn tr(_: Box<dyn Tr>) {} // res=ImplicitObjectLifetimeDefault, name='_, source=Other, syntax=Implicit /// /// fn capture_outlives<'a>() -> -/// impl FnOnce() + 'a // res=Param, ident='a, source=OutlivesBound, syntax=Named +/// impl FnOnce() + 'a // res=Param, ident='a, source=OutlivesBound, syntax=ExplicitBound /// { /// || {} /// } /// /// fn capture_precise<'a>() -> -/// impl FnOnce() + use<'a> // res=Param, ident='a, source=PreciseCapturing, syntax=Named +/// impl FnOnce() + use<'a> // res=Param, ident='a, source=PreciseCapturing, syntax=ExplicitBound /// { /// || {} /// } /// /// // (commented out because these cases trigger errors) -/// // struct S1<'a>(&'a str); // res=Param, name='a, source=Reference, syntax=Named -/// // struct S2(S1); // res=Error, name='_, source=Path, syntax=Hidden -/// // struct S3(S1<'_>); // res=Error, name='_, source=Path, syntax=Anonymous -/// // struct S4(S1<'a>); // res=Error, name='a, source=Path, syntax=Named +/// // struct S1<'a>(&'a str); // res=Param, name='a, source=Reference, syntax=ExplicitBound +/// // struct S2(S1); // res=Error, name='_, source=Path, syntax=Implicit +/// // struct S3(S1<'_>); // res=Error, name='_, source=Path, syntax=ExplicitAnonymous +/// // struct S4(S1<'a>); // res=Error, name='a, source=Path, syntax=ExplicitBound /// ``` /// -/// Some combinations that cannot occur are `LifetimeSyntax::Hidden` with +/// Some combinations that cannot occur are `LifetimeSyntax::Implicit` with /// `LifetimeSource::OutlivesBound` or `LifetimeSource::PreciseCapturing` /// — there's no way to "elide" these lifetimes. #[derive(Debug, Copy, Clone, HashStable_Generic)] @@ -206,7 +206,7 @@ impl ParamName { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable_Generic)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, HashStable_Generic)] pub enum LifetimeKind { /// User-given names or fresh (synthetic) names. Param(LocalDefId), @@ -287,12 +287,8 @@ impl Lifetime { self.ident.name == kw::UnderscoreLifetime } - pub fn is_syntactically_hidden(&self) -> bool { - matches!(self.syntax, LifetimeSyntax::Hidden) - } - - pub fn is_syntactically_anonymous(&self) -> bool { - matches!(self.syntax, LifetimeSyntax::Anonymous) + pub fn is_implicit(&self) -> bool { + matches!(self.syntax, LifetimeSyntax::Implicit) } pub fn is_static(&self) -> bool { @@ -307,28 +303,28 @@ impl Lifetime { match (self.syntax, self.source) { // The user wrote `'a` or `'_`. - (Named | Anonymous, _) => (self.ident.span, format!("{new_lifetime}")), + (ExplicitBound | ExplicitAnonymous, _) => (self.ident.span, format!("{new_lifetime}")), // The user wrote `Path<T>`, and omitted the `'_,`. - (Hidden, Path { angle_brackets: AngleBrackets::Full }) => { + (Implicit, Path { angle_brackets: AngleBrackets::Full }) => { (self.ident.span, format!("{new_lifetime}, ")) } // The user wrote `Path<>`, and omitted the `'_`.. - (Hidden, Path { angle_brackets: AngleBrackets::Empty }) => { + (Implicit, Path { angle_brackets: AngleBrackets::Empty }) => { (self.ident.span, format!("{new_lifetime}")) } // The user wrote `Path` and omitted the `<'_>`. - (Hidden, Path { angle_brackets: AngleBrackets::Missing }) => { + (Implicit, Path { angle_brackets: AngleBrackets::Missing }) => { (self.ident.span.shrink_to_hi(), format!("<{new_lifetime}>")) } // The user wrote `&type` or `&mut type`. - (Hidden, Reference) => (self.ident.span, format!("{new_lifetime} ")), + (Implicit, Reference) => (self.ident.span, format!("{new_lifetime} ")), - (Hidden, source) => { - unreachable!("can't suggest for a hidden lifetime of {source:?}") + (Implicit, source) => { + unreachable!("can't suggest for a implicit lifetime of {source:?}") } } } diff --git a/compiler/rustc_hir/src/hir/tests.rs b/compiler/rustc_hir/src/hir/tests.rs index 8684adee29c..1fd793bc161 100644 --- a/compiler/rustc_hir/src/hir/tests.rs +++ b/compiler/rustc_hir/src/hir/tests.rs @@ -55,7 +55,7 @@ fn trait_object_roundtrips_impl(syntax: TraitObjectSyntax) { ident: Ident::new(sym::name, DUMMY_SP), kind: LifetimeKind::Static, source: LifetimeSource::Other, - syntax: LifetimeSyntax::Hidden, + syntax: LifetimeSyntax::Implicit, }; let unambig = TyKind::TraitObject::<'_, ()>(&[], TaggedRef::new(<, syntax)); let unambig_to_ambig = unsafe { std::mem::transmute::<_, TyKind<'_, AmbigArg>>(unambig) }; diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index 2374f388250..ece3f9107b0 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -545,11 +545,12 @@ impl Cursor<'_> { let mut s = self.as_str(); let mut found = false; + let mut size = 0; while let Some(closing) = s.find(&"-".repeat(length_opening as usize)) { let preceding_chars_start = s[..closing].rfind("\n").map_or(0, |i| i + 1); if s[preceding_chars_start..closing].chars().all(is_whitespace) { // candidate found - self.bump_bytes(closing); + self.bump_bytes(size + closing); // in case like // ---cargo // --- blahblah @@ -562,6 +563,7 @@ impl Cursor<'_> { break; } else { s = &s[closing + length_opening as usize..]; + size += closing + length_opening as usize; } } diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index e7e60e8701a..fd2e2ba39ac 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -253,11 +253,6 @@ lint_duplicate_macro_attribute = lint_duplicate_matcher_binding = duplicate matcher binding -lint_elided_named_lifetime = elided lifetime has a name - .label_elided = this elided lifetime gets resolved as `{$name}` - .label_named = lifetime `{$name}` declared here - .suggestion = consider specifying it explicitly - lint_enum_intrinsics_mem_discriminant = the return value of `mem::discriminant` is unspecified when called with a non-enum type .note = the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `{$ty_param}`, which is not an enum @@ -516,6 +511,28 @@ lint_metavariable_still_repeating = variable `{$name}` is still repeating at thi lint_metavariable_wrong_operator = meta-variable repeats with different Kleene operator +lint_mismatched_lifetime_syntaxes = + lifetime flowing from input to output with different syntax can be confusing + .label_mismatched_lifetime_syntaxes_inputs = + {$n_inputs -> + [one] this lifetime flows + *[other] these lifetimes flow + } to the output + .label_mismatched_lifetime_syntaxes_outputs = + the {$n_outputs -> + [one] lifetime gets + *[other] lifetimes get + } resolved as `{$lifetime_name}` + +lint_mismatched_lifetime_syntaxes_suggestion_explicit = + one option is to consistently use `{$lifetime_name}` + +lint_mismatched_lifetime_syntaxes_suggestion_implicit = + one option is to consistently remove the lifetime + +lint_mismatched_lifetime_syntaxes_suggestion_mixed = + one option is to remove the lifetime for references and use the anonymous lifetime for paths + lint_missing_fragment_specifier = missing fragment specifier lint_missing_unsafe_on_extern = extern blocks should be unsafe diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 71b621e8d20..60c477dd6c7 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -10,11 +10,11 @@ use rustc_errors::{ use rustc_middle::middle::stability; use rustc_middle::ty::TyCtxt; use rustc_session::Session; -use rustc_session::lint::{BuiltinLintDiag, ElidedLifetimeResolution}; -use rustc_span::{BytePos, kw}; +use rustc_session::lint::BuiltinLintDiag; +use rustc_span::BytePos; use tracing::debug; -use crate::lints::{self, ElidedNamedLifetime}; +use crate::lints; mod check_cfg; @@ -471,16 +471,5 @@ pub fn decorate_builtin_lint( BuiltinLintDiag::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by } => { lints::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by }.decorate_lint(diag) } - BuiltinLintDiag::ElidedNamedLifetimes { elided: (span, kind), resolution } => { - match resolution { - ElidedLifetimeResolution::Static => { - ElidedNamedLifetime { span, kind, name: kw::StaticLifetime, declaration: None } - } - ElidedLifetimeResolution::Param(name, declaration) => { - ElidedNamedLifetime { span, kind, name, declaration: Some(declaration) } - } - } - .decorate_lint(diag) - } } } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 0a52e42e442..c86f66cc9b0 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -55,6 +55,7 @@ mod invalid_from_utf8; mod late; mod let_underscore; mod levels; +mod lifetime_syntax; mod lints; mod macro_expr_fragment_specifier_2024_migration; mod map_unit_fn; @@ -96,6 +97,7 @@ use impl_trait_overcaptures::ImplTraitOvercaptures; use internal::*; use invalid_from_utf8::*; use let_underscore::*; +use lifetime_syntax::*; use macro_expr_fragment_specifier_2024_migration::*; use map_unit_fn::*; use multiple_supertrait_upcastable::*; @@ -246,6 +248,7 @@ late_lint_methods!( StaticMutRefs: StaticMutRefs, UnqualifiedLocalImports: UnqualifiedLocalImports, CheckTransmutes: CheckTransmutes, + LifetimeSyntax: LifetimeSyntax, ] ] ); @@ -353,6 +356,7 @@ fn register_builtins(store: &mut LintStore) { store.register_renamed("unused_tuple_struct_fields", "dead_code"); store.register_renamed("static_mut_ref", "static_mut_refs"); store.register_renamed("temporary_cstring_as_ptr", "dangling_pointers_from_temporaries"); + store.register_renamed("elided_named_lifetimes", "mismatched_lifetime_syntaxes"); // These were moved to tool lints, but rustc still sees them when compiling normally, before // tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use diff --git a/compiler/rustc_lint/src/lifetime_syntax.rs b/compiler/rustc_lint/src/lifetime_syntax.rs new file mode 100644 index 00000000000..31b038e6a46 --- /dev/null +++ b/compiler/rustc_lint/src/lifetime_syntax.rs @@ -0,0 +1,503 @@ +use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{self as hir, LifetimeSource}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::Span; +use tracing::instrument; + +use crate::{LateContext, LateLintPass, LintContext, lints}; + +declare_lint! { + /// The `mismatched_lifetime_syntaxes` lint detects when the same + /// lifetime is referred to by different syntaxes between function + /// arguments and return values. + /// + /// The three kinds of syntaxes are: + /// + /// 1. Named lifetimes. These are references (`&'a str`) or paths + /// (`Person<'a>`) that use a lifetime with a name, such as + /// `'static` or `'a`. + /// + /// 2. Elided lifetimes. These are references with no explicit + /// lifetime (`&str`), references using the anonymous lifetime + /// (`&'_ str`), and paths using the anonymous lifetime + /// (`Person<'_>`). + /// + /// 3. Hidden lifetimes. These are paths that do not contain any + /// visual indication that it contains a lifetime (`Person`). + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(mismatched_lifetime_syntaxes)] + /// + /// pub fn mixing_named_with_elided(v: &'static u8) -> &u8 { + /// v + /// } + /// + /// struct Person<'a> { + /// name: &'a str, + /// } + /// + /// pub fn mixing_hidden_with_elided(v: Person) -> Person<'_> { + /// v + /// } + /// + /// struct Foo; + /// + /// impl Foo { + /// // Lifetime elision results in the output lifetime becoming + /// // `'static`, which is not what was intended. + /// pub fn get_mut(&'static self, x: &mut u8) -> &mut u8 { + /// unsafe { &mut *(x as *mut _) } + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Lifetime elision is useful because it frees you from having to + /// give each lifetime its own name and show the relation of input + /// and output lifetimes for common cases. However, a lifetime + /// that uses inconsistent syntax between related arguments and + /// return values is more confusing. + /// + /// In certain `unsafe` code, lifetime elision combined with + /// inconsistent lifetime syntax may result in unsound code. + pub MISMATCHED_LIFETIME_SYNTAXES, + Warn, + "detects when a lifetime uses different syntax between arguments and return values" +} + +declare_lint_pass!(LifetimeSyntax => [MISMATCHED_LIFETIME_SYNTAXES]); + +impl<'tcx> LateLintPass<'tcx> for LifetimeSyntax { + #[instrument(skip_all)] + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: hir::intravisit::FnKind<'tcx>, + fd: &'tcx hir::FnDecl<'tcx>, + _: &'tcx hir::Body<'tcx>, + _: rustc_span::Span, + _: rustc_span::def_id::LocalDefId, + ) { + let mut input_map = Default::default(); + let mut output_map = Default::default(); + + for input in fd.inputs { + LifetimeInfoCollector::collect(input, &mut input_map); + } + + if let hir::FnRetTy::Return(output) = fd.output { + LifetimeInfoCollector::collect(output, &mut output_map); + } + + report_mismatches(cx, &input_map, &output_map); + } +} + +#[instrument(skip_all)] +fn report_mismatches<'tcx>( + cx: &LateContext<'tcx>, + inputs: &LifetimeInfoMap<'tcx>, + outputs: &LifetimeInfoMap<'tcx>, +) { + for (resolved_lifetime, output_info) in outputs { + if let Some(input_info) = inputs.get(resolved_lifetime) { + if !lifetimes_use_matched_syntax(input_info, output_info) { + emit_mismatch_diagnostic(cx, input_info, output_info); + } + } + } +} + +fn lifetimes_use_matched_syntax(input_info: &[Info<'_>], output_info: &[Info<'_>]) -> bool { + // Categorize lifetimes into source/syntax buckets. + let mut n_hidden = 0; + let mut n_elided = 0; + let mut n_named = 0; + + for info in input_info.iter().chain(output_info) { + use LifetimeSource::*; + use hir::LifetimeSyntax::*; + + let syntax_source = (info.lifetime.syntax, info.lifetime.source); + + match syntax_source { + // Ignore any other kind of lifetime. + (_, Other) => continue, + + // E.g. `&T`. + (Implicit, Reference | OutlivesBound | PreciseCapturing) | + // E.g. `&'_ T`. + (ExplicitAnonymous, Reference | OutlivesBound | PreciseCapturing) | + // E.g. `ContainsLifetime<'_>`. + (ExplicitAnonymous, Path { .. }) => n_elided += 1, + + // E.g. `ContainsLifetime`. + (Implicit, Path { .. }) => n_hidden += 1, + + // E.g. `&'a T`. + (ExplicitBound, Reference | OutlivesBound | PreciseCapturing) | + // E.g. `ContainsLifetime<'a>`. + (ExplicitBound, Path { .. }) => n_named += 1, + }; + } + + let syntax_counts = (n_hidden, n_elided, n_named); + tracing::debug!(?syntax_counts); + + matches!(syntax_counts, (_, 0, 0) | (0, _, 0) | (0, 0, _)) +} + +fn emit_mismatch_diagnostic<'tcx>( + cx: &LateContext<'tcx>, + input_info: &[Info<'_>], + output_info: &[Info<'_>], +) { + // There can only ever be zero or one bound lifetime + // for a given lifetime resolution. + let mut bound_lifetime = None; + + // We offer the following kinds of suggestions (when appropriate + // such that the suggestion wouldn't violate the lint): + // + // 1. Every lifetime becomes named, when there is already a + // user-provided name. + // + // 2. A "mixed" signature, where references become implicit + // and paths become explicitly anonymous. + // + // 3. Every lifetime becomes implicit. + // + // 4. Every lifetime becomes explicitly anonymous. + // + // Number 2 is arguably the most common pattern and the one we + // should push strongest. Number 3 is likely the next most common, + // followed by number 1. Coming in at a distant last would be + // number 4. + // + // Beyond these, there are variants of acceptable signatures that + // we won't suggest because they are very low-value. For example, + // we will never suggest `fn(&T1, &'_ T2) -> &T3` even though that + // would pass the lint. + // + // The following collections are the lifetime instances that we + // suggest changing to a given alternate style. + + // 1. Convert all to named. + let mut suggest_change_to_explicit_bound = Vec::new(); + + // 2. Convert to mixed. We track each kind of change separately. + let mut suggest_change_to_mixed_implicit = Vec::new(); + let mut suggest_change_to_mixed_explicit_anonymous = Vec::new(); + + // 3. Convert all to implicit. + let mut suggest_change_to_implicit = Vec::new(); + + // 4. Convert all to explicit anonymous. + let mut suggest_change_to_explicit_anonymous = Vec::new(); + + // Some styles prevent using implicit syntax at all. + let mut allow_suggesting_implicit = true; + + // It only makes sense to suggest mixed if we have both sources. + let mut saw_a_reference = false; + let mut saw_a_path = false; + + for info in input_info.iter().chain(output_info) { + use LifetimeSource::*; + use hir::LifetimeSyntax::*; + + let syntax_source = (info.lifetime.syntax, info.lifetime.source); + + if let (_, Other) = syntax_source { + // Ignore any other kind of lifetime. + continue; + } + + if let (ExplicitBound, _) = syntax_source { + bound_lifetime = Some(info); + } + + match syntax_source { + // E.g. `&T`. + (Implicit, Reference) => { + suggest_change_to_explicit_anonymous.push(info); + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `&'_ T`. + (ExplicitAnonymous, Reference) => { + suggest_change_to_implicit.push(info); + suggest_change_to_mixed_implicit.push(info); + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `ContainsLifetime`. + (Implicit, Path { .. }) => { + suggest_change_to_mixed_explicit_anonymous.push(info); + suggest_change_to_explicit_anonymous.push(info); + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `ContainsLifetime<'_>`. + (ExplicitAnonymous, Path { .. }) => { + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `&'a T`. + (ExplicitBound, Reference) => { + suggest_change_to_implicit.push(info); + suggest_change_to_mixed_implicit.push(info); + suggest_change_to_explicit_anonymous.push(info); + } + + // E.g. `ContainsLifetime<'a>`. + (ExplicitBound, Path { .. }) => { + suggest_change_to_mixed_explicit_anonymous.push(info); + suggest_change_to_explicit_anonymous.push(info); + } + + (Implicit, OutlivesBound | PreciseCapturing) => { + panic!("This syntax / source combination is not possible"); + } + + // E.g. `+ '_`, `+ use<'_>`. + (ExplicitAnonymous, OutlivesBound | PreciseCapturing) => { + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `+ 'a`, `+ use<'a>`. + (ExplicitBound, OutlivesBound | PreciseCapturing) => { + suggest_change_to_mixed_explicit_anonymous.push(info); + suggest_change_to_explicit_anonymous.push(info); + } + + (_, Other) => { + panic!("This syntax / source combination has already been skipped"); + } + } + + if matches!(syntax_source, (_, Path { .. } | OutlivesBound | PreciseCapturing)) { + allow_suggesting_implicit = false; + } + + match syntax_source { + (_, Reference) => saw_a_reference = true, + (_, Path { .. }) => saw_a_path = true, + _ => {} + } + } + + let make_implicit_suggestions = + |infos: &[&Info<'_>]| infos.iter().map(|i| i.removing_span()).collect::<Vec<_>>(); + + let inputs = input_info.iter().map(|info| info.reporting_span()).collect(); + let outputs = output_info.iter().map(|info| info.reporting_span()).collect(); + + let explicit_bound_suggestion = bound_lifetime.map(|info| { + build_mismatch_suggestion(info.lifetime_name(), &suggest_change_to_explicit_bound) + }); + + let is_bound_static = bound_lifetime.is_some_and(|info| info.is_static()); + + tracing::debug!(?bound_lifetime, ?explicit_bound_suggestion, ?is_bound_static); + + let should_suggest_mixed = + // Do we have a mixed case? + (saw_a_reference && saw_a_path) && + // Is there anything to change? + (!suggest_change_to_mixed_implicit.is_empty() || + !suggest_change_to_mixed_explicit_anonymous.is_empty()) && + // If we have `'static`, we don't want to remove it. + !is_bound_static; + + let mixed_suggestion = should_suggest_mixed.then(|| { + let implicit_suggestions = make_implicit_suggestions(&suggest_change_to_mixed_implicit); + + let explicit_anonymous_suggestions = suggest_change_to_mixed_explicit_anonymous + .iter() + .map(|info| info.suggestion("'_")) + .collect(); + + lints::MismatchedLifetimeSyntaxesSuggestion::Mixed { + implicit_suggestions, + explicit_anonymous_suggestions, + tool_only: false, + } + }); + + tracing::debug!( + ?suggest_change_to_mixed_implicit, + ?suggest_change_to_mixed_explicit_anonymous, + ?mixed_suggestion, + ); + + let should_suggest_implicit = + // Is there anything to change? + !suggest_change_to_implicit.is_empty() && + // We never want to hide the lifetime in a path (or similar). + allow_suggesting_implicit && + // If we have `'static`, we don't want to remove it. + !is_bound_static; + + let implicit_suggestion = should_suggest_implicit.then(|| { + let suggestions = make_implicit_suggestions(&suggest_change_to_implicit); + + lints::MismatchedLifetimeSyntaxesSuggestion::Implicit { suggestions, tool_only: false } + }); + + tracing::debug!( + ?should_suggest_implicit, + ?suggest_change_to_implicit, + allow_suggesting_implicit, + ?implicit_suggestion, + ); + + let should_suggest_explicit_anonymous = + // Is there anything to change? + !suggest_change_to_explicit_anonymous.is_empty() && + // If we have `'static`, we don't want to remove it. + !is_bound_static; + + let explicit_anonymous_suggestion = should_suggest_explicit_anonymous + .then(|| build_mismatch_suggestion("'_", &suggest_change_to_explicit_anonymous)); + + tracing::debug!( + ?should_suggest_explicit_anonymous, + ?suggest_change_to_explicit_anonymous, + ?explicit_anonymous_suggestion, + ); + + let lifetime_name = bound_lifetime.map(|info| info.lifetime_name()).unwrap_or("'_").to_owned(); + + // We can produce a number of suggestions which may overwhelm + // the user. Instead, we order the suggestions based on Rust + // idioms. The "best" choice is shown to the user and the + // remaining choices are shown to tools only. + let mut suggestions = Vec::new(); + suggestions.extend(explicit_bound_suggestion); + suggestions.extend(mixed_suggestion); + suggestions.extend(implicit_suggestion); + suggestions.extend(explicit_anonymous_suggestion); + + cx.emit_span_lint( + MISMATCHED_LIFETIME_SYNTAXES, + Vec::clone(&inputs), + lints::MismatchedLifetimeSyntaxes { lifetime_name, inputs, outputs, suggestions }, + ); +} + +fn build_mismatch_suggestion( + lifetime_name: &str, + infos: &[&Info<'_>], +) -> lints::MismatchedLifetimeSyntaxesSuggestion { + let lifetime_name = lifetime_name.to_owned(); + + let suggestions = infos.iter().map(|info| info.suggestion(&lifetime_name)).collect(); + + lints::MismatchedLifetimeSyntaxesSuggestion::Explicit { + lifetime_name, + suggestions, + tool_only: false, + } +} + +#[derive(Debug)] +struct Info<'tcx> { + type_span: Span, + referenced_type_span: Option<Span>, + lifetime: &'tcx hir::Lifetime, +} + +impl<'tcx> Info<'tcx> { + fn lifetime_name(&self) -> &str { + self.lifetime.ident.as_str() + } + + fn is_static(&self) -> bool { + self.lifetime.is_static() + } + + /// When reporting a lifetime that is implicit, we expand the span + /// to include the type. Otherwise we end up pointing at nothing, + /// which is a bit confusing. + fn reporting_span(&self) -> Span { + if self.lifetime.is_implicit() { self.type_span } else { self.lifetime.ident.span } + } + + /// When removing an explicit lifetime from a reference, + /// we want to remove the whitespace after the lifetime. + /// + /// ```rust + /// fn x(a: &'_ u8) {} + /// ``` + /// + /// Should become: + /// + /// ```rust + /// fn x(a: &u8) {} + /// ``` + // FIXME: Ideally, we'd also remove the lifetime declaration. + fn removing_span(&self) -> Span { + let mut span = self.suggestion("'dummy").0; + + if let Some(referenced_type_span) = self.referenced_type_span { + span = span.until(referenced_type_span); + } + + span + } + + fn suggestion(&self, lifetime_name: &str) -> (Span, String) { + self.lifetime.suggestion(lifetime_name) + } +} + +type LifetimeInfoMap<'tcx> = FxIndexMap<&'tcx hir::LifetimeKind, Vec<Info<'tcx>>>; + +struct LifetimeInfoCollector<'a, 'tcx> { + type_span: Span, + referenced_type_span: Option<Span>, + map: &'a mut LifetimeInfoMap<'tcx>, +} + +impl<'a, 'tcx> LifetimeInfoCollector<'a, 'tcx> { + fn collect(ty: &'tcx hir::Ty<'tcx>, map: &'a mut LifetimeInfoMap<'tcx>) { + let mut this = Self { type_span: ty.span, referenced_type_span: None, map }; + + intravisit::walk_unambig_ty(&mut this, ty); + } +} + +impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> { + #[instrument(skip(self))] + fn visit_lifetime(&mut self, lifetime: &'tcx hir::Lifetime) { + let type_span = self.type_span; + let referenced_type_span = self.referenced_type_span; + + let info = Info { type_span, referenced_type_span, lifetime }; + + self.map.entry(&lifetime.kind).or_default().push(info); + } + + #[instrument(skip(self))] + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) -> Self::Result { + let old_type_span = self.type_span; + let old_referenced_type_span = self.referenced_type_span; + + self.type_span = ty.span; + if let hir::TyKind::Ref(_, ty) = ty.kind { + self.referenced_type_span = Some(ty.ty.span); + } + + intravisit::walk_ty(self, ty); + + self.type_span = old_type_span; + self.referenced_type_span = old_referenced_type_span; + } +} diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 10d0e2c93a8..9d3c74a9a2b 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -8,17 +8,17 @@ use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString, ElidedLifetimeInPathSubdiag, EmissionGuarantee, LintDiagnostic, MultiSpan, Subdiagnostic, SuggestionStyle, }; +use rustc_hir as hir; use rustc_hir::def::Namespace; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::VisitorExt; -use rustc_hir::{self as hir, MissingLifetimeKind}; use rustc_macros::{LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::inhabitedness::InhabitedPredicate; use rustc_middle::ty::{Clause, PolyExistentialTraitRef, Ty, TyCtxt}; use rustc_session::Session; use rustc_session::lint::AmbiguityErrorDiag; use rustc_span::edition::Edition; -use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol, kw, sym}; +use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol, sym}; use crate::builtin::{InitError, ShorthandAssocTyCollector, TypeAliasBounds}; use crate::errors::{OverruledAttributeSub, RequestedLevel}; @@ -2752,58 +2752,6 @@ pub(crate) struct ElidedLifetimesInPaths { pub subdiag: ElidedLifetimeInPathSubdiag, } -pub(crate) struct ElidedNamedLifetime { - pub span: Span, - pub kind: MissingLifetimeKind, - pub name: Symbol, - pub declaration: Option<Span>, -} - -impl<G: EmissionGuarantee> LintDiagnostic<'_, G> for ElidedNamedLifetime { - fn decorate_lint(self, diag: &mut rustc_errors::Diag<'_, G>) { - let Self { span, kind, name, declaration } = self; - diag.primary_message(fluent::lint_elided_named_lifetime); - diag.arg("name", name); - diag.span_label(span, fluent::lint_label_elided); - if let Some(declaration) = declaration { - diag.span_label(declaration, fluent::lint_label_named); - } - // FIXME(GrigorenkoPV): this `if` and `return` should be removed, - // but currently this lint's suggestions can conflict with those of `clippy::needless_lifetimes`: - // https://github.com/rust-lang/rust/pull/129840#issuecomment-2323349119 - // HACK: `'static` suggestions will never sonflict, emit only those for now. - if name != kw::StaticLifetime { - return; - } - match kind { - MissingLifetimeKind::Underscore => diag.span_suggestion_verbose( - span, - fluent::lint_suggestion, - format!("{name}"), - Applicability::MachineApplicable, - ), - MissingLifetimeKind::Ampersand => diag.span_suggestion_verbose( - span.shrink_to_hi(), - fluent::lint_suggestion, - format!("{name} "), - Applicability::MachineApplicable, - ), - MissingLifetimeKind::Comma => diag.span_suggestion_verbose( - span.shrink_to_hi(), - fluent::lint_suggestion, - format!("{name}, "), - Applicability::MachineApplicable, - ), - MissingLifetimeKind::Brackets => diag.span_suggestion_verbose( - span.shrink_to_hi(), - fluent::lint_suggestion, - format!("<{name}>"), - Applicability::MachineApplicable, - ), - }; - } -} - #[derive(LintDiagnostic)] #[diag(lint_invalid_crate_type_value)] pub(crate) struct UnknownCrateTypes { @@ -3241,3 +3189,128 @@ pub(crate) struct ReservedMultihash { #[suggestion(code = " ", applicability = "machine-applicable")] pub suggestion: Span, } + +#[derive(Debug)] +pub(crate) struct MismatchedLifetimeSyntaxes { + pub lifetime_name: String, + pub inputs: Vec<Span>, + pub outputs: Vec<Span>, + + pub suggestions: Vec<MismatchedLifetimeSyntaxesSuggestion>, +} + +impl<'a, G: EmissionGuarantee> LintDiagnostic<'a, G> for MismatchedLifetimeSyntaxes { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) { + diag.primary_message(fluent::lint_mismatched_lifetime_syntaxes); + + diag.arg("lifetime_name", self.lifetime_name); + + diag.arg("n_inputs", self.inputs.len()); + for input in self.inputs { + let a = diag.eagerly_translate(fluent::lint_label_mismatched_lifetime_syntaxes_inputs); + diag.span_label(input, a); + } + + diag.arg("n_outputs", self.outputs.len()); + for output in self.outputs { + let a = diag.eagerly_translate(fluent::lint_label_mismatched_lifetime_syntaxes_outputs); + diag.span_label(output, a); + } + + let mut suggestions = self.suggestions.into_iter(); + if let Some(s) = suggestions.next() { + diag.subdiagnostic(s); + + for mut s in suggestions { + s.make_tool_only(); + diag.subdiagnostic(s); + } + } + } +} + +#[derive(Debug)] +pub(crate) enum MismatchedLifetimeSyntaxesSuggestion { + Implicit { + suggestions: Vec<Span>, + tool_only: bool, + }, + + Mixed { + implicit_suggestions: Vec<Span>, + explicit_anonymous_suggestions: Vec<(Span, String)>, + tool_only: bool, + }, + + Explicit { + lifetime_name: String, + suggestions: Vec<(Span, String)>, + tool_only: bool, + }, +} + +impl MismatchedLifetimeSyntaxesSuggestion { + fn make_tool_only(&mut self) { + use MismatchedLifetimeSyntaxesSuggestion::*; + + let tool_only = match self { + Implicit { tool_only, .. } | Mixed { tool_only, .. } | Explicit { tool_only, .. } => { + tool_only + } + }; + + *tool_only = true; + } +} + +impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { + use MismatchedLifetimeSyntaxesSuggestion::*; + + let style = |tool_only| { + if tool_only { SuggestionStyle::CompletelyHidden } else { SuggestionStyle::ShowAlways } + }; + + match self { + Implicit { suggestions, tool_only } => { + let suggestions = suggestions.into_iter().map(|s| (s, String::new())).collect(); + diag.multipart_suggestion_with_style( + fluent::lint_mismatched_lifetime_syntaxes_suggestion_implicit, + suggestions, + Applicability::MachineApplicable, + style(tool_only), + ); + } + + Mixed { implicit_suggestions, explicit_anonymous_suggestions, tool_only } => { + let implicit_suggestions = + implicit_suggestions.into_iter().map(|s| (s, String::new())); + + let suggestions = + implicit_suggestions.chain(explicit_anonymous_suggestions).collect(); + + diag.multipart_suggestion_with_style( + fluent::lint_mismatched_lifetime_syntaxes_suggestion_mixed, + suggestions, + Applicability::MachineApplicable, + style(tool_only), + ); + } + + Explicit { lifetime_name, suggestions, tool_only } => { + diag.arg("lifetime_name", lifetime_name); + + let msg = diag.eagerly_translate( + fluent::lint_mismatched_lifetime_syntaxes_suggestion_explicit, + ); + + diag.multipart_suggestion_with_style( + msg, + suggestions, + Applicability::MachineApplicable, + style(tool_only), + ); + } + } + } +} diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 1b14157c5f0..843d5778421 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -40,7 +40,6 @@ declare_lint_pass! { DUPLICATE_MACRO_ATTRIBUTES, ELIDED_LIFETIMES_IN_ASSOCIATED_CONSTANT, ELIDED_LIFETIMES_IN_PATHS, - ELIDED_NAMED_LIFETIMES, EXPLICIT_BUILTIN_CFGS_IN_FLAGS, EXPORTED_PRIVATE_DEPENDENCIES, FFI_UNWIND_CALLS, @@ -1833,38 +1832,6 @@ declare_lint! { } declare_lint! { - /// The `elided_named_lifetimes` lint detects when an elided - /// lifetime ends up being a named lifetime, such as `'static` - /// or some lifetime parameter `'a`. - /// - /// ### Example - /// - /// ```rust,compile_fail - /// #![deny(elided_named_lifetimes)] - /// struct Foo; - /// impl Foo { - /// pub fn get_mut(&'static self, x: &mut u8) -> &mut u8 { - /// unsafe { &mut *(x as *mut _) } - /// } - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// Lifetime elision is quite useful, because it frees you from having - /// to give each lifetime its own name, but sometimes it can produce - /// somewhat surprising resolutions. In safe code, it is mostly okay, - /// because the borrow checker prevents any unsoundness, so the worst - /// case scenario is you get a confusing error message in some other place. - /// But with `unsafe` code, such unexpected resolutions may lead to unsound code. - pub ELIDED_NAMED_LIFETIMES, - Warn, - "detects when an elided lifetime gets resolved to be `'static` or some named parameter" -} - -declare_lint! { /// The `bare_trait_objects` lint suggests using `dyn Trait` for trait /// objects. /// diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index e19bf59e543..1d9b7a7fcb9 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -9,7 +9,7 @@ use rustc_data_structures::stable_hasher::{ use rustc_error_messages::{DiagMessage, MultiSpan}; use rustc_hir::def::Namespace; use rustc_hir::def_id::DefPathHash; -use rustc_hir::{HashStableContext, HirId, ItemLocalId, MissingLifetimeKind}; +use rustc_hir::{HashStableContext, HirId, ItemLocalId}; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; pub use rustc_span::edition::Edition; use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol, sym}; @@ -610,12 +610,6 @@ pub enum DeprecatedSinceKind { InVersion(String), } -#[derive(Debug)] -pub enum ElidedLifetimeResolution { - Static, - Param(Symbol, Span), -} - // This could be a closure, but then implementing derive trait // becomes hacky (and it gets allocated). #[derive(Debug)] @@ -628,10 +622,6 @@ pub enum BuiltinLintDiag { }, MacroExpandedMacroExportsAccessedByAbsolutePaths(Span), ElidedLifetimesInPaths(usize, Span, bool, Span), - ElidedNamedLifetimes { - elided: (Span, MissingLifetimeKind), - resolution: ElidedLifetimeResolution, - }, UnknownCrateTypes { span: Span, candidate: Option<Symbol>, diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 2a4be5fc3b1..3dc285fdab6 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -1729,7 +1729,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { if ident.name == kw::StaticLifetime { self.record_lifetime_res( lifetime.id, - LifetimeRes::Static { suppress_elision_warning: false }, + LifetimeRes::Static, LifetimeElisionCandidate::Named, ); return; @@ -1877,8 +1877,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { if lifetimes_in_scope.is_empty() { self.record_lifetime_res( lifetime.id, - // We are inside a const item, so do not warn. - LifetimeRes::Static { suppress_elision_warning: true }, + LifetimeRes::Static, elision_candidate, ); return; @@ -2225,47 +2224,6 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { panic!("lifetime {id:?} resolved multiple times ({prev_res:?} before, {res:?} now)") } - match candidate { - LifetimeElisionCandidate::Missing(missing @ MissingLifetime { .. }) => { - debug_assert_eq!(id, missing.id); - match res { - LifetimeRes::Static { suppress_elision_warning } => { - if !suppress_elision_warning { - self.r.lint_buffer.buffer_lint( - lint::builtin::ELIDED_NAMED_LIFETIMES, - missing.id_for_lint, - missing.span, - BuiltinLintDiag::ElidedNamedLifetimes { - elided: (missing.span, missing.kind), - resolution: lint::ElidedLifetimeResolution::Static, - }, - ); - } - } - LifetimeRes::Param { param, binder: _ } => { - let tcx = self.r.tcx(); - self.r.lint_buffer.buffer_lint( - lint::builtin::ELIDED_NAMED_LIFETIMES, - missing.id_for_lint, - missing.span, - BuiltinLintDiag::ElidedNamedLifetimes { - elided: (missing.span, missing.kind), - resolution: lint::ElidedLifetimeResolution::Param( - tcx.item_name(param.into()), - tcx.source_span(param), - ), - }, - ); - } - LifetimeRes::Fresh { .. } - | LifetimeRes::Infer - | LifetimeRes::Error - | LifetimeRes::ElidedAnchor { .. } => {} - } - } - LifetimeElisionCandidate::Ignore | LifetimeElisionCandidate::Named => {} - } - match res { LifetimeRes::Param { .. } | LifetimeRes::Fresh { .. } | LifetimeRes::Static { .. } => { if let Some(ref mut candidates) = self.lifetime_elision_candidates { @@ -2788,14 +2746,9 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { .. }) => { self.with_static_rib(def_kind, |this| { - this.with_lifetime_rib( - LifetimeRibKind::Elided(LifetimeRes::Static { - suppress_elision_warning: true, - }), - |this| { - this.visit_ty(ty); - }, - ); + this.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Static), |this| { + this.visit_ty(ty); + }); if let Some(expr) = expr { // We already forbid generic params because of the above item rib, // so it doesn't matter whether this is a trivial constant. @@ -2832,9 +2785,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { this.visit_generics(generics); this.with_lifetime_rib( - LifetimeRibKind::Elided(LifetimeRes::Static { - suppress_elision_warning: true, - }), + LifetimeRibKind::Elided(LifetimeRes::Static), |this| this.visit_ty(ty), ); diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 97a45fcf233..2f6aed35f25 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -3440,7 +3440,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { maybe_static = true; in_scope_lifetimes = vec![( Ident::with_dummy_span(kw::StaticLifetime), - (DUMMY_NODE_ID, LifetimeRes::Static { suppress_elision_warning: false }), + (DUMMY_NODE_ID, LifetimeRes::Static), )]; } } else if elided_len == 0 { @@ -3452,7 +3452,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { maybe_static = true; in_scope_lifetimes = vec![( Ident::with_dummy_span(kw::StaticLifetime), - (DUMMY_NODE_ID, LifetimeRes::Static { suppress_elision_warning: false }), + (DUMMY_NODE_ID, LifetimeRes::Static), )]; } } else if num_params == 1 { diff --git a/compiler/rustc_target/src/spec/abi_map.rs b/compiler/rustc_target/src/spec/abi_map.rs index d9101f79f04..be94e18dc39 100644 --- a/compiler/rustc_target/src/spec/abi_map.rs +++ b/compiler/rustc_target/src/spec/abi_map.rs @@ -29,6 +29,7 @@ impl AbiMapping { } } + #[track_caller] pub fn unwrap(self) -> CanonAbi { self.into_option().unwrap() } diff --git a/compiler/rustc_trait_selection/src/errors.rs b/compiler/rustc_trait_selection/src/errors.rs index 0bea308ed5c..06f81ac554e 100644 --- a/compiler/rustc_trait_selection/src/errors.rs +++ b/compiler/rustc_trait_selection/src/errors.rs @@ -593,7 +593,7 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> { matches!( arg, hir::GenericArg::Lifetime(lifetime) - if lifetime.is_syntactically_hidden() + if lifetime.is_implicit() ) }) { self.suggestions.push(( |
