Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use rustc_middle::ty::{
suggest_constraining_type_params,
};
use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
use rustc_session::parse::feature_err;
use rustc_span::def_id::{DefId, LocalDefId};
use rustc_span::hygiene::DesugaringKind;
use rustc_span::{BytePos, ExpnKind, Ident, MacroKind, Span, Symbol, kw, sym};
Expand Down Expand Up @@ -826,6 +827,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
"partial initialization isn't supported, fully initialize the binding with a \
default value and mutate it, or use `std::mem::MaybeUninit`",
);
if !tcx.features().partial_init_locals() {
feature_err(
tcx.sess,
sym::partial_init_locals,
span,
"#![feature(partial_init_locals)] might be able to \
enable the initialisation here",
)
.emit();
}
}
err.span_label(span, format!("{path} {used} here but it {isnt_initialized}"));

Expand Down
55 changes: 30 additions & 25 deletions compiler/rustc_borrowck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
/// access.
///
/// Returns `true` if an error is reported.
#[instrument(level = "debug", skip(self, state))]
fn access_place(
&mut self,
location: Location,
Expand Down Expand Up @@ -2000,9 +2001,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
//
// 1. Move of `a.b.c`, use of `a.b.c`
// 2. Move of `a.b.c`, use of `a.b.c.d` (without first reinitializing `a.b.c.d`)
// 3. Uninitialized `(a.b.c: &_)`, use of `*a.b.c`; note that with
// partial initialization support, one might have `a.x`
// initialized but not `a.b`.
// 3. Uninitialized `(a.b.c: &_)`, use of `*a.b.c`
//
// OK scenarios:
//
Expand Down Expand Up @@ -2201,6 +2200,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
debug!("check_if_assigned_path_is_moved place: {:?}", place);

// None case => assigning to `x` does not require `x` be initialized.
let tcx = self.infcx.tcx;
let partial_init_locals = tcx.features().partial_init_locals();
for (place_base, elem) in place.iter_projections().rev() {
match elem {
ProjectionElem::Index(_/*operand*/) |
Expand Down Expand Up @@ -2235,7 +2236,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
// if type of `P` has a dtor, then
// assigning to `P.f` requires `P` itself
// be already initialized
let tcx = self.infcx.tcx;
let base_ty = place_base.ty(self.body(), tcx).ty;
match base_ty.kind() {
ty::Adt(def, _) if def.has_dtor(tcx) => {
Expand All @@ -2248,6 +2248,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
break;
}

ty::Adt(def, _) if partial_init_locals && !def.has_dtor(tcx) => {}
ty::Tuple(..) if partial_init_locals => {}

// Once `let s; s.x = V; read(s.x);`,
// is allowed, remove this match arm.
ty::Adt(..) | ty::Tuple(..) => {
Expand Down Expand Up @@ -2351,6 +2354,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
/// Checks the permissions for the given place and read or write kind
///
/// Returns `true` if an error is reported.
#[instrument(level = "debug", skip(self, state))]
fn check_access_permissions(
&mut self,
(place, span): (Place<'tcx>, Span),
Expand All @@ -2359,11 +2363,6 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
state: &BorrowckDomain,
location: Location,
) -> bool {
debug!(
"check_access_permissions({:?}, {:?}, is_local_mutation_allowed: {:?})",
place, kind, is_local_mutation_allowed
);

let error_access;
let the_place_err;

Expand Down Expand Up @@ -2451,23 +2450,29 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
// partial initialization, do not complain about mutability
// errors except for actual mutation (as opposed to an attempt
// to do a partial initialization).
let previously_initialized = self.is_local_ever_initialized(place.local, state);

// at this point, we have set up the error reporting state.
if let Some(init_index) = previously_initialized {
if let (AccessKind::Mutate, Some(_)) = (error_access, place.as_local()) {
// If this is a mutate access to an immutable local variable with no projections
// report the error as an illegal reassignment
let init = &self.move_data.inits[init_index];
let assigned_span = init.span(self.body);
self.report_illegal_reassignment((place, span), assigned_span, place);
} else {
self.report_mutability_error(place, span, the_place_err, error_access, location)
}
true
let Some(init_index) = self.is_local_ever_initialized(place.local, state) else {
return false;
};
// NOTE(partial_init_locals): now we need to check again if the local is possibly uninitialised.
if self.root_cx.tcx.features().partial_init_locals()
&& let Some(mpi) = self.move_path_for_place(Place::from(place.local).as_ref())
&& state.uninits.contains(mpi)
{
// Partial init is still in progress, so mutable access on the local is not required.
return false;
}

// At this point, we have set up the error reporting state.
if let (AccessKind::Mutate, Some(_)) = (error_access, place.as_local()) {
// If this is a mutate access to an immutable local variable with no projections
// report the error as an illegal reassignment
let init = &self.move_data.inits[init_index];
let assigned_span = init.span(self.body);
self.report_illegal_reassignment((place, span), assigned_span, place);
} else {
false
self.report_mutability_error(place, span, the_place_err, error_access, location)
}
true
}

fn is_local_ever_initialized(&self, local: Local, state: &BorrowckDomain) -> Option<InitIndex> {
Expand Down Expand Up @@ -2511,12 +2516,12 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {

/// Whether this value can be written or borrowed mutably.
/// Returns the root place if the place passed in is a projection.
#[instrument(level = "debug", skip(self), ret)]
fn is_mutable(
&self,
place: PlaceRef<'tcx>,
is_local_mutation_allowed: LocalMutationIsAllowed,
) -> Result<RootPlace<'tcx>, PlaceRef<'tcx>> {
debug!("is_mutable: place={:?}, is_local...={:?}", place, is_local_mutation_allowed);
match place.last_projection() {
None => {
let local = &self.body.local_decls[place.local];
Expand Down
19 changes: 6 additions & 13 deletions compiler/rustc_borrowck/src/type_check/liveness/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::traits::ObligationCtxt;
use rustc_trait_selection::traits::query::dropck_outlives;
use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOp, TypeOpOutput};
use tracing::debug;
use tracing::{debug, instrument};

use crate::polonius;
use crate::region_infer::values;
Expand Down Expand Up @@ -544,25 +544,18 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> {
/// the regions in its type must be live at `location`. The
/// precise set will depend on the dropck constraints, and in
/// particular this takes `#[may_dangle]` into account.
#[instrument(
level = "debug",
skip(self, live_at),
fields(live_at = ?values::pretty_print_points(self.location_map, live_at.iter())))
]
fn add_drop_live_facts_for(
&mut self,
dropped_local: Local,
dropped_ty: Ty<'tcx>,
drop_locations: &[Location],
live_at: &IntervalSet<PointIndex>,
) {
debug!(
"add_drop_live_constraint(\
dropped_local={:?}, \
dropped_ty={:?}, \
drop_locations={:?}, \
live_at={:?})",
dropped_local,
dropped_ty,
drop_locations,
values::pretty_print_points(self.location_map, live_at.iter()),
);

let local_span = self.body().local_decls()[dropped_local].source_info.span;
let drop_data = self.drop_data.entry(dropped_ty).or_insert_with({
let typeck = &self.typeck;
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,8 @@ declare_features! (
(unstable, opaque_generic_const_args, "1.95.0", Some(151972)),
/// Allows using `#[optimize(X)]`.
(unstable, optimize_attribute, "1.34.0", Some(54882)),
/// Allows partial initialisation of no-Drop `struct`s and tuples.
(unstable, partial_init_locals, "CURRENT_RUSTC_VERSION", None),
/// Allows specifying nop padding on functions for dynamic patching.
(unstable, patchable_function_entry, "1.81.0", Some(123115)),
/// Experimental features that make `Pin` more ergonomic.
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_middle/src/ty/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,14 @@ impl<'tcx> TyCtxt<'tcx> {
| ty::AliasTermKind::ProjectionConst => None,
}
}

pub fn ty_can_partial_init_locals(self, ty: Ty<'tcx>) -> bool {
match ty.kind() {
ty::Tuple(..) => true,
ty::Adt(def, _) => !def.has_dtor(self),
_ => false,
}
}
}

struct OpaqueTypeExpander<'tcx> {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_mir_dataflow/src/drop_flag_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub fn on_lookup_result_bits<'tcx, F>(
}
}

/// Invoke `each_child` on each of the **descendants** of the `move_path_index`.
pub fn on_all_children_bits<'tcx, F>(
move_data: &MoveData<'tcx>,
move_path_index: MovePathIndex,
Expand Down
55 changes: 51 additions & 4 deletions compiler/rustc_mir_dataflow/src/impls/initialized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,55 @@ impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {

impl<'tcx> MaybeUninitializedPlaces<'_, 'tcx> {
fn update_bits(
&self,
state: &mut <Self as Analysis<'tcx>>::Domain,
path: MovePathIndex,
dfstate: DropFlagState,
) {
let partial_init_locals = self.tcx.features().partial_init_locals();
match dfstate {
DropFlagState::Absent => state.gen_(path),
DropFlagState::Present => state.kill(path),
DropFlagState::Present => {
if partial_init_locals {
self.kill_partial_init_ancestors(state, path);
} else {
state.kill(path);
}
}
}
}

/// Kill ancestors on the move path, which has no destructor and all children are definitely
/// initialised.
///
/// Call this function only when the language feature is enabled.
#[instrument(level = "debug", skip_all)]
fn kill_partial_init_ancestors(
&self,
state: &mut <Self as Analysis<'tcx>>::Domain,
mut path: MovePathIndex,
) {
loop {
state.kill(path);
debug!(?path, "kill");
path = if let Some(parent) = self.move_data.move_paths[path].parent {
parent
} else {
break;
};
if !self.tcx.ty_can_partial_init_locals(
self.move_data.move_paths[path].place.ty(self.body, self.tcx).ty,
) {
break;
}
let mut child = self.move_data.move_paths[path].first_child;
while let Some(child_mpi) = child {
if state.contains(child_mpi) {
debug!(?child_mpi, "found uninit, bail");
return;
}
child = self.move_data.move_paths[child_mpi].next_sibling;
}
}
}
}
Expand Down Expand Up @@ -519,7 +561,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
location: Location,
) {
drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| {
Self::update_bits(state, path, s)
self.update_bits(state, path, s)
});

// Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a
Expand All @@ -533,7 +575,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
location: Location,
) -> TerminatorEdges<'mir, 'tcx> {
drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| {
Self::update_bits(state, path, s)
self.update_bits(state, path, s)
});
if self.skip_unreachable_unwind.contains(location.block) {
let mir::TerminatorKind::Drop { target, unwind, .. } = terminator.kind else { bug!() };
Expand All @@ -550,14 +592,19 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
_block: mir::BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
let partial_init_locals = self.tcx.features().partial_init_locals();
return_places.for_each(|place| {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 0 (initialized).
on_lookup_result_bits(
self.move_data(),
self.move_data().rev_lookup.find(place.as_ref()),
|mpi| {
state.kill(mpi);
if partial_init_locals {
self.kill_partial_init_ancestors(state, mpi);
} else {
state.kill(mpi);
}
},
);
});
Expand Down
16 changes: 15 additions & 1 deletion compiler/rustc_mir_dataflow/src/move_paths/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
}
}

if let LookupResult::Exact(path) = self.data.rev_lookup.find(place) {
if let LookupResult::Exact(mut path) = self.data.rev_lookup.find(place) {
let init = self.data.inits.push(Init {
location: InitLocation::Statement(self.loc),
path,
Expand All @@ -588,6 +588,20 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {

self.data.init_path_map[path].push(init);
self.data.init_loc_map[self.loc].push(init);

// NOTE(partial_init_locals):
// Note that we can walk the partial init'ed paths and attach the init indices,
// so that they could be considered, albeit partially, ever-initialised.
if self.tcx.features().partial_init_locals() {
while let Some(parent) = self.data.move_paths[path].parent {
path = parent;
let place = self.data.move_paths[path].place;
if !self.tcx.ty_can_partial_init_locals(place.ty(self.body, self.tcx).ty) {
break;
}
self.data.init_path_map[path].push(init);
}
}
}
}
}
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,7 @@ symbols! {
param_attrs,
parent_label,
partial_cmp,
partial_init_locals,
partial_ord,
passes,
pat,
Expand Down
3 changes: 3 additions & 0 deletions tests/ui/async-await/partial-initialization-across-await.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ async fn noop() {}
async fn test_tuple() {
let mut t: (i32, i32);
t.0 = 42; //~ ERROR E0381
//~^ ERROR E0658
noop().await;
t.1 = 88;
let _ = t;
Expand All @@ -19,6 +20,7 @@ async fn test_tuple() {
async fn test_tuple_struct() {
let mut t: T;
t.0 = 42; //~ ERROR E0381
//~^ ERROR E0658
noop().await;
t.1 = 88;
let _ = t;
Expand All @@ -27,6 +29,7 @@ async fn test_tuple_struct() {
async fn test_struct() {
let mut t: S;
t.x = 42; //~ ERROR E0381
//~^ ERROR E0658
noop().await;
t.y = 88;
let _ = t;
Expand Down
Loading
Loading