Skip to content

[WIP] Structural inititalization #143625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
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
54 changes: 50 additions & 4 deletions compiler/rustc_borrowck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1946,6 +1946,11 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {

debug!("check_if_full_path_is_moved place: {:?}", place_span.0);
let (prefix, mpi) = self.move_path_closest_to(place_span.0);

if prefix == place_span.0 && !self.forbid_structural_initialization(mpi) {
return;
}

if maybe_uninits.contains(mpi) {
self.report_use_of_moved_or_uninitialized(
location,
Expand Down Expand Up @@ -1987,6 +1992,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
if (from..to).contains(offset) {
let uninit_child =
self.move_data.find_in_move_path_or_its_descendants(child_mpi, |mpi| {
// FIXME(structural_init) you can't partially init an array element, right?
maybe_uninits.contains(mpi)
});

Expand Down Expand Up @@ -2059,9 +2065,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {

debug!("check_if_path_or_subpath_is_moved place: {:?}", place_span.0);
if let Some(mpi) = self.move_path_for_place(place_span.0) {
let uninit_mpi = self
.move_data
.find_in_move_path_or_its_descendants(mpi, |mpi| maybe_uninits.contains(mpi));
let uninit_mpi = self.move_data.find_in_move_path_or_its_descendants(mpi, |mpi| {
maybe_uninits.contains(mpi) && self.forbid_structural_initialization(mpi)
});

if let Some(uninit_mpi) = uninit_mpi {
self.report_use_of_moved_or_uninitialized(
Expand All @@ -2075,6 +2081,46 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
}
}

fn forbid_structural_initialization(&self, mpi: MovePathIndex) -> bool {
// FIXME: cache this

let tcx = self.infcx.tcx;

if !tcx.features().structural_init() {
return true;
}

let path = &self.move_data.move_paths[mpi];

let field_count = match path.place.ty(self.body(), tcx).ty.kind() {
ty::Adt(adt, _) if adt.is_struct() && !adt.has_dtor(tcx) => {
let variant = adt.non_enum_variant();

if variant.field_list_has_applicable_non_exhaustive() {
return true;
}

variant.fields.len()
}
ty::Tuple(tys) => tys.len(),

_ => return true,
};

// A structurally initialized type is "uninit" but all of it's fields are init.
// This means all of it's fields must have MovePaths
// because fields that are never written to will not have MovePaths.
// Without this check, we may not detect that unwritten fields are uninit.
for field in (0..field_count).map(FieldIdx::from_usize) {
// FIXME WrapUnsafeBinder?
if self.move_data.rev_lookup.project(mpi, ProjectionElem::Field(field, ())).is_none() {
return true;
}
}

false
}

/// Currently MoveData does not store entries for all places in
/// the input MIR. For example it will currently filter out
/// places that are Copy; thus we do not track places of shared
Expand Down Expand Up @@ -2164,7 +2210,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {

// Once `let s; s.x = V; read(s.x);`,
// is allowed, remove this match arm.
ty::Adt(..) | ty::Tuple(..) => {
ty::Adt(..) | ty::Tuple(..) if !tcx.features().structural_init() => {
check_parent_of_field(self, location, place_base, span, state);
}

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 @@ -241,6 +241,8 @@ declare_features! (
(unstable, sized_hierarchy, "1.89.0", None),
/// Allows using the `#[stable]` and `#[unstable]` attributes.
(internal, staged_api, "1.0.0", None),
/// FIXME: needs description and tracking issue.
(unstable, structural_init, "CURRENT_RUSTC_VERSION", None),
/// Added for testing unstable lints; perma-unstable.
(internal, test_unstable_lint, "1.60.0", None),
/// Helps with formatting for `group_imports = "StdExternalCrate"`.
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_mir_dataflow/src/move_paths/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ impl<'tcx> MovePathLookup<'tcx> {
};

for (_, elem) in self.un_derefer.iter_projections(place) {
if let Some(&subpath) = self.projections.get(&(result, elem.lift())) {
if let Some(subpath) = self.project(result, elem.lift()) {
result = subpath;
} else {
return LookupResult::Parent(Some(result));
Expand All @@ -346,6 +346,10 @@ impl<'tcx> MovePathLookup<'tcx> {
) -> impl DoubleEndedIterator<Item = (Local, MovePathIndex)> {
self.locals.iter_enumerated().filter_map(|(l, &idx)| Some((l, idx?)))
}

pub fn project(&self, mpi: MovePathIndex, elem: ProjectionKind) -> Option<MovePathIndex> {
self.projections.get(&(mpi, elem)).copied()
}
}

impl<'tcx> MoveData<'tcx> {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2099,6 +2099,7 @@ symbols! {
struct_field_attributes,
struct_inherit,
struct_variant,
structural_init,
structural_match,
structural_peq,
sub,
Expand Down
2 changes: 0 additions & 2 deletions src/tools/tidy/src/issues.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2971,8 +2971,6 @@ ui/nll/issue-112604-closure-output-normalize.rs
ui/nll/issue-16223.rs
ui/nll/issue-21114-ebfull.rs
ui/nll/issue-21114-kixunil.rs
ui/nll/issue-21232-partial-init-and-erroneous-use.rs
ui/nll/issue-21232-partial-init-and-use.rs
ui/nll/issue-22323-temp-destruction.rs
ui/nll/issue-24535-allow-mutable-borrow-in-match-guard.rs
ui/nll/issue-27282-move-match-input-into-guard.rs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
//@ revisions: stable feature
//@[feature] run-pass
// gate-test-structural_init
#![cfg_attr(feature, feature(structural_init))]

// This test enumerates various cases of interest for partial
// [re]initialization of ADTs and tuples.
//
// See rust-lang/rust#21232, rust-lang/rust#54986, and rust-lang/rust#54987.
//
// All of tests in this file are expected to change from being
// rejected, at least under NLL (by rust-lang/rust#54986) to being
// **accepted** when rust-lang/rust#54987 is implemented.
// (That's why there are assertions in the code.)
// All of the tests in this file should fail to compile normally
// and compile with `feature(structural_init)`.
//
// See issue-21232-partial-init-and-erroneous-use.rs for cases of
// tests that are meant to continue failing to compile once
// rust-lang/rust#54987 is implemented.
// See structural_init_invalid.rs for cases of
// tests that are meant to continue failing to compile
// with `feature(structural_init)`.

struct S<Y> {
x: u32,
Expand All @@ -37,18 +38,22 @@ fn borrow_t(t: &T) { assert_eq!(t.0, 10); assert_eq!(*t.1, 20); }
fn move_t(t: T) { assert_eq!(t.0, 10); assert_eq!(*t.1, 20); }

struct Q<F> {
#[allow(dead_code)]
v: u32,
r: R<F>,
}

struct R<F> {
#[allow(dead_code)]
w: u32,
f: F,
}

impl<F> Q<F> { fn new(f: F) -> Self { Q { v: 0, r: R::new(f) } } }
impl<F> R<F> { fn new(f: F) -> Self { R { w: 0, f } } }

struct Empty;

// Axes to cover:
// * local/field: Is the structure in a local or a field
// * fully/partial/void: Are we fully initializing it before using any part?
Expand Down Expand Up @@ -94,65 +99,65 @@ macro_rules! use_part {

fn test_0000_local_fully_init_and_use_struct() {
let s: S<B>;
s.x = 10; s.y = Box::new(20); //~ ERROR E0381
s.x = 10; s.y = Box::new(20); //[stable]~ ERROR E0381
use_fully!(struct s);
}

fn test_0001_local_fully_init_and_use_tuple() {
let t: T;
t.0 = 10; t.1 = Box::new(20); //~ ERROR E0381
t.0 = 10; t.1 = Box::new(20); //[stable]~ ERROR E0381
use_fully!(tuple t);
}

fn test_0010_local_fully_reinit_and_use_struct() {
let mut s: S<B> = S::new(); drop(s);
s.x = 10; s.y = Box::new(20);
//~^ ERROR assign to part of moved value: `s` [E0382]
//[stable]~^ ERROR assign to part of moved value: `s` [E0382]
use_fully!(struct s);
}

fn test_0011_local_fully_reinit_and_use_tuple() {
let mut t: T = (0, Box::new(0)); drop(t);
t.0 = 10; t.1 = Box::new(20);
//~^ ERROR assign to part of moved value: `t` [E0382]
//[stable]~^ ERROR assign to part of moved value: `t` [E0382]
use_fully!(tuple t);
}

fn test_0100_local_partial_init_and_use_struct() {
let s: S<B>;
s.x = 10; //~ ERROR E0381
s.x = 10; //[stable]~ ERROR E0381
use_part!(struct s);
}

fn test_0101_local_partial_init_and_use_tuple() {
let t: T;
t.0 = 10; //~ ERROR E0381
t.0 = 10; //[stable]~ ERROR E0381
use_part!(tuple t);
}

fn test_0110_local_partial_reinit_and_use_struct() {
let mut s: S<B> = S::new(); drop(s);
s.x = 10;
//~^ ERROR assign to part of moved value: `s` [E0382]
//[stable]~^ ERROR assign to part of moved value: `s` [E0382]
use_part!(struct s);
}

fn test_0111_local_partial_reinit_and_use_tuple() {
let mut t: T = (0, Box::new(0)); drop(t);
t.0 = 10;
//~^ ERROR assign to part of moved value: `t` [E0382]
//[stable]~^ ERROR assign to part of moved value: `t` [E0382]
use_part!(tuple t);
}

fn test_0200_local_void_init_and_use_struct() {
let s: S<Void>;
s.x = 10; //~ ERROR E0381
s.x = 10; //[stable]~ ERROR E0381
use_part!(struct s);
}

fn test_0201_local_void_init_and_use_tuple() {
let t: Tvoid;
t.0 = 10; //~ ERROR E0381
t.0 = 10; //[stable]~ ERROR E0381
use_part!(tuple t);
}

Expand All @@ -167,65 +172,65 @@ fn test_0201_local_void_init_and_use_tuple() {

fn test_1000_field_fully_init_and_use_struct() {
let q: Q<S<B>>;
q.r.f.x = 10; q.r.f.y = Box::new(20); //~ ERROR E0381
q.r.f.x = 10; q.r.f.y = Box::new(20); //[stable]~ ERROR E0381
use_fully!(struct q.r.f);
}

fn test_1001_field_fully_init_and_use_tuple() {
let q: Q<T>;
q.r.f.0 = 10; q.r.f.1 = Box::new(20); //~ ERROR E0381
q.r.f.0 = 10; q.r.f.1 = Box::new(20); //[stable]~ ERROR E0381
use_fully!(tuple q.r.f);
}

fn test_1010_field_fully_reinit_and_use_struct() {
let mut q: Q<S<B>> = Q::new(S::new()); drop(q.r);
q.r.f.x = 10; q.r.f.y = Box::new(20);
//~^ ERROR assign to part of moved value: `q.r` [E0382]
//[stable]~^ ERROR assign to part of moved value: `q.r` [E0382]
use_fully!(struct q.r.f);
}

fn test_1011_field_fully_reinit_and_use_tuple() {
let mut q: Q<T> = Q::new((0, Box::new(0))); drop(q.r);
q.r.f.0 = 10; q.r.f.1 = Box::new(20);
//~^ ERROR assign to part of moved value: `q.r` [E0382]
//[stable]~^ ERROR assign to part of moved value: `q.r` [E0382]
use_fully!(tuple q.r.f);
}

fn test_1100_field_partial_init_and_use_struct() {
let q: Q<S<B>>;
q.r.f.x = 10; //~ ERROR E0381
q.r.f.x = 10; //[stable]~ ERROR E0381
use_part!(struct q.r.f);
}

fn test_1101_field_partial_init_and_use_tuple() {
let q: Q<T>;
q.r.f.0 = 10; //~ ERROR E0381
q.r.f.0 = 10; //[stable]~ ERROR E0381
use_part!(tuple q.r.f);
}

fn test_1110_field_partial_reinit_and_use_struct() {
let mut q: Q<S<B>> = Q::new(S::new()); drop(q.r);
q.r.f.x = 10;
//~^ ERROR assign to part of moved value: `q.r` [E0382]
//[stable]~^ ERROR assign to part of moved value: `q.r` [E0382]
use_part!(struct q.r.f);
}

fn test_1111_field_partial_reinit_and_use_tuple() {
let mut q: Q<T> = Q::new((0, Box::new(0))); drop(q.r);
q.r.f.0 = 10;
//~^ ERROR assign to part of moved value: `q.r` [E0382]
//[stable]~^ ERROR assign to part of moved value: `q.r` [E0382]
use_part!(tuple q.r.f);
}

fn test_1200_field_void_init_and_use_struct() {
let mut q: Q<S<Void>>;
q.r.f.x = 10; //~ ERROR E0381
let q: Q<S<Void>>;
q.r.f.x = 10; //[stable]~ ERROR E0381
use_part!(struct q.r.f);
}

fn test_1201_field_void_init_and_use_tuple() {
let mut q: Q<Tvoid>;
q.r.f.0 = 10; //~ ERROR E0381
let q: Q<Tvoid>;
q.r.f.0 = 10; //[stable]~ ERROR E0381
use_part!(tuple q.r.f);
}

Expand All @@ -242,7 +247,7 @@ fn issue_26996() {
let mut c = (1, "".to_owned());
match c {
c2 => {
c.0 = 2; //~ ERROR assign to part of moved value
c.0 = 2; //[stable]~ ERROR assign to part of moved value
assert_eq!(c2.0, 1);
}
}
Expand All @@ -252,20 +257,33 @@ fn issue_27021() {
let mut c = (1, (1, "".to_owned()));
match c {
c2 => {
(c.1).0 = 2; //~ ERROR assign to part of moved value
(c.1).0 = 2; //[stable]~ ERROR assign to part of moved value
assert_eq!((c2.1).0, 1);
}
}

let mut c = (1, (1, (1, "".to_owned())));
match c.1 {
c2 => {
((c.1).1).0 = 3; //~ ERROR assign to part of moved value
((c.1).1).0 = 3; //[stable]~ ERROR assign to part of moved value
assert_eq!((c2.1).0, 1);
}
}
}

// Strange case discovered during implementation.
// FIXME: not decided if this should compile or not.
fn test_empty_struct() {
let e: Empty;
drop(e); //[stable]~ ERROR used binding `e` isn't initialized [E0381]
}

#[expect(dropping_copy_types)]
fn test_empty_tuple() {
let t: ();
drop(t); //[stable]~ ERROR used binding `t` isn't initialized [E0381]
}

fn main() {
test_0000_local_fully_init_and_use_struct();
test_0001_local_fully_init_and_use_tuple();
Expand Down Expand Up @@ -294,4 +312,7 @@ fn main() {

issue_26996();
issue_27021();

test_empty_struct();
test_empty_tuple();
}
Loading
Loading