Auto merge of #117764 - cuviper:beta-next, r=cuviper

[beta] backports

- dropck_outlives check whether generator witness needs_drop #117134
- Make sure that predicates with unmentioned bound vars are still considered global in the old solver #117589
- Check binders with bound vars for global bounds that don't hold #117637
- generator layout: ignore fake borrows #117712

r? ghost
This commit is contained in:
bors 2023-11-10 00:34:47 +00:00
commit fbf0758499
57 changed files with 307 additions and 183 deletions

View file

@ -71,7 +71,7 @@ impl<'tcx> fmt::Display for BorrowData<'tcx> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
let kind = match self.kind {
mir::BorrowKind::Shared => "",
mir::BorrowKind::Shallow => "shallow ",
mir::BorrowKind::Fake => "fake ",
mir::BorrowKind::Mut { kind: mir::MutBorrowKind::ClosureCapture } => "uniq ",
// FIXME: differentiate `TwoPhaseBorrow`
mir::BorrowKind::Mut {

View file

@ -49,7 +49,7 @@ pub fn categorize(context: PlaceContext) -> Option<DefUse> {
// cross suspension points so this behavior is unproblematic.
PlaceContext::MutatingUse(MutatingUseContext::Borrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::FakeBorrow) |
// `PlaceMention` and `AscribeUserType` both evaluate the place, which must not
// contain dangling references.

View file

@ -1025,7 +1025,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
self.cannot_uniquely_borrow_by_two_closures(span, &desc_place, issued_span, None)
}
(BorrowKind::Mut { .. }, BorrowKind::Shallow) => {
(BorrowKind::Mut { .. }, BorrowKind::Fake) => {
if let Some(immutable_section_description) =
self.classify_immutable_section(issued_borrow.assigned_place)
{
@ -1117,11 +1117,10 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
)
}
(BorrowKind::Shared, BorrowKind::Shared | BorrowKind::Shallow)
| (
BorrowKind::Shallow,
BorrowKind::Mut { .. } | BorrowKind::Shared | BorrowKind::Shallow,
) => unreachable!(),
(BorrowKind::Shared, BorrowKind::Shared | BorrowKind::Fake)
| (BorrowKind::Fake, BorrowKind::Mut { .. } | BorrowKind::Shared | BorrowKind::Fake) => {
unreachable!()
}
};
if issued_spans == borrow_spans {
@ -2644,7 +2643,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
let loan_span = loan_spans.args_or_use();
let descr_place = self.describe_any_place(place.as_ref());
if loan.kind == BorrowKind::Shallow {
if loan.kind == BorrowKind::Fake {
if let Some(section) = self.classify_immutable_section(loan.assigned_place) {
let mut err = self.cannot_mutate_in_immutable_section(
span,

View file

@ -628,7 +628,7 @@ impl UseSpans<'_> {
err.subdiagnostic(match kind {
Some(kd) => match kd {
rustc_middle::mir::BorrowKind::Shared
| rustc_middle::mir::BorrowKind::Shallow => {
| rustc_middle::mir::BorrowKind::Fake => {
CaptureVarKind::Immut { kind_span: capture_kind_span }
}

View file

@ -253,8 +253,8 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> {
match rvalue {
&Rvalue::Ref(_ /*rgn*/, bk, place) => {
let access_kind = match bk {
BorrowKind::Shallow => {
(Shallow(Some(ArtificialField::ShallowBorrow)), Read(ReadKind::Borrow(bk)))
BorrowKind::Fake => {
(Shallow(Some(ArtificialField::FakeBorrow)), Read(ReadKind::Borrow(bk)))
}
BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
BorrowKind::Mut { .. } => {
@ -376,8 +376,8 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> {
// have already taken the reservation
}
(Read(_), BorrowKind::Shallow | BorrowKind::Shared)
| (Read(ReadKind::Borrow(BorrowKind::Shallow)), BorrowKind::Mut { .. }) => {
(Read(_), BorrowKind::Fake | BorrowKind::Shared)
| (Read(ReadKind::Borrow(BorrowKind::Fake)), BorrowKind::Mut { .. }) => {
// Reads don't invalidate shared or shallow borrows
}
@ -422,7 +422,7 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> {
// only mutable borrows should be 2-phase
assert!(match borrow.kind {
BorrowKind::Shared | BorrowKind::Shallow => false,
BorrowKind::Shared | BorrowKind::Fake => false,
BorrowKind::Mut { .. } => true,
});

View file

@ -837,7 +837,7 @@ use self::ReadOrWrite::{Activation, Read, Reservation, Write};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum ArtificialField {
ArrayLength,
ShallowBorrow,
FakeBorrow,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@ -1076,18 +1076,18 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
Control::Continue
}
(Read(_), BorrowKind::Shared | BorrowKind::Shallow)
| (Read(ReadKind::Borrow(BorrowKind::Shallow)), BorrowKind::Mut { .. }) => {
(Read(_), BorrowKind::Shared | BorrowKind::Fake)
| (Read(ReadKind::Borrow(BorrowKind::Fake)), BorrowKind::Mut { .. }) => {
Control::Continue
}
(Reservation(_), BorrowKind::Shallow | BorrowKind::Shared) => {
(Reservation(_), BorrowKind::Fake | BorrowKind::Shared) => {
// This used to be a future compatibility warning (to be
// disallowed on NLL). See rust-lang/rust#56254
Control::Continue
}
(Write(WriteKind::Move), BorrowKind::Shallow) => {
(Write(WriteKind::Move), BorrowKind::Fake) => {
// Handled by initialization checks.
Control::Continue
}
@ -1195,8 +1195,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
match rvalue {
&Rvalue::Ref(_ /*rgn*/, bk, place) => {
let access_kind = match bk {
BorrowKind::Shallow => {
(Shallow(Some(ArtificialField::ShallowBorrow)), Read(ReadKind::Borrow(bk)))
BorrowKind::Fake => {
(Shallow(Some(ArtificialField::FakeBorrow)), Read(ReadKind::Borrow(bk)))
}
BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
BorrowKind::Mut { .. } => {
@ -1217,7 +1217,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
flow_state,
);
let action = if bk == BorrowKind::Shallow {
let action = if bk == BorrowKind::Fake {
InitializationRequiringAction::MatchOn
} else {
InitializationRequiringAction::Borrow
@ -1569,7 +1569,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
// only mutable borrows should be 2-phase
assert!(match borrow.kind {
BorrowKind::Shared | BorrowKind::Shallow => false,
BorrowKind::Shared | BorrowKind::Fake => false,
BorrowKind::Mut { .. } => true,
});
@ -2002,14 +2002,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
| WriteKind::Replace
| WriteKind::StorageDeadOrDrop
| WriteKind::MutableBorrow(BorrowKind::Shared)
| WriteKind::MutableBorrow(BorrowKind::Shallow),
| WriteKind::MutableBorrow(BorrowKind::Fake),
)
| Write(
WriteKind::Move
| WriteKind::Replace
| WriteKind::StorageDeadOrDrop
| WriteKind::MutableBorrow(BorrowKind::Shared)
| WriteKind::MutableBorrow(BorrowKind::Shallow),
| WriteKind::MutableBorrow(BorrowKind::Fake),
) => {
if self.is_mutable(place.as_ref(), is_local_mutation_allowed).is_err()
&& !self.has_buffered_errors()
@ -2033,7 +2033,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
return false;
}
Read(
ReadKind::Borrow(BorrowKind::Mut { .. } | BorrowKind::Shared | BorrowKind::Shallow)
ReadKind::Borrow(BorrowKind::Mut { .. } | BorrowKind::Shared | BorrowKind::Fake)
| ReadKind::Copy,
) => {
// Access authorized

View file

@ -204,7 +204,7 @@ fn place_components_conflict<'tcx>(
match (elem, &base_ty.kind(), access) {
(_, _, Shallow(Some(ArtificialField::ArrayLength)))
| (_, _, Shallow(Some(ArtificialField::ShallowBorrow))) => {
| (_, _, Shallow(Some(ArtificialField::FakeBorrow))) => {
// The array length is like additional fields on the
// type; it does not overlap any existing data there.
// Furthermore, if cannot actually be a prefix of any
@ -272,10 +272,10 @@ fn place_components_conflict<'tcx>(
// If the second example, where we did, then we still know
// that the borrow can access a *part* of our place that
// our access cares about, so we still have a conflict.
if borrow_kind == BorrowKind::Shallow
if borrow_kind == BorrowKind::Fake
&& borrow_place.projection.len() < access_place.projection.len()
{
debug!("borrow_conflicts_with_place: shallow borrow");
debug!("borrow_conflicts_with_place: fake borrow");
false
} else {
debug!("borrow_conflicts_with_place: full borrow, CONFLICT");

View file

@ -745,7 +745,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> {
PlaceContext::MutatingUse(_) => ty::Invariant,
PlaceContext::NonUse(StorageDead | StorageLive | VarDebugInfo) => ty::Invariant,
PlaceContext::NonMutatingUse(
Inspect | Copy | Move | PlaceMention | SharedBorrow | ShallowBorrow | AddressOf
Inspect | Copy | Move | PlaceMention | SharedBorrow | FakeBorrow | AddressOf
| Projection,
) => ty::Covariant,
PlaceContext::NonUse(AscribeUserTy(variance)) => variance,

View file

@ -234,7 +234,7 @@ impl<'mir, 'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> Visitor<'tcx>
| PlaceContext::NonMutatingUse(
NonMutatingUseContext::Inspect
| NonMutatingUseContext::SharedBorrow
| NonMutatingUseContext::ShallowBorrow
| NonMutatingUseContext::FakeBorrow
| NonMutatingUseContext::AddressOf
| NonMutatingUseContext::Projection,
) => {

View file

@ -415,8 +415,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
BorrowKind::Shared => {
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow)
}
BorrowKind::Shallow => {
PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
BorrowKind::Fake => {
PlaceContext::NonMutatingUse(NonMutatingUseContext::FakeBorrow)
}
BorrowKind::Mut { .. } => {
PlaceContext::MutatingUse(MutatingUseContext::Borrow)
@ -491,7 +491,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
self.check_mut_borrow(place.local, hir::BorrowKind::Raw)
}
Rvalue::Ref(_, BorrowKind::Shared | BorrowKind::Shallow, place)
Rvalue::Ref(_, BorrowKind::Shared | BorrowKind::Fake, place)
| Rvalue::AddressOf(Mutability::Not, place) => {
let borrowed_place_has_mut_interior = qualifs::in_place::<HasMutInterior, _>(
&self.ccx,

View file

@ -105,7 +105,7 @@ where
fn ref_allows_mutation(&self, kind: mir::BorrowKind, place: mir::Place<'tcx>) -> bool {
match kind {
mir::BorrowKind::Mut { .. } => true,
mir::BorrowKind::Shared | mir::BorrowKind::Shallow => {
mir::BorrowKind::Shared | mir::BorrowKind::Fake => {
self.shared_borrow_allows_mutation(place)
}
}

View file

@ -454,7 +454,7 @@ impl<'tcx> Validator<'_, 'tcx> {
match kind {
// Reject these borrow types just to be safe.
// FIXME(RalfJung): could we allow them? Should we? No point in it until we have a usecase.
BorrowKind::Shallow | BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture } => {
BorrowKind::Fake | BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture } => {
return Err(Unpromotable);
}

View file

@ -821,11 +821,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
}
match rvalue {
Rvalue::Use(_) | Rvalue::CopyForDeref(_) | Rvalue::Aggregate(..) => {}
Rvalue::Ref(_, BorrowKind::Shallow, _) => {
Rvalue::Ref(_, BorrowKind::Fake, _) => {
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) {
self.fail(
location,
"`Assign` statement with a `Shallow` borrow should have been removed in runtime MIR",
"`Assign` statement with a `Fake` borrow should have been removed in runtime MIR",
);
}
}

View file

@ -32,6 +32,7 @@ use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _
use rustc_trait_selection::traits::{
self, ObligationCause, ObligationCauseCode, ObligationCtxt, WellFormedLoc,
};
use rustc_type_ir::TypeFlags;
use std::cell::LazyCell;
use std::ops::{ControlFlow, Deref};
@ -1862,7 +1863,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
continue;
}
// Match the existing behavior.
if pred.is_global() && !pred.has_late_bound_vars() {
if pred.is_global() && !pred.has_type_flags(TypeFlags::HAS_BINDER_VARS) {
let pred = self.normalize(span, None, pred);
let hir_node = tcx.hir().find_by_def_id(self.body_def_id);

View file

@ -924,7 +924,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
Ref(region, borrow_kind, ref place) => {
let kind_str = match borrow_kind {
BorrowKind::Shared => "",
BorrowKind::Shallow => "shallow ",
BorrowKind::Fake => "fake ",
BorrowKind::Mut { .. } => "mut ",
};

View file

@ -443,7 +443,7 @@ impl<'tcx> Rvalue<'tcx> {
impl BorrowKind {
pub fn mutability(&self) -> Mutability {
match *self {
BorrowKind::Shared | BorrowKind::Shallow => Mutability::Not,
BorrowKind::Shared | BorrowKind::Fake => Mutability::Not,
BorrowKind::Mut { .. } => Mutability::Mut,
}
}
@ -451,7 +451,7 @@ impl BorrowKind {
pub fn allows_two_phase_borrow(&self) -> bool {
match *self {
BorrowKind::Shared
| BorrowKind::Shallow
| BorrowKind::Fake
| BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::ClosureCapture } => {
false
}

View file

@ -123,7 +123,7 @@ pub enum AnalysisPhase {
/// * [`TerminatorKind::FalseEdge`]
/// * [`StatementKind::FakeRead`]
/// * [`StatementKind::AscribeUserType`]
/// * [`Rvalue::Ref`] with `BorrowKind::Shallow`
/// * [`Rvalue::Ref`] with `BorrowKind::Fake`
///
/// Furthermore, `Deref` projections must be the first projection within any place (if they
/// appear at all)
@ -182,7 +182,7 @@ pub enum BorrowKind {
/// should not prevent `if let None = x { ... }`, for example, because the
/// mutating `(*x as Some).0` can't affect the discriminant of `x`.
/// We can also report errors with this kind of borrow differently.
Shallow,
Fake,
/// Data is mutable and not aliasable.
Mut { kind: MutBorrowKind },

View file

@ -273,7 +273,7 @@ impl BorrowKind {
// We have no type corresponding to a shallow borrow, so use
// `&` as an approximation.
BorrowKind::Shallow => hir::Mutability::Not,
BorrowKind::Fake => hir::Mutability::Not,
}
}
}

View file

@ -647,8 +647,8 @@ macro_rules! make_mir_visitor {
BorrowKind::Shared => PlaceContext::NonMutatingUse(
NonMutatingUseContext::SharedBorrow
),
BorrowKind::Shallow => PlaceContext::NonMutatingUse(
NonMutatingUseContext::ShallowBorrow
BorrowKind::Fake => PlaceContext::NonMutatingUse(
NonMutatingUseContext::FakeBorrow
),
BorrowKind::Mut { .. } =>
PlaceContext::MutatingUse(MutatingUseContext::Borrow),
@ -1253,8 +1253,8 @@ pub enum NonMutatingUseContext {
Move,
/// Shared borrow.
SharedBorrow,
/// Shallow borrow.
ShallowBorrow,
/// A fake borrow.
FakeBorrow,
/// AddressOf for *const pointer.
AddressOf,
/// PlaceMention statement.
@ -1333,7 +1333,7 @@ impl PlaceContext {
matches!(
self,
PlaceContext::NonMutatingUse(
NonMutatingUseContext::SharedBorrow | NonMutatingUseContext::ShallowBorrow
NonMutatingUseContext::SharedBorrow | NonMutatingUseContext::FakeBorrow
) | PlaceContext::MutatingUse(MutatingUseContext::Borrow)
)
}

View file

@ -20,8 +20,8 @@ impl<'tcx> TyCtxt<'tcx> {
where
T: TypeFoldable<TyCtxt<'tcx>>,
{
// If there's nothing to erase avoid performing the query at all
if !value.has_type_flags(TypeFlags::HAS_LATE_BOUND | TypeFlags::HAS_FREE_REGIONS) {
// If there's nothing to erase or anonymize, avoid performing the query at all
if !value.has_type_flags(TypeFlags::HAS_BINDER_VARS | TypeFlags::HAS_FREE_REGIONS) {
return value;
}
debug!("erase_regions({:?})", value);

View file

@ -34,26 +34,6 @@ impl FlagComputation {
result.flags
}
pub fn bound_var_flags(vars: &ty::List<ty::BoundVariableKind>) -> FlagComputation {
let mut computation = FlagComputation::new();
for bv in vars {
match bv {
ty::BoundVariableKind::Ty(_) => {
computation.flags |= TypeFlags::HAS_TY_LATE_BOUND;
}
ty::BoundVariableKind::Region(_) => {
computation.flags |= TypeFlags::HAS_RE_LATE_BOUND;
}
ty::BoundVariableKind::Const => {
computation.flags |= TypeFlags::HAS_CT_LATE_BOUND;
}
}
}
computation
}
fn add_flags(&mut self, flags: TypeFlags) {
self.flags = self.flags | flags;
}
@ -77,7 +57,11 @@ impl FlagComputation {
where
F: FnOnce(&mut Self, T),
{
let mut computation = FlagComputation::bound_var_flags(value.bound_vars());
let mut computation = FlagComputation::new();
if !value.bound_vars().is_empty() {
computation.add_flags(TypeFlags::HAS_BINDER_VARS);
}
f(&mut computation, value.skip_binder());

View file

@ -1097,8 +1097,10 @@ impl<'tcx> Ty<'tcx> {
// This doesn't depend on regions, so try to minimize distinct
// query keys used.
// If normalization fails, we just use `query_ty`.
let query_ty =
tcx.try_normalize_erasing_regions(param_env, query_ty).unwrap_or(query_ty);
debug_assert!(!param_env.has_infer());
let query_ty = tcx
.try_normalize_erasing_regions(param_env, query_ty)
.unwrap_or_else(|_| tcx.erase_regions(query_ty));
tcx.needs_drop_raw(param_env.and(query_ty))
}
@ -1287,7 +1289,6 @@ pub fn needs_drop_components<'tcx>(
| ty::FnDef(..)
| ty::FnPtr(_)
| ty::Char
| ty::GeneratorWitness(..)
| ty::RawPtr(_)
| ty::Ref(..)
| ty::Str => Ok(SmallVec::new()),
@ -1327,7 +1328,8 @@ pub fn needs_drop_components<'tcx>(
| ty::Placeholder(..)
| ty::Infer(_)
| ty::Closure(..)
| ty::Generator(..) => Ok(smallvec![ty]),
| ty::Generator(..)
| ty::GeneratorWitness(..) => Ok(smallvec![ty]),
}
}

View file

@ -494,15 +494,11 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for HasTypeFlagsVisitor {
&mut self,
t: &Binder<'tcx, T>,
) -> ControlFlow<Self::BreakTy> {
// If we're looking for any of the HAS_*_LATE_BOUND flags, we need to
// additionally consider the bound vars on the binder itself, even if
// the contents of a the binder (e.g. a `TraitRef`) doesn't reference
// the bound vars.
if self.flags.intersects(TypeFlags::HAS_LATE_BOUND) {
let bound_var_flags = FlagComputation::bound_var_flags(t.bound_vars());
if bound_var_flags.flags.intersects(self.flags) {
return ControlFlow::Break(FoundFlags);
}
// If we're looking for the HAS_BINDER_VARS flag, check if the
// binder has vars. This won't be present in the binder's bound
// value, so we need to check here too.
if self.flags.intersects(TypeFlags::HAS_BINDER_VARS) && !t.bound_vars().is_empty() {
return ControlFlow::Break(FoundFlags);
}
t.super_visit_with(self)

View file

@ -690,7 +690,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
fake_borrow_temp.into(),
Rvalue::Ref(
tcx.lifetimes.re_erased,
BorrowKind::Shallow,
BorrowKind::Fake,
Place { local: base_place.local, projection },
),
);

View file

@ -2004,7 +2004,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let re_erased = tcx.lifetimes.re_erased;
let scrutinee_source_info = self.source_info(scrutinee_span);
for &(place, temp) in fake_borrows {
let borrow = Rvalue::Ref(re_erased, BorrowKind::Shallow, place);
let borrow = Rvalue::Ref(re_erased, BorrowKind::Fake, place);
self.cfg.push_assign(block, scrutinee_source_info, Place::from(temp), borrow);
}

View file

@ -259,7 +259,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
);
};
match borrow_kind {
BorrowKind::Shallow | BorrowKind::Shared => {
BorrowKind::Fake | BorrowKind::Shared => {
if !ty.is_freeze(self.tcx, self.param_env) {
self.requires_unsafe(pat.span, BorrowOfLayoutConstrainedField);
}
@ -446,7 +446,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
visit::walk_expr(&mut visitor, expr);
if visitor.found {
match borrow_kind {
BorrowKind::Shallow | BorrowKind::Shared
BorrowKind::Fake | BorrowKind::Shared
if !self.thir[arg].ty.is_freeze(self.tcx, self.param_env) =>
{
self.requires_unsafe(expr.span, BorrowOfLayoutConstrainedField)
@ -454,7 +454,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
BorrowKind::Mut { .. } => {
self.requires_unsafe(expr.span, MutationOfLayoutConstrainedField)
}
BorrowKind::Shallow | BorrowKind::Shared => {}
BorrowKind::Fake | BorrowKind::Shared => {}
}
}
}

View file

@ -5,7 +5,8 @@ use rustc_middle::mir::*;
use crate::{AnalysisDomain, GenKill, GenKillAnalysis};
/// A dataflow analysis that tracks whether a pointer or reference could possibly exist that points
/// to a given local.
/// to a given local. This analysis ignores fake borrows, so it should not be used by
/// borrowck.
///
/// At present, this is used as a very limited form of alias analysis. For example,
/// `MaybeBorrowedLocals` is used to compute which locals are live during a yield expression for
@ -91,13 +92,17 @@ where
self.super_rvalue(rvalue, location);
match rvalue {
Rvalue::AddressOf(_, borrowed_place) | Rvalue::Ref(_, _, borrowed_place) => {
// We ignore fake borrows as these get removed after analysis and shouldn't effect
// the layout of generators.
Rvalue::AddressOf(_, borrowed_place)
| Rvalue::Ref(_, BorrowKind::Mut { .. } | BorrowKind::Shared, borrowed_place) => {
if !borrowed_place.is_indirect() {
self.trans.gen(borrowed_place.local);
}
}
Rvalue::Cast(..)
| Rvalue::Ref(_, BorrowKind::Fake, _)
| Rvalue::ShallowInitBox(..)
| Rvalue::Use(..)
| Rvalue::ThreadLocalRef(..)

View file

@ -201,7 +201,7 @@ impl DefUse {
| NonMutatingUseContext::Inspect
| NonMutatingUseContext::Move
| NonMutatingUseContext::PlaceMention
| NonMutatingUseContext::ShallowBorrow
| NonMutatingUseContext::FakeBorrow
| NonMutatingUseContext::SharedBorrow,
) => Some(DefUse::Use),

View file

@ -4,13 +4,13 @@
//!
//! - [`AscribeUserType`]
//! - [`FakeRead`]
//! - [`Assign`] statements with a [`Shallow`] borrow
//! - [`Assign`] statements with a [`Fake`] borrow
//!
//! [`AscribeUserType`]: rustc_middle::mir::StatementKind::AscribeUserType
//! [`Assign`]: rustc_middle::mir::StatementKind::Assign
//! [`FakeRead`]: rustc_middle::mir::StatementKind::FakeRead
//! [`Nop`]: rustc_middle::mir::StatementKind::Nop
//! [`Shallow`]: rustc_middle::mir::BorrowKind::Shallow
//! [`Fake`]: rustc_middle::mir::BorrowKind::Fake
use crate::MirPass;
use rustc_middle::mir::{Body, BorrowKind, Rvalue, StatementKind, TerminatorKind};
@ -24,7 +24,7 @@ impl<'tcx> MirPass<'tcx> for CleanupPostBorrowck {
for statement in basic_block.statements.iter_mut() {
match statement.kind {
StatementKind::AscribeUserType(..)
| StatementKind::Assign(box (_, Rvalue::Ref(_, BorrowKind::Shallow, _)))
| StatementKind::Assign(box (_, Rvalue::Ref(_, BorrowKind::Fake, _)))
| StatementKind::FakeRead(..) => statement.make_nop(),
_ => (),
}

View file

@ -683,7 +683,7 @@ impl<'tcx> Visitor<'tcx> for CanConstProp {
// These can't ever be propagated under any scheme, as we can't reason about indirect
// mutation.
| NonMutatingUse(NonMutatingUseContext::SharedBorrow)
| NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
| NonMutatingUse(NonMutatingUseContext::FakeBorrow)
| NonMutatingUse(NonMutatingUseContext::AddressOf)
| MutatingUse(MutatingUseContext::Borrow)
| MutatingUse(MutatingUseContext::AddressOf) => {

View file

@ -131,7 +131,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
let observes_address = match ctxt {
PlaceContext::NonMutatingUse(
NonMutatingUseContext::SharedBorrow
| NonMutatingUseContext::ShallowBorrow
| NonMutatingUseContext::FakeBorrow
| NonMutatingUseContext::AddressOf,
) => true,
// For debuginfo, merging locals is ok.

View file

@ -583,6 +583,14 @@ struct LivenessInfo {
storage_liveness: IndexVec<BasicBlock, Option<BitSet<Local>>>,
}
/// Computes which locals have to be stored in the state-machine for the
/// given coroutine.
///
/// The basic idea is as follows:
/// - a local is live until we encounter a `StorageDead` statement. In
/// case none exist, the local is considered to be always live.
/// - a local has to be stored if it is either directly used after the
/// the suspend point, or if it is live and has been previously borrowed.
fn locals_live_across_suspend_points<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
@ -1392,16 +1400,15 @@ pub(crate) fn mir_generator_witnesses<'tcx>(
// The first argument is the generator type passed by value
let gen_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty;
// Get the interior types and args which typeck computed
let movable = match *gen_ty.kind() {
ty::Generator(_, _, movability) => movability == hir::Movability::Movable,
ty::Error(_) => return None,
_ => span_bug!(body.span, "unexpected generator type {}", gen_ty),
};
// When first entering the generator, move the resume argument into its new local.
let always_live_locals = always_storage_live_locals(&body);
// The witness simply contains all locals live across suspend points.
let always_live_locals = always_storage_live_locals(&body);
let liveness_info = locals_live_across_suspend_points(tcx, body, &always_live_locals, movable);
// Extract locals which are live across suspension point into `layout`

View file

@ -247,7 +247,7 @@ impl<'tcx> Visitor<'tcx> for SsaVisitor<'_> {
// so we have to remove them too.
PlaceContext::NonMutatingUse(
NonMutatingUseContext::SharedBorrow
| NonMutatingUseContext::ShallowBorrow
| NonMutatingUseContext::FakeBorrow
| NonMutatingUseContext::AddressOf,
)
| PlaceContext::MutatingUse(_) => {

View file

@ -335,7 +335,7 @@ impl<'tcx> Stable<'tcx> for mir::BorrowKind {
use mir::BorrowKind::*;
match *self {
Shared => stable_mir::mir::BorrowKind::Shared,
Shallow => stable_mir::mir::BorrowKind::Shallow,
Fake => stable_mir::mir::BorrowKind::Fake,
Mut { kind } => stable_mir::mir::BorrowKind::Mut { kind: kind.stable(tables) },
}
}

View file

@ -14,6 +14,7 @@ use crate::solve::EvalCtxt;
//
// For types with an "existential" binder, i.e. generator witnesses, we also
// instantiate the binder with placeholders eagerly.
#[instrument(level = "debug", skip(ecx), ret)]
pub(in crate::solve) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
ecx: &EvalCtxt<'_, 'tcx>,
ty: Ty<'tcx>,
@ -107,6 +108,7 @@ pub(in crate::solve) fn replace_erased_lifetimes_with_bound_vars<'tcx>(
ty::Binder::bind_with_vars(ty, bound_vars)
}
#[instrument(level = "debug", skip(ecx), ret)]
pub(in crate::solve) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
ecx: &EvalCtxt<'_, 'tcx>,
ty: Ty<'tcx>,
@ -152,6 +154,7 @@ pub(in crate::solve) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
}
}
#[instrument(level = "debug", skip(ecx), ret)]
pub(in crate::solve) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
ecx: &EvalCtxt<'_, 'tcx>,
ty: Ty<'tcx>,

View file

@ -133,7 +133,7 @@ pub fn compute_dropck_outlives_inner<'tcx>(
result.overflows.len(),
ty_stack.len()
);
dtorck_constraint_for_ty_inner(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?;
dtorck_constraint_for_ty_inner(tcx, param_env, DUMMY_SP, depth, ty, &mut constraints)?;
// "outlives" represent types/regions that may be touched
// by a destructor.
@ -185,16 +185,15 @@ pub fn compute_dropck_outlives_inner<'tcx>(
/// Returns a set of constraints that needs to be satisfied in
/// order for `ty` to be valid for destruction.
#[instrument(level = "debug", skip(tcx, param_env, span, constraints))]
pub fn dtorck_constraint_for_ty_inner<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'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(());
@ -224,13 +223,13 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
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)
dtorck_constraint_for_ty_inner(tcx, param_env, span, 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)?;
dtorck_constraint_for_ty_inner(tcx, param_env, span, depth + 1, ty, constraints)?;
}
Ok::<_, NoSolution>(())
})?,
@ -249,7 +248,14 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
rustc_data_structures::stack::ensure_sufficient_stack(|| {
for ty in args.as_closure().upvar_tys() {
dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?;
dtorck_constraint_for_ty_inner(
tcx,
param_env,
span,
depth + 1,
ty,
constraints,
)?;
}
Ok::<_, NoSolution>(())
})?
@ -278,8 +284,8 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
// only take place through references with lifetimes
// derived from lifetimes attached to the upvars and resume
// argument, and we *do* incorporate those here.
if !args.as_generator().is_valid() {
let args = args.as_generator();
if !args.is_valid() {
// By the time this code runs, all type variables ought to
// be fully resolved.
tcx.sess.delay_span_bug(
@ -289,10 +295,13 @@ pub fn dtorck_constraint_for_ty_inner<'tcx>(
return Err(NoSolution);
}
constraints
.outlives
.extend(args.as_generator().upvar_tys().iter().map(ty::GenericArg::from));
constraints.outlives.push(args.as_generator().resume_ty().into());
// While we conservatively assume that all coroutines require drop
// to avoid query cycles during MIR building, we can check the actual
// witness during borrowck to avoid unnecessary liveness constraints.
if args.witness().needs_drop(tcx, tcx.erase_regions(param_env)) {
constraints.outlives.extend(args.upvar_tys().iter().map(ty::GenericArg::from));
constraints.outlives.push(args.resume_ty().into());
}
}
ty::Adt(def, args) => {

View file

@ -34,6 +34,7 @@ pub(crate) fn adt_dtorck_constraint(
) -> Result<&DropckConstraint<'_>, NoSolution> {
let def = tcx.adt_def(def_id);
let span = tcx.def_span(def_id);
let param_env = tcx.param_env(def_id);
debug!("dtorck_constraint: {:?}", def);
if def.is_manually_drop() {
@ -55,7 +56,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).instantiate_identity();
dtorck_constraint_for_ty_inner(tcx, span, fty, 0, fty, &mut result)?;
dtorck_constraint_for_ty_inner(tcx, param_env, span, 0, fty, &mut result)?;
}
result.outlives.extend(tcx.destructor_constraints(def));
dedup_dtorck_constraint(&mut result);

View file

@ -66,6 +66,9 @@ fn has_significant_drop_raw<'tcx>(
struct NeedsDropTypes<'tcx, F> {
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
// Whether to reveal coroutine witnesses, this is set
// to `false` unless we compute `needs_drop` for a coroutine witness.
reveal_coroutine_witnesses: bool,
query_ty: Ty<'tcx>,
seen_tys: FxHashSet<Ty<'tcx>>,
/// A stack of types left to process, and the recursion depth when we
@ -89,6 +92,7 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> {
Self {
tcx,
param_env,
reveal_coroutine_witnesses: false,
seen_tys,
query_ty: ty,
unchecked_tys: vec![(ty, 0)],
@ -133,8 +137,31 @@ where
// The information required to determine whether a generator has drop is
// computed on MIR, while this very method is used to build MIR.
// To avoid cycles, we consider that generators always require drop.
ty::Generator(..) => {
return Some(Err(AlwaysRequiresDrop));
//
// HACK: Because we erase regions contained in the coroutine witness, we
// have to conservatively assume that every region captured by the
// coroutine has to be live when dropped. This results in a lot of
// undesirable borrowck errors. During borrowck, we call `needs_drop`
// for the coroutine witness and check whether any of the contained types
// need to be dropped, and only require the captured types to be live
// if they do.
ty::Generator(_, args, _) => {
if self.reveal_coroutine_witnesses {
queue_type(self, args.as_generator().witness());
} else {
return Some(Err(AlwaysRequiresDrop));
}
}
ty::GeneratorWitness(def_id, args) => {
if let Some(witness) = tcx.mir_generator_witnesses(def_id) {
self.reveal_coroutine_witnesses = true;
for field_ty in &witness.field_tys {
queue_type(
self,
EarlyBinder::bind(field_ty.ty).instantiate(tcx, args),
);
}
}
}
_ if component.is_copy_modulo_regions(tcx, self.param_env) => (),
@ -191,7 +218,6 @@ where
| ty::FnPtr(..)
| ty::Tuple(_)
| ty::Bound(..)
| ty::GeneratorWitness(..)
| ty::Never
| ty::Infer(_)
| ty::Error(_) => {

View file

@ -299,6 +299,9 @@ bitflags! {
/// Does this have `Generator` or `GeneratorWitness`?
const HAS_TY_GENERATOR = 1 << 23;
/// Does this have any binders with bound vars (e.g. that need to be anonymized)?
const HAS_BINDER_VARS = 1 << 24;
}
}

View file

@ -151,7 +151,6 @@ pub enum TyKind<I: Interner> {
/// the type of the generator, we convert them to higher ranked
/// lifetimes bound by the witness itself.
///
/// This variant is only using when `drop_tracking_mir` is set.
/// This contains the `DefId` and the `GenericArgsRef` of the generator.
/// The actual witness types are computed on MIR by the `mir_generator_witnesses` query.
///

View file

@ -363,9 +363,10 @@ pub enum BorrowKind {
Shared,
/// The immediately borrowed place must be immutable, but projections from
/// it don't need to be. For example, a shallow borrow of `a.b` doesn't
/// it don't need to be. This is used to prevent match guards from replacing
/// the scrutinee. For example, a fake borrow of `a.b` doesn't
/// conflict with a mutable borrow of `a.b.c`.
Shallow,
Fake,
/// Data is mutable and not aliasable.
Mut {

View file

@ -51,7 +51,7 @@ fn full_tested_match() -> () {
bb5: {
StorageLive(_6);
_6 = &((_2 as Some).0: i32);
_4 = &shallow _2;
_4 = &fake _2;
StorageLive(_7);
_7 = guard() -> [return: bb6, unwind: bb12];
}

View file

@ -57,7 +57,7 @@ fn full_tested_match2() -> () {
bb5: {
StorageLive(_6);
_6 = &((_2 as Some).0: i32);
_4 = &shallow _2;
_4 = &fake _2;
StorageLive(_7);
_7 = guard() -> [return: bb6, unwind: bb12];
}

View file

@ -78,7 +78,7 @@ fn main() -> () {
bb8: {
StorageLive(_7);
_7 = &((_2 as Some).0: i32);
_5 = &shallow _2;
_5 = &fake _2;
StorageLive(_8);
_8 = guard() -> [return: bb9, unwind: bb20];
}
@ -120,7 +120,7 @@ fn main() -> () {
bb14: {
StorageLive(_11);
_11 = &((_2 as Some).0: i32);
_5 = &shallow _2;
_5 = &fake _2;
StorageLive(_12);
StorageLive(_13);
_13 = (*_11);

View file

@ -80,8 +80,8 @@
_6 = &(_2.1: bool);
StorageLive(_8);
_8 = &(_2.2: std::string::String);
- _3 = &shallow (_2.0: bool);
- _4 = &shallow (_2.1: bool);
- _3 = &fake (_2.0: bool);
- _4 = &fake (_2.1: bool);
StorageLive(_9);
StorageLive(_10);
_10 = _1;
@ -137,8 +137,8 @@
_6 = &(_2.0: bool);
StorageLive(_8);
_8 = &(_2.2: std::string::String);
- _3 = &shallow (_2.0: bool);
- _4 = &shallow (_2.1: bool);
- _3 = &fake (_2.0: bool);
- _4 = &fake (_2.1: bool);
StorageLive(_12);
StorageLive(_13);
_13 = _1;

View file

@ -80,8 +80,8 @@
_6 = &(_2.1: bool);
StorageLive(_8);
_8 = &(_2.2: std::string::String);
- _3 = &shallow (_2.0: bool);
- _4 = &shallow (_2.1: bool);
- _3 = &fake (_2.0: bool);
- _4 = &fake (_2.1: bool);
StorageLive(_9);
StorageLive(_10);
_10 = _1;
@ -137,8 +137,8 @@
_6 = &(_2.0: bool);
StorageLive(_8);
_8 = &(_2.2: std::string::String);
- _3 = &shallow (_2.0: bool);
- _4 = &shallow (_2.1: bool);
- _3 = &fake (_2.0: bool);
- _4 = &fake (_2.1: bool);
StorageLive(_12);
StorageLive(_13);
_13 = _1;

View file

@ -68,7 +68,7 @@ fn main() -> () {
}
bb9: {
_8 = &shallow _1;
_8 = &fake _1;
StorageLive(_9);
_9 = _2;
switchInt(move _9) -> [0: bb11, otherwise: bb10];

View file

@ -34,10 +34,10 @@
}
bb4: {
- _4 = &shallow _1;
- _5 = &shallow (*((_1 as Some).0: &&i32));
- _6 = &shallow ((_1 as Some).0: &&i32);
- _7 = &shallow (*(*((_1 as Some).0: &&i32)));
- _4 = &fake _1;
- _5 = &fake (*((_1 as Some).0: &&i32));
- _6 = &fake ((_1 as Some).0: &&i32);
- _7 = &fake (*(*((_1 as Some).0: &&i32)));
+ nop;
+ nop;
+ nop;

View file

@ -34,10 +34,10 @@
}
bb4: {
- _4 = &shallow _1;
- _5 = &shallow (*((_1 as Some).0: &&i32));
- _6 = &shallow ((_1 as Some).0: &&i32);
- _7 = &shallow (*(*((_1 as Some).0: &&i32)));
- _4 = &fake _1;
- _5 = &fake (*((_1 as Some).0: &&i32));
- _6 = &fake ((_1 as Some).0: &&i32);
- _7 = &fake (*(*((_1 as Some).0: &&i32)));
+ nop;
+ nop;
+ nop;

View file

@ -0,0 +1,18 @@
// check-pass
// edition: 2021
// regression test for #116242.
use std::future;
fn main() {
let mut recv = future::ready(());
let _combined_fut = async {
let _ = || read(&mut recv);
};
drop(recv);
}
fn read<F: future::Future>(_: &mut F) -> F::Output {
todo!()
}

View file

@ -0,0 +1,23 @@
// check-pass
// edition: 2021
// regression test found while working on #117134.
use std::future;
fn main() {
let mut recv = future::ready(());
let _combined_fut = async {
let _ = || read(&mut recv);
};
let _uwu = (String::new(), _combined_fut);
// Dropping a coroutine as part of a more complex
// types should not add unnecessary liveness
// constraints.
drop(recv);
}
fn read<F: future::Future>(_: &mut F) -> F::Output {
todo!()
}

View file

@ -1,24 +1,16 @@
error[E0597]: `a` does not live long enough
--> $DIR/borrowing.rs:9:33
|
LL | let _b = {
| -- borrow later stored here
LL | let a = 3;
LL | Pin::new(&mut || yield &a).resume(())
| ----------^
| | |
| | borrowed value does not live long enough
| -- ^ borrowed value does not live long enough
| |
| value captured here by generator
| a temporary with access to the borrow is created here ...
LL |
LL | };
| -- ... and the borrow might be used here, when that temporary is dropped and runs the destructor for generator
| |
| `a` dropped here while still borrowed
|
= note: the temporary is part of an expression at the end of a block;
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
|
LL | let x = Pin::new(&mut || yield &a).resume(()); x
| +++++++ +++
| - `a` dropped here while still borrowed
error[E0597]: `a` does not live long enough
--> $DIR/borrowing.rs:16:20

View file

@ -1,4 +1,5 @@
// edition:2021
// check-pass
#![feature(generators)]
fn main() {
@ -6,6 +7,5 @@ fn main() {
|| {
let _c = || yield *&mut *x;
|| _ = &mut *x;
//~^ cannot borrow `*x` as mutable more than once at a time
};
}

View file

@ -1,18 +0,0 @@
error[E0499]: cannot borrow `*x` as mutable more than once at a time
--> $DIR/issue-110929-generator-conflict-error-ice.rs:8:9
|
LL | let _c = || yield *&mut *x;
| -- -- first borrow occurs due to use of `*x` in generator
| |
| first mutable borrow occurs here
LL | || _ = &mut *x;
| ^^ -- second borrow occurs due to use of `*x` in closure
| |
| second mutable borrow occurs here
LL |
LL | };
| - first borrow might be used here, when `_c` is dropped and runs the destructor for generator
error: aborting due to previous error
For more information about this error, try `rustc --explain E0499`.

View file

@ -4,10 +4,9 @@ error[E0499]: cannot borrow `thing` as mutable more than once at a time
LL | gen.as_mut().resume(&mut thing);
| ---------- first mutable borrow occurs here
LL | gen.as_mut().resume(&mut thing);
| ^^^^^^^^^^ second mutable borrow occurs here
LL |
LL | }
| - first borrow might be used here, when `gen` is dropped and runs the destructor for generator
| ------ ^^^^^^^^^^ second mutable borrow occurs here
| |
| first borrow later used by call
error: aborting due to previous error

View file

@ -0,0 +1,34 @@
// check-pass
// edition: 2021
// regression test for #117059
struct SendNotSync(*const ());
unsafe impl Send for SendNotSync {}
// impl !Sync for SendNotSync {} // automatically disabled
struct Inner {
stream: SendNotSync,
state: bool,
}
struct SendSync;
impl std::ops::Deref for SendSync {
type Target = Inner;
fn deref(&self) -> &Self::Target {
todo!();
}
}
async fn next() {
let inner = SendSync;
match inner.state {
true if false => {}
false => async {}.await,
_ => {}
}
}
fn is_send<T: Send>(_: T) {}
fn main() {
is_send(next())
}

View file

@ -0,0 +1,40 @@
// check-pass
trait Foo {
type Assoc;
fn do_it(_: &Self::Assoc)
where
for<'a> Self: Baz<'a>;
}
trait Baz<'a>: Foo {}
impl Foo for () {
type Assoc = Inherent;
// Ensure that the `for<'a> Self: Baz<'a>` predicate, which has
// a supertrait `for<'a> Self: Foo`, does not cause us to fail
// to normalize `Self::Assoc`.
fn do_it(x: &Self::Assoc)
where
for<'a> Self: Baz<'a>,
{
x.inherent();
}
}
struct Inherent;
impl Inherent {
fn inherent(&self) {}
}
// This trivial bound doesn't hold, but the unused lifetime tripped up that check after #117589, and
// showed up in its crater results (in `soa-derive 0.13.0`).
fn do_it()
where
for<'a> Inherent: Clone,
{
}
fn main() {}