Auto merge of #112016 - GuillaumeGomez:rollup-fhqn4i6, r=GuillaumeGomez

Rollup of 6 pull requests

Successful merges:

 - #111936 (Include test suite metadata in the build metrics)
 - #111952 (Remove DesugaringKind::Replace.)
 - #111966 (Add #[inline] to array TryFrom impls)
 - #111983 (Perform MIR type ops locally in new solver)
 - #111997 (Fix re-export of doc hidden macro not showing up)
 - #112014 (rustdoc: get unnormalized link destination for suggestions)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2023-05-27 12:29:07 +00:00
commit 82b311b418
54 changed files with 1610 additions and 749 deletions

View file

@ -1635,34 +1635,6 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
})
}
/// Reports StorageDeadOrDrop of `place` conflicts with `borrow`.
///
/// Depending on the origin of the StorageDeadOrDrop, this may be
/// reported as either a drop or an illegal mutation of a borrowed value.
/// The latter is preferred when the this is a drop triggered by a
/// reassignment, as it's more user friendly to report a problem with the
/// explicit assignment than the implicit drop.
#[instrument(level = "debug", skip(self))]
pub(crate) fn report_storage_dead_or_drop_of_borrowed(
&mut self,
location: Location,
place_span: (Place<'tcx>, Span),
borrow: &BorrowData<'tcx>,
) {
// It's sufficient to check the last desugaring as Replace is the last
// one to be applied.
if let Some(DesugaringKind::Replace) = place_span.1.desugaring_kind() {
self.report_illegal_mutation_of_borrowed(location, place_span, borrow)
} else {
self.report_borrowed_value_does_not_live_long_enough(
location,
borrow,
place_span,
Some(WriteKind::StorageDeadOrDrop),
)
}
}
/// This means that some data referenced by `borrow` needs to live
/// past the point where the StorageDeadOrDrop of `place` occurs.
/// This is usually interpreted as meaning that `place` has too

View file

@ -641,13 +641,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
let Some(hir::Node::Item(item)) = node else { return; };
let hir::ItemKind::Fn(.., body_id) = item.kind else { return; };
let body = self.infcx.tcx.hir().body(body_id);
let mut assign_span = span;
// Drop desugaring is done at MIR build so it's not in the HIR
if let Some(DesugaringKind::Replace) = span.desugaring_kind() {
assign_span.remove_mark();
}
let mut v = V { assign_span, err, ty, suggested: false };
let mut v = V { assign_span: span, err, ty, suggested: false };
v.visit_body(body);
if !v.suggested {
err.help(format!(

View file

@ -112,11 +112,13 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
TerminatorKind::SwitchInt { discr, targets: _ } => {
self.consume_operand(location, discr);
}
TerminatorKind::Drop { place: drop_place, target: _, unwind: _ } => {
TerminatorKind::Drop { place: drop_place, target: _, unwind: _, replace } => {
let write_kind =
if *replace { WriteKind::Replace } else { WriteKind::StorageDeadOrDrop };
self.access_place(
location,
*drop_place,
(AccessDepth::Drop, Write(WriteKind::StorageDeadOrDrop)),
(AccessDepth::Drop, Write(write_kind)),
LocalMutationIsAllowed::Yes,
);
}

View file

@ -685,17 +685,19 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx
TerminatorKind::SwitchInt { discr, targets: _ } => {
self.consume_operand(loc, (discr, span), flow_state);
}
TerminatorKind::Drop { place, target: _, unwind: _ } => {
TerminatorKind::Drop { place, target: _, unwind: _, replace } => {
debug!(
"visit_terminator_drop \
loc: {:?} term: {:?} place: {:?} span: {:?}",
loc, term, place, span
);
let write_kind =
if *replace { WriteKind::Replace } else { WriteKind::StorageDeadOrDrop };
self.access_place(
loc,
(*place, span),
(AccessDepth::Drop, Write(WriteKind::StorageDeadOrDrop)),
(AccessDepth::Drop, Write(write_kind)),
LocalMutationIsAllowed::Yes,
flow_state,
);
@ -885,6 +887,7 @@ enum ReadKind {
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum WriteKind {
StorageDeadOrDrop,
Replace,
MutableBorrow(BorrowKind),
Mutate,
Move,
@ -1132,13 +1135,21 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
this.buffer_error(err);
}
WriteKind::StorageDeadOrDrop => this
.report_storage_dead_or_drop_of_borrowed(location, place_span, borrow),
.report_borrowed_value_does_not_live_long_enough(
location,
borrow,
place_span,
Some(WriteKind::StorageDeadOrDrop),
),
WriteKind::Mutate => {
this.report_illegal_mutation_of_borrowed(location, place_span, borrow)
}
WriteKind::Move => {
this.report_move_out_while_borrowed(location, place_span, borrow)
}
WriteKind::Replace => {
this.report_illegal_mutation_of_borrowed(location, place_span, borrow)
}
}
Control::Break
}
@ -1982,12 +1993,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
Reservation(
WriteKind::Move
| WriteKind::Replace
| WriteKind::StorageDeadOrDrop
| WriteKind::MutableBorrow(BorrowKind::Shared)
| WriteKind::MutableBorrow(BorrowKind::Shallow),
)
| Write(
WriteKind::Move
| WriteKind::Replace
| WriteKind::StorageDeadOrDrop
| WriteKind::MutableBorrow(BorrowKind::Shared)
| WriteKind::MutableBorrow(BorrowKind::Shallow),

View file

@ -3,9 +3,9 @@ use rustc_index::bit_set::HybridBitSet;
use rustc_index::interval::IntervalSet;
use rustc_infer::infer::canonical::QueryRegionConstraints;
use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
use rustc_middle::traits::query::DropckOutlivesResult;
use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt};
use rustc_span::DUMMY_SP;
use rustc_trait_selection::traits::query::dropck_outlives::DropckOutlivesResult;
use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives;
use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
use std::rc::Rc;

View file

@ -473,7 +473,7 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
| TerminatorKind::GeneratorDrop => {
bug!("shouldn't exist at codegen {:?}", bb_data.terminator());
}
TerminatorKind::Drop { place, target, unwind: _ } => {
TerminatorKind::Drop { place, target, unwind: _, replace: _ } => {
let drop_place = codegen_place(fx, *place);
crate::abi::codegen_drop(fx, source_info, drop_place);

View file

@ -1256,7 +1256,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
MergingSucc::False
}
mir::TerminatorKind::Drop { place, target, unwind } => {
mir::TerminatorKind::Drop { place, target, unwind, replace: _ } => {
self.codegen_drop_terminator(helper, bx, place, target, unwind, mergeable_succ())
}

View file

@ -114,7 +114,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
Drop { place, target, unwind } => {
Drop { place, target, unwind, replace: _ } => {
let frame = self.frame();
let ty = place.ty(&frame.body.local_decls, *self.tcx).ty;
let ty = self.subst_from_frame_and_normalize_erasing_regions(frame, ty)?;

View file

@ -603,7 +603,11 @@ pub enum TerminatorKind<'tcx> {
/// > The drop glue is executed if, among all statements executed within this `Body`, an assignment to
/// > the place or one of its "parents" occurred more recently than a move out of it. This does not
/// > consider indirect assignments.
Drop { place: Place<'tcx>, target: BasicBlock, unwind: UnwindAction },
///
/// The `replace` flag indicates whether this terminator was created as part of an assignment.
/// This should only be used for diagnostic purposes, and does not have any operational
/// meaning.
Drop { place: Place<'tcx>, target: BasicBlock, unwind: UnwindAction, replace: bool },
/// Roughly speaking, evaluates the `func` operand and the arguments, and starts execution of
/// the referred to function. The operand types must match the argument types of the function.

View file

@ -504,6 +504,7 @@ macro_rules! make_mir_visitor {
place,
target: _,
unwind: _,
replace: _,
} => {
self.visit_place(
place,

View file

@ -57,6 +57,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
place: self.parse_place(args[0])?,
target: self.parse_block(args[1])?,
unwind: UnwindAction::Continue,
replace: false,
})
},
@call("mir_call", args) => {

View file

@ -725,6 +725,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
place: to_drop,
target: success,
unwind: UnwindAction::Continue,
replace: false,
},
);
this.diverge_from(block);

View file

@ -91,7 +91,7 @@ use rustc_middle::middle::region;
use rustc_middle::mir::*;
use rustc_middle::thir::{Expr, LintLevel};
use rustc_span::{DesugaringKind, Span, DUMMY_SP};
use rustc_span::{Span, DUMMY_SP};
#[derive(Debug)]
pub struct Scopes<'tcx> {
@ -371,6 +371,7 @@ impl DropTree {
// The caller will handle this if needed.
unwind: UnwindAction::Terminate,
place: drop_data.0.local.into(),
replace: false,
};
cfg.terminate(block, drop_data.0.source_info, terminator);
}
@ -1128,9 +1129,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
place: Place<'tcx>,
value: Rvalue<'tcx>,
) -> BlockAnd<()> {
let span = self.tcx.with_stable_hashing_context(|hcx| {
span.mark_with_reason(None, DesugaringKind::Replace, self.tcx.sess.edition(), hcx)
});
let source_info = self.source_info(span);
// create the new block for the assignment
@ -1148,6 +1146,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
place,
target: assign,
unwind: UnwindAction::Cleanup(assign_unwind),
replace: true,
},
);
self.diverge_from(block);
@ -1261,6 +1260,7 @@ fn build_scope_drops<'tcx>(
place: local.into(),
target: next,
unwind: UnwindAction::Continue,
replace: false,
},
);
block = next;

View file

@ -237,6 +237,7 @@ where
place: self.place,
target: self.succ,
unwind: self.unwind.into_action(),
replace: false,
},
);
}
@ -719,6 +720,7 @@ where
place: tcx.mk_place_deref(ptr),
target: loop_block,
unwind: unwind.into_action(),
replace: false,
},
);
@ -963,8 +965,12 @@ where
}
fn drop_block(&mut self, target: BasicBlock, unwind: Unwind) -> BasicBlock {
let block =
TerminatorKind::Drop { place: self.place, target, unwind: unwind.into_action() };
let block = TerminatorKind::Drop {
place: self.place,
target,
unwind: unwind.into_action(),
replace: false,
};
self.new_block(unwind, block)
}

View file

@ -479,7 +479,7 @@ impl Direction for Forward {
Goto { target } => propagate(target, exit_state),
Assert { target, unwind, expected: _, msg: _, cond: _ }
| Drop { target, unwind, place: _ }
| Drop { target, unwind, place: _, replace: _ }
| FalseUnwind { real_target: target, unwind } => {
if let UnwindAction::Cleanup(unwind) = unwind {
propagate(unwind, exit_state);

View file

@ -80,7 +80,7 @@ fn add_move_for_packed_drop<'tcx>(
is_cleanup: bool,
) {
debug!("add_move_for_packed_drop({:?} @ {:?})", terminator, loc);
let TerminatorKind::Drop { ref place, target, unwind } = terminator.kind else {
let TerminatorKind::Drop { ref place, target, unwind, replace } = terminator.kind else {
unreachable!();
};
@ -98,6 +98,11 @@ fn add_move_for_packed_drop<'tcx>(
patch.add_assign(loc, Place::from(temp), Rvalue::Use(Operand::Move(*place)));
patch.patch_terminator(
loc.block,
TerminatorKind::Drop { place: Place::from(temp), target: storage_dead_block, unwind },
TerminatorKind::Drop {
place: Place::from(temp),
target: storage_dead_block,
unwind,
replace,
},
);
}

View file

@ -14,7 +14,7 @@ use rustc_mir_dataflow::un_derefer::UnDerefer;
use rustc_mir_dataflow::MoveDataParamEnv;
use rustc_mir_dataflow::{on_all_children_bits, on_all_drop_children_bits};
use rustc_mir_dataflow::{Analysis, ResultsCursor};
use rustc_span::{DesugaringKind, Span};
use rustc_span::Span;
use rustc_target::abi::{FieldIdx, VariantIdx};
use std::fmt;
@ -401,7 +401,7 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
let terminator = data.terminator();
match terminator.kind {
TerminatorKind::Drop { mut place, target, unwind } => {
TerminatorKind::Drop { mut place, target, unwind, replace } => {
if let Some(new_place) = self.un_derefer.derefer(place.as_ref(), self.body) {
place = new_place;
}
@ -434,10 +434,7 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
)
}
LookupResult::Parent(..) => {
if !matches!(
terminator.source_info.span.desugaring_kind(),
Some(DesugaringKind::Replace),
) {
if !replace {
self.tcx.sess.delay_span_bug(
terminator.source_info.span,
format!("drop of untracked value {:?}", bb),

View file

@ -1045,7 +1045,10 @@ fn elaborate_generator_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
for (block, block_data) in body.basic_blocks.iter_enumerated() {
let (target, unwind, source_info) = match block_data.terminator() {
Terminator { source_info, kind: TerminatorKind::Drop { place, target, unwind } } => {
Terminator {
source_info,
kind: TerminatorKind::Drop { place, target, unwind, replace: _ },
} => {
if let Some(local) = place.as_local() {
if local == SELF_ARG {
(target, unwind, source_info)
@ -1304,6 +1307,7 @@ fn insert_clean_drop(body: &mut Body<'_>) -> BasicBlock {
place: Place::from(SELF_ARG),
target: return_block,
unwind: UnwindAction::Continue,
replace: false,
};
let source_info = SourceInfo::outermost(body.span);

View file

@ -449,7 +449,7 @@ impl<'tcx> Inliner<'tcx> {
checker.visit_basic_block_data(bb, blk);
let term = blk.terminator();
if let TerminatorKind::Drop { ref place, target, unwind } = term.kind {
if let TerminatorKind::Drop { ref place, target, unwind, replace: _ } = term.kind {
work_list.push(target);
// If the place doesn't actually need dropping, treat it like a regular goto.
@ -457,8 +457,8 @@ impl<'tcx> Inliner<'tcx> {
.callee
.subst_mir(self.tcx, ty::EarlyBinder(&place.ty(callee_body, tcx).ty));
if ty.needs_drop(tcx, self.param_env) && let UnwindAction::Cleanup(unwind) = unwind {
work_list.push(unwind);
}
work_list.push(unwind);
}
} else if callee_attrs.instruction_set != self.codegen_fn_attrs.instruction_set
&& matches!(term.kind, TerminatorKind::InlineAsm { .. })
{

View file

@ -544,6 +544,7 @@ impl<'tcx> CloneShimBuilder<'tcx> {
place: dest_field,
target: unwind,
unwind: UnwindAction::Terminate,
replace: false,
},
true,
);
@ -800,6 +801,7 @@ fn build_call_shim<'tcx>(
place: rcvr_place(),
target: BasicBlock::new(2),
unwind: UnwindAction::Continue,
replace: false,
},
false,
);
@ -815,6 +817,7 @@ fn build_call_shim<'tcx>(
place: rcvr_place(),
target: BasicBlock::new(4),
unwind: UnwindAction::Terminate,
replace: false,
},
true,
);

View file

@ -309,7 +309,7 @@ fn rustc_terminator_to_terminator(
Terminate => Terminator::Abort,
Return => Terminator::Return,
Unreachable => Terminator::Unreachable,
Drop { place, target, unwind } => Terminator::Drop {
Drop { place, target, unwind, replace: _ } => Terminator::Drop {
place: rustc_place_to_place(place),
target: target.as_usize(),
unwind: rustc_unwind_to_unwind(unwind),

View file

@ -1147,7 +1147,6 @@ pub enum DesugaringKind {
Await,
ForLoop,
WhileLoop,
Replace,
}
impl DesugaringKind {
@ -1163,7 +1162,6 @@ impl DesugaringKind {
DesugaringKind::OpaqueTy => "`impl Trait`",
DesugaringKind::ForLoop => "`for` loop",
DesugaringKind::WhileLoop => "`while` loop",
DesugaringKind::Replace => "drop and replace",
}
}
}

View file

@ -1,9 +1,9 @@
use crate::infer::InferCtxt;
use crate::traits::query::type_op::{self, TypeOp, TypeOpOutput};
use crate::traits::{ObligationCause, ObligationCtxt};
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::ErrorGuaranteed;
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
use rustc_infer::infer::InferOk;
use rustc_middle::infer::canonical::{OriginalQueryValues, QueryRegionConstraints};
use rustc_middle::ty::{self, ParamEnv, Ty, TypeFolder, TypeVisitableExt};
use rustc_span::def_id::LocalDefId;
@ -68,20 +68,29 @@ impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> {
return vec![];
}
let span = self.tcx.def_span(body_id);
let result: Result<_, ErrorGuaranteed> = param_env
.and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty })
.fully_perform(self, span);
let result = match result {
Ok(r) => r,
Err(_) => {
return vec![];
}
let mut canonical_var_values = OriginalQueryValues::default();
let canonical_ty =
self.canonicalize_query_keep_static(param_env.and(ty), &mut canonical_var_values);
let Ok(canonical_result) = self.tcx.implied_outlives_bounds(canonical_ty) else {
return vec![];
};
let TypeOpOutput { output, constraints, .. } = result;
let mut constraints = QueryRegionConstraints::default();
let Ok(InferOk { value, obligations }) = self
.instantiate_nll_query_response_and_region_obligations(
&ObligationCause::dummy(),
param_env,
&canonical_var_values,
canonical_result,
&mut constraints,
) else {
return vec![];
};
assert_eq!(&obligations, &[]);
if !constraints.is_empty() {
let span = self.tcx.def_span(body_id);
if let Some(constraints) = constraints {
debug!(?constraints);
if !constraints.member_constraints.is_empty() {
span_bug!(span, "{:#?}", constraints.member_constraints);
@ -108,7 +117,7 @@ impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> {
}
};
output
value
}
fn implied_bounds_tys(

View file

@ -1,6 +1,11 @@
use rustc_middle::ty::{self, Ty, TyCtxt};
use crate::traits::query::normalize::QueryNormalizeExt;
use crate::traits::query::NoSolution;
use crate::traits::{Normalized, ObligationCause, ObligationCtxt};
pub use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
use rustc_middle::ty::{self, EarlyBinder, ParamEnvAnd, Ty, TyCtxt};
use rustc_span::source_map::{Span, DUMMY_SP};
/// This returns true if the type `ty` is "trivial" for
/// dropck-outlives -- that is, if it doesn't require any types to
@ -71,3 +76,263 @@ pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
| ty::Generator(..) => false,
}
}
pub fn compute_dropck_outlives_inner<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
goal: ParamEnvAnd<'tcx, Ty<'tcx>>,
) -> Result<DropckOutlivesResult<'tcx>, NoSolution> {
let tcx = ocx.infcx.tcx;
let ParamEnvAnd { param_env, value: for_ty } = goal;
let mut result = DropckOutlivesResult { kinds: vec![], overflows: vec![] };
// A stack of types left to process. Each round, we pop
// something from the stack and invoke
// `dtorck_constraint_for_ty_inner`. This may produce new types that
// have to be pushed on the stack. This continues until we have explored
// all the reachable types from the type `for_ty`.
//
// Example: Imagine that we have the following code:
//
// ```rust
// struct A {
// value: B,
// children: Vec<A>,
// }
//
// struct B {
// value: u32
// }
//
// fn f() {
// let a: A = ...;
// ..
// } // here, `a` is dropped
// ```
//
// at the point where `a` is dropped, we need to figure out
// which types inside of `a` contain region data that may be
// accessed by any destructors in `a`. We begin by pushing `A`
// onto the stack, as that is the type of `a`. We will then
// invoke `dtorck_constraint_for_ty_inner` which will expand `A`
// into the types of its fields `(B, Vec<A>)`. These will get
// pushed onto the stack. Eventually, expanding `Vec<A>` will
// lead to us trying to push `A` a second time -- to prevent
// infinite recursion, we notice that `A` was already pushed
// once and stop.
let mut ty_stack = vec![(for_ty, 0)];
// Set used to detect infinite recursion.
let mut ty_set = FxHashSet::default();
let cause = ObligationCause::dummy();
let mut constraints = DropckConstraint::empty();
while let Some((ty, depth)) = ty_stack.pop() {
debug!(
"{} kinds, {} overflows, {} ty_stack",
result.kinds.len(),
result.overflows.len(),
ty_stack.len()
);
dtorck_constraint_for_ty_inner(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?;
// "outlives" represent types/regions that may be touched
// by a destructor.
result.kinds.append(&mut constraints.outlives);
result.overflows.append(&mut constraints.overflows);
// If we have even one overflow, we should stop trying to evaluate further --
// chances are, the subsequent overflows for this evaluation won't provide useful
// information and will just decrease the speed at which we can emit these errors
// (since we'll be printing for just that much longer for the often enormous types
// that result here).
if !result.overflows.is_empty() {
break;
}
// dtorck types are "types that will get dropped but which
// do not themselves define a destructor", more or less. We have
// to push them onto the stack to be expanded.
for ty in constraints.dtorck_types.drain(..) {
let Normalized { value: ty, obligations } =
ocx.infcx.at(&cause, param_env).query_normalize(ty)?;
ocx.register_obligations(obligations);
debug!("dropck_outlives: ty from dtorck_types = {:?}", ty);
match ty.kind() {
// All parameters live for the duration of the
// function.
ty::Param(..) => {}
// A projection that we couldn't resolve - it
// might have a destructor.
ty::Alias(..) => {
result.kinds.push(ty.into());
}
_ => {
if ty_set.insert(ty) {
ty_stack.push((ty, depth + 1));
}
}
}
}
}
debug!("dropck_outlives: result = {:#?}", result);
Ok(result)
}
/// Returns a set of constraints that needs to be satisfied in
/// order for `ty` to be valid for destruction.
pub fn dtorck_constraint_for_ty_inner<'tcx>(
tcx: TyCtxt<'tcx>,
span: Span,
for_ty: Ty<'tcx>,
depth: usize,
ty: Ty<'tcx>,
constraints: &mut DropckConstraint<'tcx>,
) -> Result<(), NoSolution> {
debug!("dtorck_constraint_for_ty_inner({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty);
if !tcx.recursion_limit().value_within_limit(depth) {
constraints.overflows.push(ty);
return Ok(());
}
if trivial_dropck_outlives(tcx, ty) {
return Ok(());
}
match ty.kind() {
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Str
| ty::Never
| ty::Foreign(..)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(_)
| ty::GeneratorWitness(..)
| ty::GeneratorWitnessMIR(..) => {
// these types never have a destructor
}
ty::Array(ety, _) | ty::Slice(ety) => {
// single-element containers, behave like their element
rustc_data_structures::stack::ensure_sufficient_stack(|| {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, *ety, constraints)
})?;
}
ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in tys.iter() {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
}
Ok::<_, NoSolution>(())
})?,
ty::Closure(_, substs) => {
if !substs.as_closure().is_valid() {
// By the time this code runs, all type variables ought to
// be fully resolved.
tcx.sess.delay_span_bug(
span,
format!("upvar_tys for closure not found. Expected capture information for closure {ty}",),
);
return Err(NoSolution);
}
rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in substs.as_closure().upvar_tys() {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
}
Ok::<_, NoSolution>(())
})?
}
ty::Generator(_, substs, _movability) => {
// rust-lang/rust#49918: types can be constructed, stored
// in the interior, and sit idle when generator yields
// (and is subsequently dropped).
//
// It would be nice to descend into interior of a
// generator to determine what effects dropping it might
// have (by looking at any drop effects associated with
// its interior).
//
// However, the interior's representation uses things like
// GeneratorWitness that explicitly assume they are not
// traversed in such a manner. So instead, we will
// simplify things for now by treating all generators as
// if they were like trait objects, where its upvars must
// all be alive for the generator's (potential)
// destructor.
//
// In particular, skipping over `_interior` is safe
// because any side-effects from dropping `_interior` can
// only take place through references with lifetimes
// derived from lifetimes attached to the upvars and resume
// argument, and we *do* incorporate those here.
if !substs.as_generator().is_valid() {
// By the time this code runs, all type variables ought to
// be fully resolved.
tcx.sess.delay_span_bug(
span,
format!("upvar_tys for generator not found. Expected capture information for generator {ty}",),
);
return Err(NoSolution);
}
constraints.outlives.extend(
substs
.as_generator()
.upvar_tys()
.map(|t| -> ty::subst::GenericArg<'tcx> { t.into() }),
);
constraints.outlives.push(substs.as_generator().resume_ty().into());
}
ty::Adt(def, substs) => {
let DropckConstraint { dtorck_types, outlives, overflows } =
tcx.at(span).adt_dtorck_constraint(def.did())?;
// FIXME: we can try to recursively `dtorck_constraint_on_ty`
// there, but that needs some way to handle cycles.
constraints
.dtorck_types
.extend(dtorck_types.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
constraints
.outlives
.extend(outlives.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
constraints
.overflows
.extend(overflows.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
}
// Objects must be alive in order for their destructor
// to be called.
ty::Dynamic(..) => {
constraints.outlives.push(ty.into());
}
// Types that can't be resolved. Pass them forward.
ty::Alias(..) | ty::Param(..) => {
constraints.dtorck_types.push(ty);
}
ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(_) => {
// By the time this code runs, all type variables ought to
// be fully resolved.
return Err(NoSolution);
}
}
Ok(())
}

View file

@ -1,8 +1,13 @@
use crate::infer::canonical::{Canonical, CanonicalQueryResponse};
use crate::traits::ObligationCtxt;
use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
use rustc_infer::traits::Obligation;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::{ParamEnvAnd, TyCtxt};
use rustc_middle::traits::{ObligationCause, ObligationCauseCode};
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, UserSelfTy, UserSubsts, UserType};
pub use rustc_middle::traits::query::type_op::AscribeUserType;
use rustc_span::{Span, DUMMY_SP};
impl<'tcx> super::QueryTypeOp<'tcx> for AscribeUserType<'tcx> {
type QueryResponse = ();
@ -20,4 +25,116 @@ impl<'tcx> super::QueryTypeOp<'tcx> for AscribeUserType<'tcx> {
) -> Result<CanonicalQueryResponse<'tcx, ()>, NoSolution> {
tcx.type_op_ascribe_user_type(canonicalized)
}
fn perform_locally_in_new_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution> {
type_op_ascribe_user_type_with_span(ocx, key, None)
}
}
/// The core of the `type_op_ascribe_user_type` query: for diagnostics purposes in NLL HRTB errors,
/// this query can be re-run to better track the span of the obligation cause, and improve the error
/// message. Do not call directly unless you're in that very specific context.
pub fn type_op_ascribe_user_type_with_span<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, AscribeUserType<'tcx>>,
span: Option<Span>,
) -> Result<(), NoSolution> {
let (param_env, AscribeUserType { mir_ty, user_ty }) = key.into_parts();
debug!("type_op_ascribe_user_type: mir_ty={:?} user_ty={:?}", mir_ty, user_ty);
let span = span.unwrap_or(DUMMY_SP);
match user_ty {
UserType::Ty(user_ty) => relate_mir_and_user_ty(ocx, param_env, span, mir_ty, user_ty)?,
UserType::TypeOf(def_id, user_substs) => {
relate_mir_and_user_substs(ocx, param_env, span, mir_ty, def_id, user_substs)?
}
};
Ok(())
}
#[instrument(level = "debug", skip(ocx, param_env, span))]
fn relate_mir_and_user_ty<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
span: Span,
mir_ty: Ty<'tcx>,
user_ty: Ty<'tcx>,
) -> Result<(), NoSolution> {
let cause = ObligationCause::dummy_with_span(span);
let user_ty = ocx.normalize(&cause, param_env, user_ty);
ocx.eq(&cause, param_env, mir_ty, user_ty)?;
// FIXME(#104764): We should check well-formedness before normalization.
let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(user_ty.into()));
ocx.register_obligation(Obligation::new(ocx.infcx.tcx, cause, param_env, predicate));
Ok(())
}
#[instrument(level = "debug", skip(ocx, param_env, span))]
fn relate_mir_and_user_substs<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
span: Span,
mir_ty: Ty<'tcx>,
def_id: DefId,
user_substs: UserSubsts<'tcx>,
) -> Result<(), NoSolution> {
let param_env = param_env.without_const();
let UserSubsts { user_self_ty, substs } = user_substs;
let tcx = ocx.infcx.tcx;
let cause = ObligationCause::dummy_with_span(span);
let ty = tcx.type_of(def_id).subst(tcx, substs);
let ty = ocx.normalize(&cause, param_env, ty);
debug!("relate_type_and_user_type: ty of def-id is {:?}", ty);
ocx.eq(&cause, param_env, mir_ty, ty)?;
// Prove the predicates coming along with `def_id`.
//
// Also, normalize the `instantiated_predicates`
// because otherwise we wind up with duplicate "type
// outlives" error messages.
let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, substs);
debug!(?instantiated_predicates);
for (instantiated_predicate, predicate_span) in instantiated_predicates {
let span = if span == DUMMY_SP { predicate_span } else { span };
let cause = ObligationCause::new(
span,
CRATE_DEF_ID,
ObligationCauseCode::AscribeUserTypeProvePredicate(predicate_span),
);
let instantiated_predicate =
ocx.normalize(&cause.clone(), param_env, instantiated_predicate);
ocx.register_obligation(Obligation::new(tcx, cause, param_env, instantiated_predicate));
}
if let Some(UserSelfTy { impl_def_id, self_ty }) = user_self_ty {
let self_ty = ocx.normalize(&cause, param_env, self_ty);
let impl_self_ty = tcx.type_of(impl_def_id).subst(tcx, substs);
let impl_self_ty = ocx.normalize(&cause, param_env, impl_self_ty);
ocx.eq(&cause, param_env, self_ty, impl_self_ty)?;
let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(impl_self_ty.into()));
ocx.register_obligation(Obligation::new(tcx, cause.clone(), param_env, predicate));
}
// In addition to proving the predicates, we have to
// prove that `ty` is well-formed -- this is because
// the WF of `ty` is predicated on the substs being
// well-formed, and we haven't proven *that*. We don't
// want to prove the WF of types from `substs` directly because they
// haven't been normalized.
//
// FIXME(nmatsakis): Well, perhaps we should normalize
// them? This would only be relevant if some input
// type were ill-formed but did not appear in `ty`,
// which...could happen with normalization...
let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(ty.into()));
ocx.register_obligation(Obligation::new(tcx, cause, param_env, predicate));
Ok(())
}

View file

@ -1,5 +1,7 @@
use crate::infer::canonical::{Canonical, CanonicalQueryResponse};
use crate::traits::ObligationCtxt;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::{ParamEnvAnd, TyCtxt};
pub use rustc_middle::traits::query::type_op::Eq;
@ -20,4 +22,12 @@ impl<'tcx> super::QueryTypeOp<'tcx> for Eq<'tcx> {
) -> Result<CanonicalQueryResponse<'tcx, ()>, NoSolution> {
tcx.type_op_eq(canonicalized)
}
fn perform_locally_in_new_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution> {
ocx.eq(&ObligationCause::dummy(), key.param_env, key.value.a, key.value.b)?;
Ok(())
}
}

View file

@ -1,7 +1,15 @@
use crate::infer::canonical::{Canonical, CanonicalQueryResponse};
use crate::traits::query::NoSolution;
use crate::traits::wf;
use crate::traits::ObligationCtxt;
use rustc_infer::infer::canonical::Canonical;
use rustc_infer::infer::outlives::components::{push_outlives_components, Component};
use rustc_infer::traits::query::OutlivesBound;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt};
use rustc_middle::infer::canonical::CanonicalQueryResponse;
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::source_map::DUMMY_SP;
use smallvec::{smallvec, SmallVec};
#[derive(Copy, Clone, Debug, HashStable, TypeFoldable, TypeVisitable, Lift)]
pub struct ImpliedOutlivesBounds<'tcx> {
@ -39,4 +47,169 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
tcx.implied_outlives_bounds(canonicalized)
}
fn perform_locally_in_new_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution> {
compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty)
}
}
pub fn compute_implied_outlives_bounds_inner<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
let tcx = ocx.infcx.tcx;
// Sometimes when we ask what it takes for T: WF, we get back that
// U: WF is required; in that case, we push U onto this stack and
// process it next. Because the resulting predicates aren't always
// guaranteed to be a subset of the original type, so we need to store the
// WF args we've computed in a set.
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
let mut wf_args = vec![ty.into()];
let mut outlives_bounds: Vec<ty::OutlivesPredicate<ty::GenericArg<'tcx>, ty::Region<'tcx>>> =
vec![];
while let Some(arg) = wf_args.pop() {
if !checked_wf_args.insert(arg) {
continue;
}
// Compute the obligations for `arg` to be well-formed. If `arg` is
// an unresolved inference variable, just substituted an empty set
// -- because the return type here is going to be things we *add*
// to the environment, it's always ok for this set to be smaller
// than the ultimate set. (Note: normally there won't be
// unresolved inference variables here anyway, but there might be
// during typeck under some circumstances.)
//
// FIXME(@lcnr): It's not really "always fine", having fewer implied
// bounds can be backward incompatible, e.g. #101951 was caused by
// us not dealing with inference vars in `TypeOutlives` predicates.
let obligations = wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, DUMMY_SP)
.unwrap_or_default();
for obligation in obligations {
debug!(?obligation);
assert!(!obligation.has_escaping_bound_vars());
// While these predicates should all be implied by other parts of
// the program, they are still relevant as they may constrain
// inference variables, which is necessary to add the correct
// implied bounds in some cases, mostly when dealing with projections.
//
// Another important point here: we only register `Projection`
// predicates, since otherwise we might register outlives
// predicates containing inference variables, and we don't
// learn anything new from those.
if obligation.predicate.has_non_region_infer() {
match obligation.predicate.kind().skip_binder() {
ty::PredicateKind::Clause(ty::Clause::Projection(..))
| ty::PredicateKind::AliasRelate(..) => {
ocx.register_obligation(obligation.clone());
}
_ => {}
}
}
let pred = match obligation.predicate.kind().no_bound_vars() {
None => continue,
Some(pred) => pred,
};
match pred {
ty::PredicateKind::Clause(ty::Clause::Trait(..))
// FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound
// if we ever support that
| ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..))
| ty::PredicateKind::Subtype(..)
| ty::PredicateKind::Coerce(..)
| ty::PredicateKind::Clause(ty::Clause::Projection(..))
| ty::PredicateKind::ClosureKind(..)
| ty::PredicateKind::ObjectSafe(..)
| ty::PredicateKind::ConstEvaluatable(..)
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::Ambiguous
| ty::PredicateKind::AliasRelate(..)
| ty::PredicateKind::TypeWellFormedFromEnv(..) => {}
// We need to search through *all* WellFormed predicates
ty::PredicateKind::WellFormed(arg) => {
wf_args.push(arg);
}
// We need to register region relationships
ty::PredicateKind::Clause(ty::Clause::RegionOutlives(ty::OutlivesPredicate(
r_a,
r_b,
))) => outlives_bounds.push(ty::OutlivesPredicate(r_a.into(), r_b)),
ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate(
ty_a,
r_b,
))) => outlives_bounds.push(ty::OutlivesPredicate(ty_a.into(), r_b)),
}
}
}
// This call to `select_all_or_error` is necessary to constrain inference variables, which we
// use further down when computing the implied bounds.
match ocx.select_all_or_error().as_slice() {
[] => (),
_ => return Err(NoSolution),
}
// We lazily compute the outlives components as
// `select_all_or_error` constrains inference variables.
let implied_bounds = outlives_bounds
.into_iter()
.flat_map(|ty::OutlivesPredicate(a, r_b)| match a.unpack() {
ty::GenericArgKind::Lifetime(r_a) => vec![OutlivesBound::RegionSubRegion(r_b, r_a)],
ty::GenericArgKind::Type(ty_a) => {
let ty_a = ocx.infcx.resolve_vars_if_possible(ty_a);
let mut components = smallvec![];
push_outlives_components(tcx, ty_a, &mut components);
implied_bounds_from_components(r_b, components)
}
ty::GenericArgKind::Const(_) => unreachable!(),
})
.collect();
Ok(implied_bounds)
}
/// When we have an implied bound that `T: 'a`, we can further break
/// this down to determine what relationships would have to hold for
/// `T: 'a` to hold. We get to assume that the caller has validated
/// those relationships.
fn implied_bounds_from_components<'tcx>(
sub_region: ty::Region<'tcx>,
sup_components: SmallVec<[Component<'tcx>; 4]>,
) -> Vec<OutlivesBound<'tcx>> {
sup_components
.into_iter()
.filter_map(|component| {
match component {
Component::Region(r) => Some(OutlivesBound::RegionSubRegion(sub_region, r)),
Component::Param(p) => Some(OutlivesBound::RegionSubParam(sub_region, p)),
Component::Alias(p) => Some(OutlivesBound::RegionSubAlias(sub_region, p)),
Component::EscapingAlias(_) =>
// If the projection has escaping regions, don't
// try to infer any implied bounds even for its
// free components. This is conservative, because
// the caller will still have to prove that those
// free components outlive `sub_region`. But the
// idea is that the WAY that the caller proves
// that may change in the future and we want to
// give ourselves room to get smarter here.
{
None
}
Component::UnresolvedInferenceVariable(..) => None,
}
})
.collect()
}

View file

@ -2,7 +2,7 @@ use crate::infer::canonical::{
Canonical, CanonicalQueryResponse, OriginalQueryValues, QueryRegionConstraints,
};
use crate::infer::{InferCtxt, InferOk};
use crate::traits::ObligationCause;
use crate::traits::{ObligationCause, ObligationCtxt};
use rustc_errors::ErrorGuaranteed;
use rustc_infer::infer::canonical::Certainty;
use rustc_infer::traits::PredicateObligations;
@ -23,6 +23,8 @@ pub mod subtype;
pub use rustc_middle::traits::query::type_op::*;
use self::custom::scrape_region_constraints;
/// "Type ops" are used in NLL to perform some particular action and
/// extract out the resulting region constraints (or an error if it
/// cannot be completed).
@ -81,6 +83,17 @@ pub trait QueryTypeOp<'tcx>: fmt::Debug + Copy + TypeFoldable<TyCtxt<'tcx>> + 't
canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>,
) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution>;
/// In the new trait solver, we already do caching in the solver itself,
/// so there's no need to canonicalize and cache via the query system.
/// Additionally, even if we were to canonicalize, we'd still need to
/// make sure to feed it predefined opaque types and the defining anchor
/// and that would require duplicating all of the tcx queries. Instead,
/// just perform these ops locally.
fn perform_locally_in_new_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution>;
fn fully_perform_into(
query_key: ParamEnvAnd<'tcx, Self>,
infcx: &InferCtxt<'tcx>,
@ -133,6 +146,16 @@ where
infcx: &InferCtxt<'tcx>,
span: Span,
) -> Result<TypeOpOutput<'tcx, Self>, ErrorGuaranteed> {
if infcx.tcx.trait_solver_next() {
return Ok(scrape_region_constraints(
infcx,
|ocx| QueryTypeOp::perform_locally_in_new_solver(ocx, self),
"query type op",
span,
)?
.0);
}
let mut region_constraints = QueryRegionConstraints::default();
let (output, error_info, mut obligations, _) =
Q::fully_perform_into(self, infcx, &mut region_constraints).map_err(|_| {

View file

@ -1,5 +1,7 @@
use crate::infer::canonical::{Canonical, CanonicalQueryResponse};
use crate::traits::ObligationCtxt;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::{self, Lift, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
use std::fmt;
@ -22,6 +24,14 @@ where
) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution> {
T::type_op_method(tcx, canonicalized)
}
fn perform_locally_in_new_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution> {
// FIXME(-Ztrait-solver=next): shouldn't be using old normalizer
Ok(ocx.normalize(&ObligationCause::dummy(), key.param_env, key.value.value))
}
}
pub trait Normalizable<'tcx>: fmt::Debug + TypeFoldable<TyCtxt<'tcx>> + Lift<'tcx> + Copy {

View file

@ -1,6 +1,9 @@
use crate::infer::canonical::{Canonical, CanonicalQueryResponse};
use crate::traits::query::dropck_outlives::{trivial_dropck_outlives, DropckOutlivesResult};
use rustc_middle::traits::query::NoSolution;
use crate::traits::query::dropck_outlives::{
compute_dropck_outlives_inner, trivial_dropck_outlives,
};
use crate::traits::ObligationCtxt;
use rustc_middle::traits::query::{DropckOutlivesResult, NoSolution};
use rustc_middle::ty::{ParamEnvAnd, Ty, TyCtxt};
#[derive(Copy, Clone, Debug, HashStable, TypeFoldable, TypeVisitable, Lift)]
@ -48,4 +51,11 @@ impl<'tcx> super::QueryTypeOp<'tcx> for DropckOutlives<'tcx> {
tcx.dropck_outlives(canonicalized)
}
fn perform_locally_in_new_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution> {
compute_dropck_outlives_inner(ocx, key.param_env.and(key.value.dropped_ty))
}
}

View file

@ -1,5 +1,8 @@
use crate::infer::canonical::{Canonical, CanonicalQueryResponse};
use crate::traits::ObligationCtxt;
use rustc_infer::traits::Obligation;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::{self, ParamEnvAnd, TyCtxt};
pub use rustc_middle::traits::query::type_op::ProvePredicate;
@ -36,4 +39,17 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ProvePredicate<'tcx> {
) -> Result<CanonicalQueryResponse<'tcx, ()>, NoSolution> {
tcx.type_op_prove_predicate(canonicalized)
}
fn perform_locally_in_new_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution> {
ocx.register_obligation(Obligation::new(
ocx.infcx.tcx,
ObligationCause::dummy(),
key.param_env,
key.value.predicate,
));
Ok(())
}
}

View file

@ -1,5 +1,7 @@
use crate::infer::canonical::{Canonical, CanonicalQueryResponse};
use crate::traits::ObligationCtxt;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::{ParamEnvAnd, TyCtxt};
pub use rustc_middle::traits::query::type_op::Subtype;
@ -17,4 +19,12 @@ impl<'tcx> super::QueryTypeOp<'tcx> for Subtype<'tcx> {
) -> Result<CanonicalQueryResponse<'tcx, ()>, NoSolution> {
tcx.type_op_subtype(canonicalized)
}
fn perform_locally_in_new_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution> {
ocx.sub(&ObligationCause::dummy(), key.param_env, key.value.sub, key.value.sup)?;
Ok(())
}
}

View file

@ -3,17 +3,14 @@ use rustc_hir::def_id::DefId;
use rustc_infer::infer::canonical::{Canonical, QueryResponse};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::query::Providers;
use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult};
use rustc_middle::ty::InternalSubsts;
use rustc_middle::ty::{self, EarlyBinder, ParamEnvAnd, Ty, TyCtxt};
use rustc_span::source_map::{Span, DUMMY_SP};
use rustc_middle::ty::TyCtxt;
use rustc_trait_selection::infer::InferCtxtBuilderExt;
use rustc_trait_selection::traits::query::dropck_outlives::trivial_dropck_outlives;
use rustc_trait_selection::traits::query::dropck_outlives::{
DropckConstraint, DropckOutlivesResult,
compute_dropck_outlives_inner, dtorck_constraint_for_ty_inner,
};
use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
use rustc_trait_selection::traits::query::{CanonicalTyGoal, NoSolution};
use rustc_trait_selection::traits::{Normalized, ObligationCause};
pub(crate) fn provide(p: &mut Providers) {
*p = Providers { dropck_outlives, adt_dtorck_constraint, ..*p };
@ -26,263 +23,10 @@ fn dropck_outlives<'tcx>(
debug!("dropck_outlives(goal={:#?})", canonical_goal);
tcx.infer_ctxt().enter_canonical_trait_query(&canonical_goal, |ocx, goal| {
let tcx = ocx.infcx.tcx;
let ParamEnvAnd { param_env, value: for_ty } = goal;
let mut result = DropckOutlivesResult { kinds: vec![], overflows: vec![] };
// A stack of types left to process. Each round, we pop
// something from the stack and invoke
// `dtorck_constraint_for_ty`. This may produce new types that
// have to be pushed on the stack. This continues until we have explored
// all the reachable types from the type `for_ty`.
//
// Example: Imagine that we have the following code:
//
// ```rust
// struct A {
// value: B,
// children: Vec<A>,
// }
//
// struct B {
// value: u32
// }
//
// fn f() {
// let a: A = ...;
// ..
// } // here, `a` is dropped
// ```
//
// at the point where `a` is dropped, we need to figure out
// which types inside of `a` contain region data that may be
// accessed by any destructors in `a`. We begin by pushing `A`
// onto the stack, as that is the type of `a`. We will then
// invoke `dtorck_constraint_for_ty` which will expand `A`
// into the types of its fields `(B, Vec<A>)`. These will get
// pushed onto the stack. Eventually, expanding `Vec<A>` will
// lead to us trying to push `A` a second time -- to prevent
// infinite recursion, we notice that `A` was already pushed
// once and stop.
let mut ty_stack = vec![(for_ty, 0)];
// Set used to detect infinite recursion.
let mut ty_set = FxHashSet::default();
let cause = ObligationCause::dummy();
let mut constraints = DropckConstraint::empty();
while let Some((ty, depth)) = ty_stack.pop() {
debug!(
"{} kinds, {} overflows, {} ty_stack",
result.kinds.len(),
result.overflows.len(),
ty_stack.len()
);
dtorck_constraint_for_ty(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?;
// "outlives" represent types/regions that may be touched
// by a destructor.
result.kinds.append(&mut constraints.outlives);
result.overflows.append(&mut constraints.overflows);
// If we have even one overflow, we should stop trying to evaluate further --
// chances are, the subsequent overflows for this evaluation won't provide useful
// information and will just decrease the speed at which we can emit these errors
// (since we'll be printing for just that much longer for the often enormous types
// that result here).
if !result.overflows.is_empty() {
break;
}
// dtorck types are "types that will get dropped but which
// do not themselves define a destructor", more or less. We have
// to push them onto the stack to be expanded.
for ty in constraints.dtorck_types.drain(..) {
let Normalized { value: ty, obligations } =
ocx.infcx.at(&cause, param_env).query_normalize(ty)?;
ocx.register_obligations(obligations);
debug!("dropck_outlives: ty from dtorck_types = {:?}", ty);
match ty.kind() {
// All parameters live for the duration of the
// function.
ty::Param(..) => {}
// A projection that we couldn't resolve - it
// might have a destructor.
ty::Alias(..) => {
result.kinds.push(ty.into());
}
_ => {
if ty_set.insert(ty) {
ty_stack.push((ty, depth + 1));
}
}
}
}
}
debug!("dropck_outlives: result = {:#?}", result);
Ok(result)
compute_dropck_outlives_inner(ocx, goal)
})
}
/// Returns a set of constraints that needs to be satisfied in
/// order for `ty` to be valid for destruction.
fn dtorck_constraint_for_ty<'tcx>(
tcx: TyCtxt<'tcx>,
span: Span,
for_ty: Ty<'tcx>,
depth: usize,
ty: Ty<'tcx>,
constraints: &mut DropckConstraint<'tcx>,
) -> Result<(), NoSolution> {
debug!("dtorck_constraint_for_ty({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty);
if !tcx.recursion_limit().value_within_limit(depth) {
constraints.overflows.push(ty);
return Ok(());
}
if trivial_dropck_outlives(tcx, ty) {
return Ok(());
}
match ty.kind() {
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Str
| ty::Never
| ty::Foreign(..)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(_)
| ty::GeneratorWitness(..)
| ty::GeneratorWitnessMIR(..) => {
// these types never have a destructor
}
ty::Array(ety, _) | ty::Slice(ety) => {
// single-element containers, behave like their element
rustc_data_structures::stack::ensure_sufficient_stack(|| {
dtorck_constraint_for_ty(tcx, span, for_ty, depth + 1, *ety, constraints)
})?;
}
ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in tys.iter() {
dtorck_constraint_for_ty(tcx, span, for_ty, depth + 1, ty, constraints)?;
}
Ok::<_, NoSolution>(())
})?,
ty::Closure(_, substs) => {
if !substs.as_closure().is_valid() {
// By the time this code runs, all type variables ought to
// be fully resolved.
tcx.sess.delay_span_bug(
span,
format!("upvar_tys for closure not found. Expected capture information for closure {ty}",),
);
return Err(NoSolution);
}
rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in substs.as_closure().upvar_tys() {
dtorck_constraint_for_ty(tcx, span, for_ty, depth + 1, ty, constraints)?;
}
Ok::<_, NoSolution>(())
})?
}
ty::Generator(_, substs, _movability) => {
// rust-lang/rust#49918: types can be constructed, stored
// in the interior, and sit idle when generator yields
// (and is subsequently dropped).
//
// It would be nice to descend into interior of a
// generator to determine what effects dropping it might
// have (by looking at any drop effects associated with
// its interior).
//
// However, the interior's representation uses things like
// GeneratorWitness that explicitly assume they are not
// traversed in such a manner. So instead, we will
// simplify things for now by treating all generators as
// if they were like trait objects, where its upvars must
// all be alive for the generator's (potential)
// destructor.
//
// In particular, skipping over `_interior` is safe
// because any side-effects from dropping `_interior` can
// only take place through references with lifetimes
// derived from lifetimes attached to the upvars and resume
// argument, and we *do* incorporate those here.
if !substs.as_generator().is_valid() {
// By the time this code runs, all type variables ought to
// be fully resolved.
tcx.sess.delay_span_bug(
span,
format!("upvar_tys for generator not found. Expected capture information for generator {ty}",),
);
return Err(NoSolution);
}
constraints.outlives.extend(
substs
.as_generator()
.upvar_tys()
.map(|t| -> ty::subst::GenericArg<'tcx> { t.into() }),
);
constraints.outlives.push(substs.as_generator().resume_ty().into());
}
ty::Adt(def, substs) => {
let DropckConstraint { dtorck_types, outlives, overflows } =
tcx.at(span).adt_dtorck_constraint(def.did())?;
// FIXME: we can try to recursively `dtorck_constraint_on_ty`
// there, but that needs some way to handle cycles.
constraints
.dtorck_types
.extend(dtorck_types.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
constraints
.outlives
.extend(outlives.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
constraints
.overflows
.extend(overflows.iter().map(|t| EarlyBinder(*t).subst(tcx, substs)));
}
// Objects must be alive in order for their destructor
// to be called.
ty::Dynamic(..) => {
constraints.outlives.push(ty.into());
}
// Types that can't be resolved. Pass them forward.
ty::Alias(..) | ty::Param(..) => {
constraints.dtorck_types.push(ty);
}
ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(_) => {
// By the time this code runs, all type variables ought to
// be fully resolved.
return Err(NoSolution);
}
}
Ok(())
}
/// Calculates the dtorck constraint for a type.
pub(crate) fn adt_dtorck_constraint(
tcx: TyCtxt<'_>,
@ -311,7 +55,7 @@ pub(crate) fn adt_dtorck_constraint(
let mut result = DropckConstraint::empty();
for field in def.all_fields() {
let fty = tcx.type_of(field.did).subst_identity();
dtorck_constraint_for_ty(tcx, span, fty, 0, fty, &mut result)?;
dtorck_constraint_for_ty_inner(tcx, span, fty, 0, fty, &mut result)?;
}
result.outlives.extend(tcx.destructor_constraints(def));
dedup_dtorck_constraint(&mut result);

View file

@ -3,18 +3,13 @@
//! [`rustc_trait_selection::traits::query::type_op::implied_outlives_bounds`].
use rustc_infer::infer::canonical::{self, Canonical};
use rustc_infer::infer::outlives::components::{push_outlives_components, Component};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::query::OutlivesBound;
use rustc_middle::query::Providers;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::source_map::DUMMY_SP;
use rustc_middle::ty::TyCtxt;
use rustc_trait_selection::infer::InferCtxtBuilderExt;
use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::compute_implied_outlives_bounds_inner;
use rustc_trait_selection::traits::query::{CanonicalTyGoal, NoSolution};
use rustc_trait_selection::traits::wf;
use rustc_trait_selection::traits::ObligationCtxt;
use smallvec::{smallvec, SmallVec};
pub(crate) fn provide(p: &mut Providers) {
*p = Providers { implied_outlives_bounds, ..*p };
@ -29,164 +24,6 @@ fn implied_outlives_bounds<'tcx>(
> {
tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| {
let (param_env, ty) = key.into_parts();
compute_implied_outlives_bounds(ocx, param_env, ty)
compute_implied_outlives_bounds_inner(ocx, param_env, ty)
})
}
fn compute_implied_outlives_bounds<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
let tcx = ocx.infcx.tcx;
// Sometimes when we ask what it takes for T: WF, we get back that
// U: WF is required; in that case, we push U onto this stack and
// process it next. Because the resulting predicates aren't always
// guaranteed to be a subset of the original type, so we need to store the
// WF args we've computed in a set.
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
let mut wf_args = vec![ty.into()];
let mut outlives_bounds: Vec<ty::OutlivesPredicate<ty::GenericArg<'tcx>, ty::Region<'tcx>>> =
vec![];
while let Some(arg) = wf_args.pop() {
if !checked_wf_args.insert(arg) {
continue;
}
// Compute the obligations for `arg` to be well-formed. If `arg` is
// an unresolved inference variable, just substituted an empty set
// -- because the return type here is going to be things we *add*
// to the environment, it's always ok for this set to be smaller
// than the ultimate set. (Note: normally there won't be
// unresolved inference variables here anyway, but there might be
// during typeck under some circumstances.)
//
// FIXME(@lcnr): It's not really "always fine", having fewer implied
// bounds can be backward incompatible, e.g. #101951 was caused by
// us not dealing with inference vars in `TypeOutlives` predicates.
let obligations = wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, DUMMY_SP)
.unwrap_or_default();
for obligation in obligations {
debug!(?obligation);
assert!(!obligation.has_escaping_bound_vars());
// While these predicates should all be implied by other parts of
// the program, they are still relevant as they may constrain
// inference variables, which is necessary to add the correct
// implied bounds in some cases, mostly when dealing with projections.
//
// Another important point here: we only register `Projection`
// predicates, since otherwise we might register outlives
// predicates containing inference variables, and we don't
// learn anything new from those.
if obligation.predicate.has_non_region_infer() {
match obligation.predicate.kind().skip_binder() {
ty::PredicateKind::Clause(ty::Clause::Projection(..))
| ty::PredicateKind::AliasRelate(..) => {
ocx.register_obligation(obligation.clone());
}
_ => {}
}
}
let pred = match obligation.predicate.kind().no_bound_vars() {
None => continue,
Some(pred) => pred,
};
match pred {
ty::PredicateKind::Clause(ty::Clause::Trait(..))
// FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound
// if we ever support that
| ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..))
| ty::PredicateKind::Subtype(..)
| ty::PredicateKind::Coerce(..)
| ty::PredicateKind::Clause(ty::Clause::Projection(..))
| ty::PredicateKind::ClosureKind(..)
| ty::PredicateKind::ObjectSafe(..)
| ty::PredicateKind::ConstEvaluatable(..)
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::Ambiguous
| ty::PredicateKind::AliasRelate(..)
| ty::PredicateKind::TypeWellFormedFromEnv(..) => {}
// We need to search through *all* WellFormed predicates
ty::PredicateKind::WellFormed(arg) => {
wf_args.push(arg);
}
// We need to register region relationships
ty::PredicateKind::Clause(ty::Clause::RegionOutlives(ty::OutlivesPredicate(
r_a,
r_b,
))) => outlives_bounds.push(ty::OutlivesPredicate(r_a.into(), r_b)),
ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate(
ty_a,
r_b,
))) => outlives_bounds.push(ty::OutlivesPredicate(ty_a.into(), r_b)),
}
}
}
// This call to `select_all_or_error` is necessary to constrain inference variables, which we
// use further down when computing the implied bounds.
match ocx.select_all_or_error().as_slice() {
[] => (),
_ => return Err(NoSolution),
}
// We lazily compute the outlives components as
// `select_all_or_error` constrains inference variables.
let implied_bounds = outlives_bounds
.into_iter()
.flat_map(|ty::OutlivesPredicate(a, r_b)| match a.unpack() {
ty::GenericArgKind::Lifetime(r_a) => vec![OutlivesBound::RegionSubRegion(r_b, r_a)],
ty::GenericArgKind::Type(ty_a) => {
let ty_a = ocx.infcx.resolve_vars_if_possible(ty_a);
let mut components = smallvec![];
push_outlives_components(tcx, ty_a, &mut components);
implied_bounds_from_components(r_b, components)
}
ty::GenericArgKind::Const(_) => unreachable!(),
})
.collect();
Ok(implied_bounds)
}
/// When we have an implied bound that `T: 'a`, we can further break
/// this down to determine what relationships would have to hold for
/// `T: 'a` to hold. We get to assume that the caller has validated
/// those relationships.
fn implied_bounds_from_components<'tcx>(
sub_region: ty::Region<'tcx>,
sup_components: SmallVec<[Component<'tcx>; 4]>,
) -> Vec<OutlivesBound<'tcx>> {
sup_components
.into_iter()
.filter_map(|component| {
match component {
Component::Region(r) => Some(OutlivesBound::RegionSubRegion(sub_region, r)),
Component::Param(p) => Some(OutlivesBound::RegionSubParam(sub_region, p)),
Component::Alias(p) => Some(OutlivesBound::RegionSubAlias(sub_region, p)),
Component::EscapingAlias(_) =>
// If the projection has escaping regions, don't
// try to infer any implied bounds even for its
// free components. This is conservative, because
// the caller will still have to prove that those
// free components outlive `sub_region`. But the
// idea is that the WAY that the caller proves
// that may change in the future and we want to
// give ourselves room to get smarter here.
{
None
}
Component::UnresolvedInferenceVariable(..) => None,
}
})
.collect()
}

View file

@ -21,7 +21,8 @@ mod normalize_erasing_regions;
mod normalize_projection_ty;
mod type_op;
pub use type_op::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause};
pub use rustc_trait_selection::traits::query::type_op::ascribe_user_type::type_op_ascribe_user_type_with_span;
pub use type_op::type_op_prove_predicate_with_cause;
use rustc_middle::query::Providers;

View file

@ -1,17 +1,15 @@
use rustc_hir as hir;
use rustc_infer::infer::canonical::{Canonical, QueryResponse};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::query::Providers;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::traits::{DefiningAnchor, ObligationCauseCode};
use rustc_middle::ty::{self, FnSig, Lift, PolyFnSig, Ty, TyCtxt, TypeFoldable};
use rustc_middle::traits::DefiningAnchor;
use rustc_middle::ty::{FnSig, Lift, PolyFnSig, Ty, TyCtxt, TypeFoldable};
use rustc_middle::ty::{ParamEnvAnd, Predicate};
use rustc_middle::ty::{UserSelfTy, UserSubsts, UserType};
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::{Span, DUMMY_SP};
use rustc_trait_selection::infer::InferCtxtBuilderExt;
use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
use rustc_trait_selection::traits::query::type_op::ascribe_user_type::AscribeUserType;
use rustc_trait_selection::traits::query::type_op::ascribe_user_type::{
type_op_ascribe_user_type_with_span, AscribeUserType,
};
use rustc_trait_selection::traits::query::type_op::eq::Eq;
use rustc_trait_selection::traits::query::type_op::normalize::Normalize;
use rustc_trait_selection::traits::query::type_op::prove_predicate::ProvePredicate;
@ -42,111 +40,6 @@ fn type_op_ascribe_user_type<'tcx>(
})
}
/// The core of the `type_op_ascribe_user_type` query: for diagnostics purposes in NLL HRTB errors,
/// this query can be re-run to better track the span of the obligation cause, and improve the error
/// message. Do not call directly unless you're in that very specific context.
pub fn type_op_ascribe_user_type_with_span<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, AscribeUserType<'tcx>>,
span: Option<Span>,
) -> Result<(), NoSolution> {
let (param_env, AscribeUserType { mir_ty, user_ty }) = key.into_parts();
debug!("type_op_ascribe_user_type: mir_ty={:?} user_ty={:?}", mir_ty, user_ty);
let span = span.unwrap_or(DUMMY_SP);
match user_ty {
UserType::Ty(user_ty) => relate_mir_and_user_ty(ocx, param_env, span, mir_ty, user_ty)?,
UserType::TypeOf(def_id, user_substs) => {
relate_mir_and_user_substs(ocx, param_env, span, mir_ty, def_id, user_substs)?
}
};
Ok(())
}
#[instrument(level = "debug", skip(ocx, param_env, span))]
fn relate_mir_and_user_ty<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
span: Span,
mir_ty: Ty<'tcx>,
user_ty: Ty<'tcx>,
) -> Result<(), NoSolution> {
let cause = ObligationCause::dummy_with_span(span);
let user_ty = ocx.normalize(&cause, param_env, user_ty);
ocx.eq(&cause, param_env, mir_ty, user_ty)?;
// FIXME(#104764): We should check well-formedness before normalization.
let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(user_ty.into()));
ocx.register_obligation(Obligation::new(ocx.infcx.tcx, cause, param_env, predicate));
Ok(())
}
#[instrument(level = "debug", skip(ocx, param_env, span))]
fn relate_mir_and_user_substs<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
span: Span,
mir_ty: Ty<'tcx>,
def_id: hir::def_id::DefId,
user_substs: UserSubsts<'tcx>,
) -> Result<(), NoSolution> {
let param_env = param_env.without_const();
let UserSubsts { user_self_ty, substs } = user_substs;
let tcx = ocx.infcx.tcx;
let cause = ObligationCause::dummy_with_span(span);
let ty = tcx.type_of(def_id).subst(tcx, substs);
let ty = ocx.normalize(&cause, param_env, ty);
debug!("relate_type_and_user_type: ty of def-id is {:?}", ty);
ocx.eq(&cause, param_env, mir_ty, ty)?;
// Prove the predicates coming along with `def_id`.
//
// Also, normalize the `instantiated_predicates`
// because otherwise we wind up with duplicate "type
// outlives" error messages.
let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, substs);
debug!(?instantiated_predicates);
for (instantiated_predicate, predicate_span) in instantiated_predicates {
let span = if span == DUMMY_SP { predicate_span } else { span };
let cause = ObligationCause::new(
span,
CRATE_DEF_ID,
ObligationCauseCode::AscribeUserTypeProvePredicate(predicate_span),
);
let instantiated_predicate =
ocx.normalize(&cause.clone(), param_env, instantiated_predicate);
ocx.register_obligation(Obligation::new(tcx, cause, param_env, instantiated_predicate));
}
if let Some(UserSelfTy { impl_def_id, self_ty }) = user_self_ty {
let self_ty = ocx.normalize(&cause, param_env, self_ty);
let impl_self_ty = tcx.type_of(impl_def_id).subst(tcx, substs);
let impl_self_ty = ocx.normalize(&cause, param_env, impl_self_ty);
ocx.eq(&cause, param_env, self_ty, impl_self_ty)?;
let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(impl_self_ty.into()));
ocx.register_obligation(Obligation::new(tcx, cause.clone(), param_env, predicate));
}
// In addition to proving the predicates, we have to
// prove that `ty` is well-formed -- this is because
// the WF of `ty` is predicated on the substs being
// well-formed, and we haven't proven *that*. We don't
// want to prove the WF of types from `substs` directly because they
// haven't been normalized.
//
// FIXME(nmatsakis): Well, perhaps we should normalize
// them? This would only be relevant if some input
// type were ill-formed but did not appear in `ty`,
// which...could happen with normalization...
let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(ty.into()));
ocx.register_obligation(Obligation::new(tcx, cause, param_env, predicate));
Ok(())
}
fn type_op_eq<'tcx>(
tcx: TyCtxt<'tcx>,
canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Eq<'tcx>>>,

View file

@ -204,6 +204,7 @@ where
{
type Error = TryFromSliceError;
#[inline]
fn try_from(slice: &[T]) -> Result<[T; N], TryFromSliceError> {
<&Self>::try_from(slice).map(|r| *r)
}
@ -228,6 +229,7 @@ where
{
type Error = TryFromSliceError;
#[inline]
fn try_from(slice: &mut [T]) -> Result<[T; N], TryFromSliceError> {
<Self>::try_from(&*slice)
}
@ -249,6 +251,7 @@ where
impl<'a, T, const N: usize> TryFrom<&'a [T]> for &'a [T; N] {
type Error = TryFromSliceError;
#[inline]
fn try_from(slice: &'a [T]) -> Result<&'a [T; N], TryFromSliceError> {
if slice.len() == N {
let ptr = slice.as_ptr() as *const [T; N];
@ -276,6 +279,7 @@ impl<'a, T, const N: usize> TryFrom<&'a [T]> for &'a [T; N] {
impl<'a, T, const N: usize> TryFrom<&'a mut [T]> for &'a mut [T; N] {
type Error = TryFromSliceError;
#[inline]
fn try_from(slice: &'a mut [T]) -> Result<&'a mut [T; N], TryFromSliceError> {
if slice.len() == N {
let ptr = slice.as_mut_ptr() as *mut [T; N];

View file

@ -14,6 +14,25 @@ use std::io::BufWriter;
use std::time::{Duration, Instant, SystemTime};
use sysinfo::{CpuExt, System, SystemExt};
// Update this number whenever a breaking change is made to the build metrics.
//
// The output format is versioned for two reasons:
//
// - The metadata is intended to be consumed by external tooling, and exposing a format version
// helps the tools determine whether they're compatible with a metrics file.
//
// - If a developer enables build metrics in their local checkout, making a breaking change to the
// metrics format would result in a hard-to-diagnose error message when an existing metrics file
// is not compatible with the new changes. With a format version number, bootstrap can discard
// incompatible metrics files instead of appending metrics to them.
//
// Version changelog:
//
// - v0: initial version
// - v1: replaced JsonNode::Test with JsonNode::TestSuite
//
const CURRENT_FORMAT_VERSION: usize = 1;
pub(crate) struct BuildMetrics {
state: RefCell<MetricsState>,
}
@ -57,7 +76,7 @@ impl BuildMetrics {
duration_excluding_children_sec: Duration::ZERO,
children: Vec::new(),
tests: Vec::new(),
test_suites: Vec::new(),
});
}
@ -84,6 +103,17 @@ impl BuildMetrics {
}
}
pub(crate) fn begin_test_suite(&self, metadata: TestSuiteMetadata, builder: &Builder<'_>) {
// Do not record dry runs, as they'd be duplicates of the actual steps.
if builder.config.dry_run() {
return;
}
let mut state = self.state.borrow_mut();
let step = state.running_steps.last_mut().unwrap();
step.test_suites.push(TestSuite { metadata, tests: Vec::new() });
}
pub(crate) fn record_test(&self, name: &str, outcome: TestOutcome, builder: &Builder<'_>) {
// Do not record dry runs, as they'd be duplicates of the actual steps.
if builder.config.dry_run() {
@ -91,12 +121,13 @@ impl BuildMetrics {
}
let mut state = self.state.borrow_mut();
state
.running_steps
.last_mut()
.unwrap()
.tests
.push(Test { name: name.to_string(), outcome });
let step = state.running_steps.last_mut().unwrap();
if let Some(test_suite) = step.test_suites.last_mut() {
test_suite.tests.push(Test { name: name.to_string(), outcome });
} else {
panic!("metrics.record_test() called without calling metrics.begin_test_suite() first");
}
}
fn collect_stats(&self, state: &mut MetricsState) {
@ -131,7 +162,20 @@ impl BuildMetrics {
// Some of our CI builds consist of multiple independent CI invocations. Ensure all the
// previous invocations are still present in the resulting file.
let mut invocations = match std::fs::read(&dest) {
Ok(contents) => t!(serde_json::from_slice::<JsonRoot>(&contents)).invocations,
Ok(contents) => {
// We first parse just the format_version field to have the check succeed even if
// the rest of the contents are not valid anymore.
let version: OnlyFormatVersion = t!(serde_json::from_slice(&contents));
if version.format_version == CURRENT_FORMAT_VERSION {
t!(serde_json::from_slice::<JsonRoot>(&contents)).invocations
} else {
println!(
"warning: overriding existing build/metrics.json, as it's not \
compatible with build metrics format version {CURRENT_FORMAT_VERSION}."
);
Vec::new()
}
}
Err(err) => {
if err.kind() != std::io::ErrorKind::NotFound {
panic!("failed to open existing metrics file at {}: {err}", dest.display());
@ -149,7 +193,7 @@ impl BuildMetrics {
children: steps.into_iter().map(|step| self.prepare_json_step(step)).collect(),
});
let json = JsonRoot { system_stats, invocations };
let json = JsonRoot { format_version: CURRENT_FORMAT_VERSION, system_stats, invocations };
t!(std::fs::create_dir_all(dest.parent().unwrap()));
let mut file = BufWriter::new(t!(File::create(&dest)));
@ -159,11 +203,7 @@ impl BuildMetrics {
fn prepare_json_step(&self, step: StepMetrics) -> JsonNode {
let mut children = Vec::new();
children.extend(step.children.into_iter().map(|child| self.prepare_json_step(child)));
children.extend(
step.tests
.into_iter()
.map(|test| JsonNode::Test { name: test.name, outcome: test.outcome }),
);
children.extend(step.test_suites.into_iter().map(JsonNode::TestSuite));
JsonNode::RustbuildStep {
type_: step.type_,
@ -198,17 +238,14 @@ struct StepMetrics {
duration_excluding_children_sec: Duration,
children: Vec<StepMetrics>,
tests: Vec<Test>,
}
struct Test {
name: String,
outcome: TestOutcome,
test_suites: Vec<TestSuite>,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
struct JsonRoot {
#[serde(default)] // For version 0 the field was not present.
format_version: usize,
system_stats: JsonInvocationSystemStats,
invocations: Vec<JsonInvocation>,
}
@ -237,11 +274,39 @@ enum JsonNode {
children: Vec<JsonNode>,
},
Test {
name: String,
#[serde(flatten)]
outcome: TestOutcome,
TestSuite(TestSuite),
}
#[derive(Serialize, Deserialize)]
struct TestSuite {
metadata: TestSuiteMetadata,
tests: Vec<Test>,
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub(crate) enum TestSuiteMetadata {
CargoPackage {
crates: Vec<String>,
target: String,
host: String,
stage: u32,
},
Compiletest {
suite: String,
mode: String,
compare_mode: Option<String>,
target: String,
host: String,
stage: u32,
},
}
#[derive(Serialize, Deserialize)]
pub(crate) struct Test {
name: String,
#[serde(flatten)]
outcome: TestOutcome,
}
#[derive(Serialize, Deserialize)]
@ -266,3 +331,9 @@ struct JsonInvocationSystemStats {
struct JsonStepSystemStats {
cpu_utilization_percent: f64,
}
#[derive(Deserialize)]
struct OnlyFormatVersion {
#[serde(default)] // For version 0 the field was not present.
format_version: usize,
}

View file

@ -317,6 +317,17 @@ impl Step for Cargo {
cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1");
cargo.env("PATH", &path_for_cargo(builder, compiler));
#[cfg(feature = "build-metrics")]
builder.metrics.begin_test_suite(
crate::metrics::TestSuiteMetadata::CargoPackage {
crates: vec!["cargo".into()],
target: self.host.triple.to_string(),
host: self.host.triple.to_string(),
stage: self.stage,
},
builder,
);
let _time = util::timeit(&builder);
add_flags_and_try_run_tests(builder, &mut cargo);
}
@ -1699,6 +1710,19 @@ note: if you're sure you want to do this, please open an issue as to why. In the
builder.ci_env.force_coloring_in_ci(&mut cmd);
#[cfg(feature = "build-metrics")]
builder.metrics.begin_test_suite(
crate::metrics::TestSuiteMetadata::Compiletest {
suite: suite.into(),
mode: mode.into(),
compare_mode: None,
target: self.target.triple.to_string(),
host: self.compiler.host.triple.to_string(),
stage: self.compiler.stage,
},
builder,
);
builder.info(&format!(
"Check compiletest suite={} mode={} ({} -> {})",
suite, mode, &compiler.host, target
@ -1708,6 +1732,20 @@ note: if you're sure you want to do this, please open an issue as to why. In the
if let Some(compare_mode) = compare_mode {
cmd.arg("--compare-mode").arg(compare_mode);
#[cfg(feature = "build-metrics")]
builder.metrics.begin_test_suite(
crate::metrics::TestSuiteMetadata::Compiletest {
suite: suite.into(),
mode: mode.into(),
compare_mode: Some(compare_mode.into()),
target: self.target.triple.to_string(),
host: self.compiler.host.triple.to_string(),
stage: self.compiler.stage,
},
builder,
);
builder.info(&format!(
"Check compiletest suite={} mode={} compare_mode={} ({} -> {})",
suite, mode, compare_mode, &compiler.host, target
@ -2034,6 +2072,17 @@ fn run_cargo_test(
let mut cargo =
prepare_cargo_test(cargo, libtest_args, crates, primary_crate, compiler, target, builder);
let _time = util::timeit(&builder);
#[cfg(feature = "build-metrics")]
builder.metrics.begin_test_suite(
crate::metrics::TestSuiteMetadata::CargoPackage {
crates: crates.iter().map(|c| c.to_string()).collect(),
target: target.triple.to_string(),
host: compiler.host.triple.to_string(),
stage: compiler.stage,
},
builder,
);
add_flags_and_try_run_tests(builder, &mut cargo)
}

View file

@ -2592,7 +2592,8 @@ fn clean_use_statement_inner<'tcx>(
} else {
if inline_attr.is_none()
&& let Res::Def(DefKind::Mod, did) = path.res
&& !did.is_local() && did.is_crate_root()
&& !did.is_local()
&& did.is_crate_root()
{
// if we're `pub use`ing an extern crate root, don't inline it unless we
// were specifically asked for it

View file

@ -1237,7 +1237,27 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
pub(crate) struct MarkdownLink {
pub kind: LinkType,
pub link: String,
pub range: Range<usize>,
pub range: MarkdownLinkRange,
}
#[derive(Clone, Debug)]
pub(crate) enum MarkdownLinkRange {
/// Normally, markdown link warnings point only at the destination.
Destination(Range<usize>),
/// In some cases, it's not possible to point at the destination.
/// Usually, this happens because backslashes `\\` are used.
/// When that happens, point at the whole link, and don't provide structured suggestions.
WholeLink(Range<usize>),
}
impl MarkdownLinkRange {
/// Extracts the inner range.
pub fn inner_range(&self) -> &Range<usize> {
match self {
MarkdownLinkRange::Destination(range) => range,
MarkdownLinkRange::WholeLink(range) => range,
}
}
}
pub(crate) fn markdown_links<R>(
@ -1257,9 +1277,9 @@ pub(crate) fn markdown_links<R>(
if md_start <= s_start && s_end <= md_end {
let start = s_start.offset_from(md_start) as usize;
let end = s_end.offset_from(md_start) as usize;
start..end
MarkdownLinkRange::Destination(start..end)
} else {
fallback
MarkdownLinkRange::WholeLink(fallback)
}
};
@ -1267,6 +1287,7 @@ pub(crate) fn markdown_links<R>(
// For diagnostics, we want to underline the link's definition but `span` will point at
// where the link is used. This is a problem for reference-style links, where the definition
// is separate from the usage.
match link {
// `Borrowed` variant means the string (the link's destination) may come directly from
// the markdown text and we can locate the original link destination.
@ -1275,10 +1296,82 @@ pub(crate) fn markdown_links<R>(
CowStr::Borrowed(s) => locate(s, span),
// For anything else, we can only use the provided range.
CowStr::Boxed(_) | CowStr::Inlined(_) => span,
CowStr::Boxed(_) | CowStr::Inlined(_) => MarkdownLinkRange::WholeLink(span),
}
};
let span_for_offset_backward = |span: Range<usize>, open: u8, close: u8| {
let mut open_brace = !0;
let mut close_brace = !0;
for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate().rev() {
let i = i + span.start;
if b == close {
close_brace = i;
break;
}
}
if close_brace < span.start || close_brace >= span.end {
return MarkdownLinkRange::WholeLink(span);
}
let mut nesting = 1;
for (i, b) in md.as_bytes()[span.start..close_brace].iter().copied().enumerate().rev() {
let i = i + span.start;
if b == close {
nesting += 1;
}
if b == open {
nesting -= 1;
}
if nesting == 0 {
open_brace = i;
break;
}
}
assert!(open_brace != close_brace);
if open_brace < span.start || open_brace >= span.end {
return MarkdownLinkRange::WholeLink(span);
}
// do not actually include braces in the span
let range = (open_brace + 1)..close_brace;
MarkdownLinkRange::Destination(range.clone())
};
let span_for_offset_forward = |span: Range<usize>, open: u8, close: u8| {
let mut open_brace = !0;
let mut close_brace = !0;
for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate() {
let i = i + span.start;
if b == open {
open_brace = i;
break;
}
}
if open_brace < span.start || open_brace >= span.end {
return MarkdownLinkRange::WholeLink(span);
}
let mut nesting = 0;
for (i, b) in md.as_bytes()[open_brace..span.end].iter().copied().enumerate() {
let i = i + open_brace;
if b == close {
nesting -= 1;
}
if b == open {
nesting += 1;
}
if nesting == 0 {
close_brace = i;
break;
}
}
assert!(open_brace != close_brace);
if open_brace < span.start || open_brace >= span.end {
return MarkdownLinkRange::WholeLink(span);
}
// do not actually include braces in the span
let range = (open_brace + 1)..close_brace;
MarkdownLinkRange::Destination(range.clone())
};
Parser::new_with_broken_link_callback(
md,
main_body_opts(),
@ -1287,11 +1380,20 @@ pub(crate) fn markdown_links<R>(
.into_offset_iter()
.filter_map(|(event, span)| match event {
Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => {
preprocess_link(MarkdownLink {
kind: link_type,
range: span_for_link(&dest, span),
link: dest.into_string(),
})
let range = match link_type {
// Link is pulled from the link itself.
LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => {
span_for_offset_backward(span, b'[', b']')
}
LinkType::CollapsedUnknown => span_for_offset_forward(span, b'[', b']'),
LinkType::Inline => span_for_offset_backward(span, b'(', b')'),
// Link is pulled from elsewhere in the document.
LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => {
span_for_link(&dest, span)
}
LinkType::Autolink | LinkType::Email => unreachable!(),
};
preprocess_link(MarkdownLink { kind: link_type, range, link: dest.into_string() })
}
_ => None,
})

View file

@ -31,7 +31,7 @@ use std::ops::Range;
use crate::clean::{self, utils::find_nearest_parent_module};
use crate::clean::{Crate, Item, ItemLink, PrimitiveType};
use crate::core::DocContext;
use crate::html::markdown::{markdown_links, MarkdownLink};
use crate::html::markdown::{markdown_links, MarkdownLink, MarkdownLinkRange};
use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};
use crate::passes::Pass;
use crate::visit::DocVisitor;
@ -248,7 +248,7 @@ struct DiagnosticInfo<'a> {
item: &'a Item,
dox: &'a str,
ori_link: &'a str,
link_range: Range<usize>,
link_range: MarkdownLinkRange,
}
struct LinkCollector<'a, 'tcx> {
@ -833,7 +833,7 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
enum PreprocessingError {
/// User error: `[std#x#y]` is not valid
MultipleAnchors,
Disambiguator(Range<usize>, String),
Disambiguator(MarkdownLinkRange, String),
MalformedGenerics(MalformedGenerics, String),
}
@ -873,6 +873,7 @@ pub(crate) struct PreprocessedMarkdownLink(
/// `link_buffer` is needed for lifetime reasons; it will always be overwritten and the contents ignored.
fn preprocess_link(
ori_link: &MarkdownLink,
dox: &str,
) -> Option<Result<PreprocessingInfo, PreprocessingError>> {
// [] is mostly likely not supposed to be a link
if ori_link.link.is_empty() {
@ -906,9 +907,15 @@ fn preprocess_link(
Err((err_msg, relative_range)) => {
// Only report error if we would not have ignored this link. See issue #83859.
if !should_ignore_link_with_disambiguators(link) {
let no_backticks_range = range_between_backticks(ori_link);
let disambiguator_range = (no_backticks_range.start + relative_range.start)
..(no_backticks_range.start + relative_range.end);
let disambiguator_range = match range_between_backticks(&ori_link.range, dox) {
MarkdownLinkRange::Destination(no_backticks_range) => {
MarkdownLinkRange::Destination(
(no_backticks_range.start + relative_range.start)
..(no_backticks_range.start + relative_range.end),
)
}
mdlr @ MarkdownLinkRange::WholeLink(_) => mdlr,
};
return Some(Err(PreprocessingError::Disambiguator(disambiguator_range, err_msg)));
} else {
return None;
@ -947,7 +954,7 @@ fn preprocess_link(
fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> {
markdown_links(s, |link| {
preprocess_link(&link).map(|pp_link| PreprocessedMarkdownLink(pp_link, link))
preprocess_link(&link, s).map(|pp_link| PreprocessedMarkdownLink(pp_link, link))
})
}
@ -1060,22 +1067,12 @@ impl LinkCollector<'_, '_> {
// valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677
// for discussion on the matter.
let kind = self.cx.tcx.def_kind(id);
self.verify_disambiguator(
path_str,
ori_link,
kind,
id,
disambiguator,
item,
&diag_info,
)?;
self.verify_disambiguator(path_str, kind, id, disambiguator, item, &diag_info)?;
} else {
match disambiguator {
Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {}
Some(other) => {
self.report_disambiguator_mismatch(
path_str, ori_link, other, res, &diag_info,
);
self.report_disambiguator_mismatch(path_str, other, res, &diag_info);
return None;
}
}
@ -1096,7 +1093,6 @@ impl LinkCollector<'_, '_> {
};
self.verify_disambiguator(
path_str,
ori_link,
kind_for_dis,
id_for_dis,
disambiguator,
@ -1118,7 +1114,6 @@ impl LinkCollector<'_, '_> {
fn verify_disambiguator(
&self,
path_str: &str,
ori_link: &MarkdownLink,
kind: DefKind,
id: DefId,
disambiguator: Option<Disambiguator>,
@ -1142,7 +1137,7 @@ impl LinkCollector<'_, '_> {
=> {}
(actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
(_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {
self.report_disambiguator_mismatch(path_str,ori_link,specified, Res::Def(kind, id),diag_info);
self.report_disambiguator_mismatch(path_str, specified, Res::Def(kind, id), diag_info);
return None;
}
}
@ -1164,14 +1159,13 @@ impl LinkCollector<'_, '_> {
fn report_disambiguator_mismatch(
&self,
path_str: &str,
ori_link: &MarkdownLink,
specified: Disambiguator,
resolved: Res,
diag_info: &DiagnosticInfo<'_>,
) {
// The resolved item did not match the disambiguator; give a better error than 'not found'
let msg = format!("incompatible link kind for `{}`", path_str);
let callback = |diag: &mut Diagnostic, sp: Option<rustc_span::Span>| {
let callback = |diag: &mut Diagnostic, sp: Option<rustc_span::Span>, link_range| {
let note = format!(
"this link resolved to {} {}, which is not {} {}",
resolved.article(),
@ -1184,14 +1178,24 @@ impl LinkCollector<'_, '_> {
} else {
diag.note(note);
}
suggest_disambiguator(resolved, diag, path_str, &ori_link.link, sp);
suggest_disambiguator(resolved, diag, path_str, link_range, sp, diag_info);
};
report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, callback);
}
fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &Range<usize>, item: &Item) {
let span = super::source_span_for_markdown_range(self.cx.tcx, dox, ori_link, &item.attrs)
.unwrap_or_else(|| item.attr_span(self.cx.tcx));
fn report_rawptr_assoc_feature_gate(
&self,
dox: &str,
ori_link: &MarkdownLinkRange,
item: &Item,
) {
let span = super::source_span_for_markdown_range(
self.cx.tcx,
dox,
ori_link.inner_range(),
&item.attrs,
)
.unwrap_or_else(|| item.attr_span(self.cx.tcx));
rustc_session::parse::feature_err(
&self.cx.tcx.sess.parse_sess,
sym::intra_doc_pointers,
@ -1371,16 +1375,23 @@ impl LinkCollector<'_, '_> {
/// [`Foo`]
/// ^^^
/// ```
fn range_between_backticks(ori_link: &MarkdownLink) -> Range<usize> {
let after_first_backtick_group = ori_link.link.bytes().position(|b| b != b'`').unwrap_or(0);
let before_second_backtick_group = ori_link
.link
///
/// This function does nothing if `ori_link.range` is a `MarkdownLinkRange::WholeLink`.
fn range_between_backticks(ori_link_range: &MarkdownLinkRange, dox: &str) -> MarkdownLinkRange {
let range = match ori_link_range {
mdlr @ MarkdownLinkRange::WholeLink(_) => return mdlr.clone(),
MarkdownLinkRange::Destination(inner) => inner.clone(),
};
let ori_link_text = &dox[range.clone()];
let after_first_backtick_group = ori_link_text.bytes().position(|b| b != b'`').unwrap_or(0);
let before_second_backtick_group = ori_link_text
.bytes()
.skip(after_first_backtick_group)
.position(|b| b == b'`')
.unwrap_or(ori_link.link.len());
(ori_link.range.start + after_first_backtick_group)
..(ori_link.range.start + before_second_backtick_group)
.unwrap_or(ori_link_text.len());
MarkdownLinkRange::Destination(
(range.start + after_first_backtick_group)..(range.start + before_second_backtick_group),
)
}
/// Returns true if we should ignore `link` due to it being unlikely
@ -1530,14 +1541,23 @@ impl Suggestion {
sp: rustc_span::Span,
) -> Vec<(rustc_span::Span, String)> {
let inner_sp = match ori_link.find('(') {
Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {
sp.with_hi(sp.lo() + BytePos((index - 1) as _))
}
Some(index) => sp.with_hi(sp.lo() + BytePos(index as _)),
None => sp,
};
let inner_sp = match ori_link.find('!') {
Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {
sp.with_hi(sp.lo() + BytePos((index - 1) as _))
}
Some(index) => inner_sp.with_hi(inner_sp.lo() + BytePos(index as _)),
None => inner_sp,
};
let inner_sp = match ori_link.find('@') {
Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {
sp.with_hi(sp.lo() + BytePos((index - 1) as _))
}
Some(index) => inner_sp.with_lo(inner_sp.lo() + BytePos(index as u32 + 1)),
None => inner_sp,
};
@ -1584,7 +1604,7 @@ fn report_diagnostic(
lint: &'static Lint,
msg: impl Into<DiagnosticMessage> + Display,
DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>,
decorate: impl FnOnce(&mut Diagnostic, Option<rustc_span::Span>),
decorate: impl FnOnce(&mut Diagnostic, Option<rustc_span::Span>, MarkdownLinkRange),
) {
let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id)
else {
@ -1596,16 +1616,32 @@ fn report_diagnostic(
let sp = item.attr_span(tcx);
tcx.struct_span_lint_hir(lint, hir_id, sp, msg, |lint| {
let span =
super::source_span_for_markdown_range(tcx, dox, link_range, &item.attrs).map(|sp| {
if dox.as_bytes().get(link_range.start) == Some(&b'`')
&& dox.as_bytes().get(link_range.end - 1) == Some(&b'`')
{
sp.with_lo(sp.lo() + BytePos(1)).with_hi(sp.hi() - BytePos(1))
} else {
sp
}
});
let (span, link_range) = match link_range {
MarkdownLinkRange::Destination(md_range) => {
let mut md_range = md_range.clone();
let sp = super::source_span_for_markdown_range(tcx, dox, &md_range, &item.attrs)
.map(|mut sp| {
while dox.as_bytes().get(md_range.start) == Some(&b' ')
|| dox.as_bytes().get(md_range.start) == Some(&b'`')
{
md_range.start += 1;
sp = sp.with_lo(sp.lo() + BytePos(1));
}
while dox.as_bytes().get(md_range.end - 1) == Some(&b' ')
|| dox.as_bytes().get(md_range.end - 1) == Some(&b'`')
{
md_range.end -= 1;
sp = sp.with_hi(sp.hi() - BytePos(1));
}
sp
});
(sp, MarkdownLinkRange::Destination(md_range))
}
MarkdownLinkRange::WholeLink(md_range) => (
super::source_span_for_markdown_range(tcx, dox, &md_range, &item.attrs),
link_range.clone(),
),
};
if let Some(sp) = span {
lint.set_span(sp);
@ -1614,21 +1650,22 @@ fn report_diagnostic(
// ^ ~~~~
// | link_range
// last_new_line_offset
let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1);
let md_range = link_range.inner_range().clone();
let last_new_line_offset = dox[..md_range.start].rfind('\n').map_or(0, |n| n + 1);
let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
// Print the line containing the `link_range` and manually mark it with '^'s.
// Print the line containing the `md_range` and manually mark it with '^'s.
lint.note(format!(
"the link appears in this line:\n\n{line}\n\
{indicator: <before$}{indicator:^<found$}",
line = line,
indicator = "",
before = link_range.start - last_new_line_offset,
found = link_range.len(),
before = md_range.start - last_new_line_offset,
found = md_range.len(),
));
}
decorate(lint, span);
decorate(lint, span, link_range);
lint
});
@ -1652,7 +1689,7 @@ fn resolution_failure(
BROKEN_INTRA_DOC_LINKS,
format!("unresolved link to `{}`", path_str),
&diag_info,
|diag, sp| {
|diag, sp, link_range| {
let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx),);
let assoc_item_not_allowed = |res: Res| {
let name = res.name(tcx);
@ -1845,7 +1882,14 @@ fn resolution_failure(
let note = match failure {
ResolutionFailure::NotResolved { .. } => unreachable!("handled above"),
ResolutionFailure::WrongNamespace { res, expected_ns } => {
suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp);
suggest_disambiguator(
res,
diag,
path_str,
link_range.clone(),
sp,
&diag_info,
);
format!(
"this link resolves to {}, which is not in the {} namespace",
@ -1882,7 +1926,7 @@ fn anchor_failure(
msg: String,
anchor_idx: usize,
) {
report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, sp| {
report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, sp, _link_range| {
if let Some(mut sp) = sp {
if let Some((fragment_offset, _)) =
diag_info.ori_link.char_indices().filter(|(_, x)| *x == '#').nth(anchor_idx)
@ -1898,11 +1942,11 @@ fn anchor_failure(
fn disambiguator_error(
cx: &DocContext<'_>,
mut diag_info: DiagnosticInfo<'_>,
disambiguator_range: Range<usize>,
disambiguator_range: MarkdownLinkRange,
msg: impl Into<DiagnosticMessage> + Display,
) {
diag_info.link_range = disambiguator_range;
report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp| {
report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp, _link_range| {
let msg = format!(
"see {}/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators",
crate::DOC_RUST_LANG_ORG_CHANNEL
@ -1922,7 +1966,7 @@ fn report_malformed_generics(
BROKEN_INTRA_DOC_LINKS,
format!("unresolved link to `{}`", path_str),
&diag_info,
|diag, sp| {
|diag, sp, _link_range| {
let note = match err {
MalformedGenerics::UnbalancedAngleBrackets => "unbalanced angle brackets",
MalformedGenerics::MissingType => "missing type for generic parameters",
@ -1995,7 +2039,7 @@ fn ambiguity_error(
}
}
report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, |diag, sp| {
report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, link_range| {
if let Some(sp) = sp {
diag.span_label(sp, "ambiguous link");
} else {
@ -2003,7 +2047,7 @@ fn ambiguity_error(
}
for res in kinds {
suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp);
suggest_disambiguator(res, diag, path_str, link_range.clone(), sp, diag_info);
}
});
true
@ -2015,13 +2059,19 @@ fn suggest_disambiguator(
res: Res,
diag: &mut Diagnostic,
path_str: &str,
ori_link: &str,
link_range: MarkdownLinkRange,
sp: Option<rustc_span::Span>,
diag_info: &DiagnosticInfo<'_>,
) {
let suggestion = res.disambiguator_suggestion();
let help = format!("to link to the {}, {}", res.descr(), suggestion.descr());
if let Some(sp) = sp {
let ori_link = match link_range {
MarkdownLinkRange::Destination(range) => Some(&diag_info.dox[range]),
MarkdownLinkRange::WholeLink(_) => None,
};
if let (Some(sp), Some(ori_link)) = (sp, ori_link) {
let mut spans = suggestion.as_help_span(path_str, ori_link, sp);
if spans.len() > 1 {
diag.multipart_suggestion(help, spans, Applicability::MaybeIncorrect);
@ -2047,7 +2097,7 @@ fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str:
let msg =
format!("public documentation for `{}` links to private item `{}`", item_name, path_str);
report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, msg, diag_info, |diag, sp| {
report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, _link_range| {
if let Some(sp) = sp {
diag.span_label(sp, "this item is private");
}

View file

@ -305,11 +305,27 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
return false;
}
if !self.view_item_stack.insert(res_did) {
let is_bang_macro = matches!(
tcx.hir().get_by_def_id(res_did),
Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, MacroKind::Bang), .. })
);
if !self.view_item_stack.insert(res_did) && !is_bang_macro {
return false;
}
let ret = match tcx.hir().get_by_def_id(res_did) {
// Bang macros are handled a bit on their because of how they are handled by the
// compiler. If they have `#[doc(hidden)]` and the re-export doesn't have
// `#[doc(inline)]`, then we don't inline it.
Node::Item(_)
if is_bang_macro
&& !please_inline
&& renamed.is_some()
&& self.cx.tcx.is_doc_hidden(ori_res_did) =>
{
return false;
}
Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => {
let prev = mem::replace(&mut self.inlining, true);
for &i in m.item_ids {

View file

@ -36,7 +36,7 @@ LL | //! [`Clone ()`].
help: to link to the trait, prefix with `trait@`
|
LL - //! [`Clone ()`].
LL + //! [`trait@Clone (`].
LL + //! [`trait@Clone `].
|
error: incompatible link kind for `Clone`
@ -47,8 +47,9 @@ LL | //! [`Clone !`].
|
help: to link to the derive macro, prefix with `derive@`
|
LL | //! [`derive@Clone !`].
| +++++++
LL - //! [`Clone !`].
LL + //! [`derive@Clone `].
|
error: aborting due to 4 previous errors

View file

@ -0,0 +1,140 @@
// Many examples are from
// https://github.com/rust-lang/rust/issues/110111#issuecomment-1517800781
#![deny(rustdoc::broken_intra_doc_links)]
//! This test case is closely linked to [raphlinus/pulldown-cmark#441], getting offsets of
//! link components. In particular, pulldown-cmark doesn't provide the offsets of the contents
//! of a link.
//!
//! To work around this, rustdoc parses parts of a link definition itself. This is basically a
//! test suite for that link syntax parser.
//!
//! [raphlinus/pulldown-cmark#441]: https://github.com/raphlinus/pulldown-cmark/issues/441
use std::clone::Clone;
// Basic version //
/// [`struct@Clone`] //~ERROR link
pub struct LinkToCloneWithBackquotes;
/// [```struct@Clone```] //~ERROR link
pub struct LinkToCloneWithMultipleBackquotes;
/// [ ` struct@Clone ` ] //~ERROR link
pub struct LinkToCloneWithSpacesAndBackquotes;
/// [ `Clone ()` ] //~ERROR link
pub struct LinkToCloneWithSpacesBackquotesAndParens;
/// [`Clone ()` ] //~ERROR link
pub struct LinkToCloneWithSpacesEndBackquotesAndParens;
/// [ `Clone ()`] //~ERROR link
pub struct LinkToCloneWithSpacesStartBackquotesAndParens;
/// [```Clone ()```] //~ERROR link
pub struct LinkToCloneWithMultipleBackquotesAndParens;
/// [```Clone \(\)```] // not URL-shaped enough
pub struct LinkToCloneWithMultipleBackquotesAndEscapedParens;
/// [ ``` Clone () ``` ] //~ERROR link
pub struct LinkToCloneWithSpacesMultipleBackquotesAndParens;
/// [ x \] ] // not URL-shaped enough
pub struct LinkWithEscapedCloseBrace;
/// [ x \[ ] // not URL-shaped enough
pub struct LinkWithEscapedOpenBrace;
/// [ x \( ] // not URL-shaped enough
pub struct LinkWithEscapedCloseParen;
/// [ x \) ] // not URL-shaped enough
pub struct LinkWithEscapedOpenParen;
/// [ Clone \(\) ] // not URL-shaped enough
pub struct LinkWithEscapedParens;
// [][] version //
/// [x][ struct@Clone] //~ERROR link
pub struct XLinkToCloneWithStartSpace;
/// [x][struct@Clone ] //~ERROR link
pub struct XLinkToCloneWithEndSpace;
/// [x][Clone\(\)] not URL-shaped enough
pub struct XLinkToCloneWithEscapedParens;
/// [x][`Clone`] not URL-shaped enough
pub struct XLinkToCloneWithBackquotes;
/// [x][Clone()] //~ERROR link
pub struct XLinkToCloneWithUnescapedParens;
/// [x][Clone ()] //~ERROR link
pub struct XLinkToCloneWithUnescapedParensAndDoubleSpace;
/// [x][Clone [] //~ERROR unresolved link to `x`
pub struct XLinkToCloneWithUnmatchedOpenParenAndDoubleSpace;
/// [x][Clone \[] // not URL-shaped enough
pub struct XLinkToCloneWithUnmatchedEscapedOpenParenAndDoubleSpace;
/// [x][Clone \]] // not URL-shaped enough
pub struct XLinkToCloneWithUnmatchedEscapedCloseParenAndDoubleSpace;
// []() version //
/// [w]( struct@Clone) //~ERROR link
pub struct WLinkToCloneWithStartSpace;
/// [w](struct@Clone ) //~ERROR link
pub struct WLinkToCloneWithEndSpace;
/// [w](Clone\(\)) //~ERROR link
pub struct WLinkToCloneWithEscapedParens;
/// [w](`Clone`) not URL-shaped enough
pub struct WLinkToCloneWithBackquotes;
/// [w](Clone()) //~ERROR link
pub struct WLinkToCloneWithUnescapedParens;
/// [w](Clone ()) not URL-shaped enough
pub struct WLinkToCloneWithUnescapedParensAndDoubleSpace;
/// [w](Clone () //~ERROR unresolved link to `w`
pub struct WLinkToCloneWithUnmatchedOpenParenAndDoubleSpace;
/// [w](Clone \() //~ERROR unresolved link to `w`
pub struct WLinkToCloneWithUnmatchedEscapedOpenParenAndDoubleSpace;
/// [w](Clone \)) //~ERROR unresolved link to `w`
pub struct WLinkToCloneWithUnmatchedEscapedCloseParenAndDoubleSpace;
// References
/// The [cln][] link here is going to be unresolved, because `Clone()` gets rejected //~ERROR link
/// in Markdown for not being URL-shaped enough.
///
/// [cln]: Clone() //~ERROR link
pub struct LinkToCloneWithParensInReference;
/// The [cln][] link here is going to be unresolved, because `struct@Clone` gets //~ERROR link
/// rejected in Markdown for not being URL-shaped enough.
///
/// [cln]: struct@Clone //~ERROR link
pub struct LinkToCloneWithWrongPrefix;
/// The [cln][] link here will produce a plain text suggestion //~ERROR link
///
/// [cln]: Clone\(\)
pub struct LinkToCloneWithEscapedParensInReference;
/// The [cln][] link here will produce a plain text suggestion //~ERROR link
///
/// [cln]: struct\@Clone
pub struct LinkToCloneWithEscapedAtsInReference;

View file

@ -0,0 +1,272 @@
error: incompatible link kind for `Clone`
--> $DIR/weird-syntax.rs:18:7
|
LL | /// [`struct@Clone`]
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
|
note: the lint level is defined here
--> $DIR/weird-syntax.rs:3:9
|
LL | #![deny(rustdoc::broken_intra_doc_links)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to link to the trait, prefix with `trait@`
|
LL | /// [`trait@Clone`]
| ~~~~~~
error: incompatible link kind for `Clone`
--> $DIR/weird-syntax.rs:21:9
|
LL | /// [```struct@Clone```]
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
|
help: to link to the trait, prefix with `trait@`
|
LL | /// [```trait@Clone```]
| ~~~~~~
error: incompatible link kind for `Clone`
--> $DIR/weird-syntax.rs:24:11
|
LL | /// [ ` struct@Clone ` ]
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
|
help: to link to the trait, prefix with `trait@`
|
LL | /// [ ` trait@Clone ` ]
| ~~~~~~
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:27:9
|
LL | /// [ `Clone ()` ]
| ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [ `Clone ()` ]
LL + /// [ `trait@Clone ` ]
|
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:30:7
|
LL | /// [`Clone ()` ]
| ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [`Clone ()` ]
LL + /// [`trait@Clone ` ]
|
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:33:9
|
LL | /// [ `Clone ()`]
| ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [ `Clone ()`]
LL + /// [ `trait@Clone `]
|
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:36:9
|
LL | /// [```Clone ()```]
| ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [```Clone ()```]
LL + /// [```trait@Clone ```]
|
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:42:13
|
LL | /// [ ``` Clone () ``` ]
| ^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [ ``` Clone () ``` ]
LL + /// [ ``` trait@Clone ``` ]
|
error: incompatible link kind for `Clone`
--> $DIR/weird-syntax.rs:62:10
|
LL | /// [x][ struct@Clone]
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
|
help: to link to the trait, prefix with `trait@`
|
LL | /// [x][ trait@Clone]
| ~~~~~~
error: incompatible link kind for `Clone`
--> $DIR/weird-syntax.rs:65:9
|
LL | /// [x][struct@Clone ]
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
|
help: to link to the trait, prefix with `trait@`
|
LL | /// [x][trait@Clone ]
| ~~~~~~
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:74:9
|
LL | /// [x][Clone()]
| ^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [x][Clone()]
LL + /// [x][trait@Clone]
|
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:77:9
|
LL | /// [x][Clone ()]
| ^^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [x][Clone ()]
LL + /// [x][trait@Clone ]
|
error: unresolved link to `x`
--> $DIR/weird-syntax.rs:80:6
|
LL | /// [x][Clone []
| ^ no item named `x` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: incompatible link kind for `Clone`
--> $DIR/weird-syntax.rs:91:10
|
LL | /// [w]( struct@Clone)
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
|
help: to link to the trait, prefix with `trait@`
|
LL | /// [w]( trait@Clone)
| ~~~~~~
error: incompatible link kind for `Clone`
--> $DIR/weird-syntax.rs:94:9
|
LL | /// [w](struct@Clone )
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
|
help: to link to the trait, prefix with `trait@`
|
LL | /// [w](trait@Clone )
| ~~~~~~
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:97:9
|
LL | /// [w](Clone\(\))
| ^^^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [w](Clone\(\))
LL + /// [w](trait@Clone)
|
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:103:9
|
LL | /// [w](Clone())
| ^^^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
help: to link to the trait, prefix with `trait@`
|
LL - /// [w](Clone())
LL + /// [w](trait@Clone)
|
error: unresolved link to `w`
--> $DIR/weird-syntax.rs:109:6
|
LL | /// [w](Clone ()
| ^ no item named `w` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `w`
--> $DIR/weird-syntax.rs:112:6
|
LL | /// [w](Clone \()
| ^ no item named `w` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `w`
--> $DIR/weird-syntax.rs:115:6
|
LL | /// [w](Clone \))
| ^ no item named `w` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `cln`
--> $DIR/weird-syntax.rs:120:10
|
LL | /// The [cln][] link here is going to be unresolved, because `Clone()` gets rejected
| ^^^ no item named `cln` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `cln`
--> $DIR/weird-syntax.rs:123:6
|
LL | /// [cln]: Clone()
| ^^^ no item named `cln` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `cln`
--> $DIR/weird-syntax.rs:126:10
|
LL | /// The [cln][] link here is going to be unresolved, because `struct@Clone` gets
| ^^^ no item named `cln` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `cln`
--> $DIR/weird-syntax.rs:129:6
|
LL | /// [cln]: struct@Clone
| ^^^ no item named `cln` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:132:9
|
LL | /// The [cln][] link here will produce a plain text suggestion
| ^^^^^ this link resolves to the trait `Clone`, which is not in the value namespace
|
= help: to link to the trait, prefix with `trait@`: trait@Clone
error: incompatible link kind for `Clone`
--> $DIR/weird-syntax.rs:137:9
|
LL | /// The [cln][] link here will produce a plain text suggestion
| ^^^^^ this link resolved to a trait, which is not a struct
|
= help: to link to the trait, prefix with `trait@`: trait@Clone
error: aborting due to 26 previous errors

View file

@ -21,6 +21,5 @@ macro_rules! foo {
() => {};
}
// This is a bug: https://github.com/rust-lang/rust/issues/59368
// @!has - '//*[@id="reexport.Macro"]/code' 'pub use crate::foo as Macro;'
// @has - '//*[@id="reexport.Macro"]/code' 'pub use crate::foo as Macro;'
pub use crate::foo as Macro;

View file

@ -5,6 +5,7 @@
// @has 'foo/index.html'
// @has - '//*[@id="main-content"]//a[@href="macro.Macro2.html"]' 'Macro2'
// @has - '//*[@id="reexport.Macro"]/code' 'pub use crate::foo as Macro;'
// @has 'foo/macro.Macro2.html'
// @has - '//*[@class="docblock"]' 'Displayed'
@ -15,7 +16,6 @@ macro_rules! foo {
() => {};
}
/// not displayed
pub use crate::foo as Macro;
/// Displayed
#[doc(inline)]

View file

@ -0,0 +1,42 @@
// This test ensures that all re-exports of doc hidden elements are displayed.
#![crate_name = "foo"]
#[doc(hidden)]
pub struct Bar;
#[macro_export]
#[doc(hidden)]
macro_rules! foo {
() => {};
}
// @has 'foo/index.html'
// @has - '//*[@id="reexport.Macro"]/code' 'pub use crate::foo as Macro;'
pub use crate::foo as Macro;
// @has - '//*[@id="reexport.Macro2"]/code' 'pub use crate::foo as Macro2;'
pub use crate::foo as Macro2;
// @has - '//*[@id="reexport.Boo"]/code' 'pub use crate::Bar as Boo;'
pub use crate::Bar as Boo;
// @has - '//*[@id="reexport.Boo2"]/code' 'pub use crate::Bar as Boo2;'
pub use crate::Bar as Boo2;
pub fn fofo() {}
// @has - '//*[@id="reexport.f1"]/code' 'pub use crate::fofo as f1;'
pub use crate::fofo as f1;
// @has - '//*[@id="reexport.f2"]/code' 'pub use crate::fofo as f2;'
pub use crate::fofo as f2;
pub mod sub {
// @has 'foo/sub/index.html'
// @has - '//*[@id="reexport.Macro"]/code' 'pub use crate::foo as Macro;'
pub use crate::foo as Macro;
// @has - '//*[@id="reexport.Macro2"]/code' 'pub use crate::foo as Macro2;'
pub use crate::foo as Macro2;
// @has - '//*[@id="reexport.f1"]/code' 'pub use crate::fofo as f1;'
pub use crate::fofo as f1;
// @has - '//*[@id="reexport.f2"]/code' 'pub use crate::fofo as f2;'
pub use crate::fofo as f2;
}

View file

@ -8,7 +8,6 @@ fn a() {
//~^ NOTE `vec[_]` is borrowed here
vec[0] = Box::new(4); //~ ERROR cannot assign
//~^ NOTE `vec[_]` is assigned to here
//~| NOTE in this expansion of desugaring of drop and replace
_a.use_ref();
//~^ NOTE borrow later used here
}
@ -23,7 +22,6 @@ fn b() {
//~^ `vec[_]` is borrowed here
vec[0] = Box::new(4); //~ ERROR cannot assign
//~^ NOTE `vec[_]` is assigned to here
//~| NOTE in this expansion of desugaring of drop and replace
_b.use_ref();
//~^ NOTE borrow later used here
}

View file

@ -6,24 +6,24 @@ LL | [box ref _a, _, _] => {
LL |
LL | vec[0] = Box::new(4);
| ^^^^^^ `vec[_]` is assigned to here but it was already borrowed
...
LL |
LL | _a.use_ref();
| ------------ borrow later used here
error[E0506]: cannot assign to `vec[_]` because it is borrowed
--> $DIR/borrowck-vec-pattern-nesting.rs:24:13
--> $DIR/borrowck-vec-pattern-nesting.rs:23:13
|
LL | &mut [ref _b @ ..] => {
| ------ `vec[_]` is borrowed here
LL |
LL | vec[0] = Box::new(4);
| ^^^^^^ `vec[_]` is assigned to here but it was already borrowed
...
LL |
LL | _b.use_ref();
| ------------ borrow later used here
error[E0508]: cannot move out of type `[Box<isize>]`, a non-copy slice
--> $DIR/borrowck-vec-pattern-nesting.rs:36:11
--> $DIR/borrowck-vec-pattern-nesting.rs:34:11
|
LL | match vec {
| ^^^ cannot move out of here
@ -41,7 +41,7 @@ LL + [_a,
|
error[E0508]: cannot move out of type `[Box<isize>]`, a non-copy slice
--> $DIR/borrowck-vec-pattern-nesting.rs:48:13
--> $DIR/borrowck-vec-pattern-nesting.rs:46:13
|
LL | let a = vec[0];
| ^^^^^^
@ -55,7 +55,7 @@ LL | let a = &vec[0];
| +
error[E0508]: cannot move out of type `[Box<isize>]`, a non-copy slice
--> $DIR/borrowck-vec-pattern-nesting.rs:57:11
--> $DIR/borrowck-vec-pattern-nesting.rs:55:11
|
LL | match vec {
| ^^^ cannot move out of here
@ -73,7 +73,7 @@ LL + [
|
error[E0508]: cannot move out of type `[Box<isize>]`, a non-copy slice
--> $DIR/borrowck-vec-pattern-nesting.rs:67:13
--> $DIR/borrowck-vec-pattern-nesting.rs:65:13
|
LL | let a = vec[0];
| ^^^^^^
@ -87,7 +87,7 @@ LL | let a = &vec[0];
| +
error[E0508]: cannot move out of type `[Box<isize>]`, a non-copy slice
--> $DIR/borrowck-vec-pattern-nesting.rs:76:11
--> $DIR/borrowck-vec-pattern-nesting.rs:74:11
|
LL | match vec {
| ^^^ cannot move out of here
@ -106,7 +106,7 @@ LL + [_a, _b, _c] => {}
|
error[E0508]: cannot move out of type `[Box<isize>]`, a non-copy slice
--> $DIR/borrowck-vec-pattern-nesting.rs:87:13
--> $DIR/borrowck-vec-pattern-nesting.rs:85:13
|
LL | let a = vec[0];
| ^^^^^^

View file

@ -5,7 +5,6 @@ fn test_drop_replace() {
b = Box::new(1); //~ NOTE first assignment
b = Box::new(2); //~ ERROR cannot assign twice to immutable variable `b`
//~| NOTE cannot assign twice to immutable
//~| NOTE in this expansion of desugaring of drop and replace
}
fn test_call() {
@ -14,14 +13,12 @@ fn test_call() {
//~| SUGGESTION mut b
b = Box::new(2); //~ ERROR cannot assign twice to immutable variable `b`
//~| NOTE cannot assign twice to immutable
//~| NOTE in this expansion of desugaring of drop and replace
}
fn test_args(b: Box<i32>) { //~ HELP consider making this binding mutable
//~| SUGGESTION mut b
b = Box::new(2); //~ ERROR cannot assign to immutable argument `b`
//~| NOTE cannot assign to immutable argument
//~| NOTE in this expansion of desugaring of drop and replace
}
fn main() {}

View file

@ -10,7 +10,7 @@ LL | b = Box::new(2);
| ^ cannot assign twice to immutable variable
error[E0384]: cannot assign twice to immutable variable `b`
--> $DIR/issue-45199.rs:15:5
--> $DIR/issue-45199.rs:14:5
|
LL | let b = Box::new(1);
| -
@ -22,7 +22,7 @@ LL | b = Box::new(2);
| ^ cannot assign twice to immutable variable
error[E0384]: cannot assign to immutable argument `b`
--> $DIR/issue-45199.rs:22:5
--> $DIR/issue-45199.rs:20:5
|
LL | fn test_args(b: Box<i32>) {
| - help: consider making this binding mutable: `mut b`

View file

@ -5,7 +5,6 @@ fn test() {
drop(b);
b = Box::new(2); //~ ERROR cannot assign twice to immutable variable `b`
//~| NOTE cannot assign twice to immutable
//~| NOTE in this expansion of desugaring of drop and replace
drop(b);
}