Merge from rustc

This commit is contained in:
The Miri Cronjob Bot 2025-06-06 05:02:49 +00:00
commit c44bc10b67
291 changed files with 4610 additions and 2821 deletions

View file

@ -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(&region, 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)

View file

@ -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(&region_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> {

View file

@ -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
}

View file

@ -72,6 +72,7 @@ mod constraints;
mod dataflow;
mod def_use;
mod diagnostics;
mod handle_placeholders;
mod member_constraints;
mod nll;
mod path_utils;

View file

@ -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.

View file

@ -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<'_>| {

View file

@ -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),
)?;
}

View file

@ -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;
impl Representative {
pub(crate) fn rvid(self) -> RegionVid {
match self {
Representative::FreeRegion(region_vid)
| Representative::Placeholder(region_vid)
| Representative::Existential(region_vid) => region_vid,
}
if self.representative_is_placeholder && other.representative_is_existential
|| (self.representative <= other.representative)
{
self.merge_min_max_seen(&other);
return self;
}
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);
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::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)
}
// For reachability, we do nothing since the representative doesn't change.
fn merge_reached(self, _other: Self) -> Self {
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 }
}
}
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;
}
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: _,
liveness_constraints,
mut outlives_constraints,
mut member_constraints,
universe_causes,
let LoweredConstraints {
constraint_sccs,
definitions,
outlives_constraints,
scc_annotations,
type_tests,
} = constraints;
liveness_constraints,
universe_causes,
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>,

View file

@ -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)) => {

View file

@ -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) {

View file

@ -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();
}

View file

@ -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
}
}
}

View file

@ -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);
},
)
}

View file

@ -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");

View file

@ -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.

View file

@ -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:?}")
}
}
}

View file

@ -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(&lt, syntax));
let unambig_to_ambig = unsafe { std::mem::transmute::<_, TyKind<'_, AmbigArg>>(unambig) };

View file

@ -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;
}
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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),
);
}
}
}
}

View file

@ -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,
@ -1832,38 +1831,6 @@ declare_lint! {
"hidden lifetime parameters in types are deprecated"
}
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.

View file

@ -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>,

View file

@ -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),
);

View file

@ -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 {

View file

@ -29,6 +29,7 @@ impl AbiMapping {
}
}
#[track_caller]
pub fn unwrap(self) -> CanonAbi {
self.into_option().unwrap()
}

View file

@ -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((