Skip to content

Commit 43aa35e

Browse files
authored
new-mut-ref: move/copy info for struct update tails (#2014)
1 parent 840fa61 commit 43aa35e

File tree

8 files changed

+371
-37
lines changed

8 files changed

+371
-37
lines changed

source/rust_verify/src/rust_to_vir_expr.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2480,7 +2480,11 @@ pub(crate) fn expr_to_vir_innermost<'tcx>(
24802480
ExprKind::Struct(qpath, fields, struct_tail) => {
24812481
let update = match struct_tail {
24822482
// Some(update) => Some(expr_to_vir(bctx, update, modifier)?),
2483-
StructTailExpr::Base(expr) => Some(expr_to_vir(bctx, expr, modifier)?.to_place()),
2483+
StructTailExpr::Base(tail_expr) => {
2484+
let place = expr_to_vir(bctx, tail_expr, modifier)?.to_place();
2485+
let tf = ctor_tail_get_taken_fields(bctx, expr)?;
2486+
Some(vir::ast::CtorUpdateTail { place: place, taken_fields: tf })
2487+
}
24842488
StructTailExpr::DefaultFields(..) => {
24852489
unsupported_err!(
24862490
expr.span,
@@ -3695,3 +3699,44 @@ pub(crate) fn borrow_mut_vir(
36953699
let typ = Arc::new(TypX::MutRef(place.typ.clone()));
36963700
bctx.spanned_typed_new(span, &typ, x)
36973701
}
3702+
3703+
fn ctor_tail_get_taken_fields<'tcx>(
3704+
bctx: &BodyCtxt<'tcx>,
3705+
expr: &Expr<'tcx>,
3706+
) -> Result<Arc<Vec<(vir::ast::Ident, UnfinalizedReadKind)>>, VirErr> {
3707+
let ExprKind::Struct(_, fields, _) = &expr.kind else {
3708+
crate::internal_err!(
3709+
expr.span,
3710+
"ctor_tail_get_taken_fields should only be called for ExprKind::Struct"
3711+
);
3712+
};
3713+
3714+
let ty = bctx.types.node_type(expr.hir_id);
3715+
let TyKind::Adt(adt_def, args) = ty.kind() else {
3716+
crate::internal_err!(expr.span, "Expected TyKind::Adt for struct expression");
3717+
};
3718+
3719+
if !adt_def.is_struct() {
3720+
crate::internal_err!(expr.span, "Expected struct for struct expression with tail");
3721+
}
3722+
let variant_def = adt_def.non_enum_variant();
3723+
3724+
let mut taken_fields = vec![];
3725+
// Iterate over all fields that are NOT present in the given struct expression.
3726+
for field_def in variant_def.fields.iter() {
3727+
if fields.iter().any(|f| f.ident.name == field_def.name) {
3728+
continue;
3729+
}
3730+
let ty = field_def.ty(bctx.ctxt.tcx, args);
3731+
let rk = if bctx.is_copy(ty) { vir::ast::ReadKind::Copy } else { vir::ast::ReadKind::Move };
3732+
let rk = UnfinalizedReadKind { preliminary_kind: rk, id: bctx.ctxt.unique_read_kind_id() };
3733+
let ident = field_ident_from_rust(field_def.name.as_str());
3734+
taken_fields.push((ident, rk));
3735+
}
3736+
3737+
if fields.len() + taken_fields.len() != variant_def.fields.len() {
3738+
crate::internal_err!(expr.span, "ctor_tail_get_taken_fields: field counts are wrong");
3739+
}
3740+
3741+
Ok(Arc::new(taken_fields))
3742+
}

source/rust_verify_test/tests/modes.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,3 +1527,37 @@ test_verify_one_file! {
15271527
}
15281528
} => Err(err) => assert_vir_error_msg(err, "cannot call function `crate::foo` with mode exec")
15291529
}
1530+
1531+
test_verify_one_file! {
1532+
#[test] fine_grained_checking_for_ctor_with_update verus_code! {
1533+
#[verifier::external_body]
1534+
tracked struct X { }
1535+
1536+
tracked struct Foo {
1537+
tracked tr: X,
1538+
ghost gh: X,
1539+
}
1540+
1541+
proof fn test(foo: Foo, tracked x: X) {
1542+
// This is ok because we only need the ghost field off of foo
1543+
let tracked foo2 = Foo { tr: x, .. foo };
1544+
}
1545+
} => Ok(())
1546+
}
1547+
1548+
test_verify_one_file! {
1549+
#[test] fine_grained_checking_for_ctor_with_update2 verus_code! {
1550+
#[verifier::external_body]
1551+
tracked struct X { }
1552+
1553+
tracked struct Foo {
1554+
tracked tr: X,
1555+
ghost gh: X,
1556+
}
1557+
1558+
proof fn test(foo: Foo, tracked x: X) {
1559+
// not ok, needs a tracked field of foo
1560+
let tracked foo2 = Foo { gh: x, .. foo };
1561+
}
1562+
} => Err(err) => assert_vir_error_msg(err, "expression has mode spec, expected mode proof")
1563+
}

source/rust_verify_test/tests/mut_refs.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2549,3 +2549,127 @@ test_verify_one_file_with_options! {
25492549
}
25502550
} => Err(err) => assert_fails(err, 4)
25512551
}
2552+
2553+
test_verify_one_file_with_options! {
2554+
#[test] ctor_with_update_tail ["new-mut-ref"] => verus_code! {
2555+
tracked struct TTPair<A, B> {
2556+
tracked a: A,
2557+
tracked b: B,
2558+
}
2559+
2560+
fn test1() {
2561+
let mut g1: Ghost<int> = Ghost(1);
2562+
let mut g2: Ghost<int> = Ghost(2);
2563+
let mut g3: Ghost<int> = Ghost(3);
2564+
2565+
let tracked p = TTPair {
2566+
a: &mut g1,
2567+
b: &mut g2,
2568+
};
2569+
2570+
proof {
2571+
*p.a.borrow_mut() = 4;
2572+
*p.b.borrow_mut() = 5;
2573+
}
2574+
2575+
let tracked p2 = TTPair {
2576+
a: &mut g3,
2577+
.. p
2578+
};
2579+
2580+
assert(has_resolved(p.b)); // FAILS
2581+
2582+
proof {
2583+
*p2.b.borrow_mut() = 4;
2584+
}
2585+
}
2586+
2587+
fn test2() {
2588+
let mut g1: Ghost<int> = Ghost(1);
2589+
let mut g2: Ghost<int> = Ghost(2);
2590+
let mut g3: Ghost<int> = Ghost(3);
2591+
2592+
let tracked p = TTPair {
2593+
a: &mut g1,
2594+
b: &mut g2,
2595+
};
2596+
2597+
proof {
2598+
*p.a.borrow_mut() = 4;
2599+
*p.b.borrow_mut() = 5;
2600+
}
2601+
2602+
let tracked p2 = TTPair {
2603+
a: &mut g3,
2604+
.. p
2605+
};
2606+
2607+
// ok, p.a was not moved
2608+
assert(has_resolved(p.a));
2609+
2610+
proof {
2611+
*p2.b.borrow_mut() = 4;
2612+
}
2613+
}
2614+
2615+
fn test3() {
2616+
let mut g1: Ghost<int> = Ghost(1);
2617+
let mut g2: Ghost<int> = Ghost(2);
2618+
let mut g3: Ghost<int> = Ghost(3);
2619+
2620+
let tracked p = TTPair {
2621+
a: &mut g1,
2622+
b: &mut g2,
2623+
};
2624+
2625+
proof {
2626+
*p.a.borrow_mut() = 4;
2627+
*p.b.borrow_mut() = 5;
2628+
}
2629+
2630+
let tracked p2 = TTPair {
2631+
a: &mut g3,
2632+
.. p
2633+
};
2634+
2635+
proof {
2636+
*p.a.borrow_mut() = 4;
2637+
*p2.a.borrow_mut() = 5;
2638+
*p2.b.borrow_mut() = 6;
2639+
}
2640+
2641+
assert(g1@ == 4);
2642+
assert(g3@ == 5);
2643+
assert(g2@ == 6);
2644+
2645+
assert(false); // FAILS
2646+
}
2647+
2648+
spec fn id<A>(a: A) -> A { a }
2649+
2650+
fn test4() {
2651+
let mut g1: Ghost<int> = Ghost(1);
2652+
let mut g2: Ghost<int> = Ghost(2);
2653+
let mut g3: Ghost<int> = Ghost(3);
2654+
2655+
let tracked p = TTPair {
2656+
a: &mut g1,
2657+
b: &mut g2,
2658+
};
2659+
2660+
proof {
2661+
*p.a.borrow_mut() = 4;
2662+
*p.b.borrow_mut() = 5;
2663+
}
2664+
2665+
let tracked ref_g3 = &mut g3;
2666+
// this doesn't do any moves because it's ghost mode:
2667+
let ghost p2 = TTPair {
2668+
a: id(ref_g3),
2669+
.. p
2670+
};
2671+
2672+
assert(has_resolved(p.b));
2673+
}
2674+
} => Err(err) => assert_fails(err, 2)
2675+
}

source/vir/src/ast.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,17 @@ pub enum AutospecUsage {
854854
Final,
855855
}
856856

857+
/// Represents the expression after the '..' in a "ctor update"
858+
/// like the `foo` in `{ a: e1, b: e2, .. foo }`.
859+
#[derive(Clone, Debug, Serialize, Deserialize, ToDebugSNode)]
860+
pub struct CtorUpdateTail {
861+
pub place: Place,
862+
/// This list needs to contain all the fields that are NOT explicitly listed.
863+
/// i.e., this is all the fields that are moved or copied out of the input struct.
864+
/// This information is needed by resolution analysis.
865+
pub taken_fields: Arc<Vec<(Ident, UnfinalizedReadKind)>>,
866+
}
867+
857868
/// Expression, similar to rustc_hir::Expr
858869
pub type Expr = Arc<SpannedTyped<ExprX>>;
859870
pub type Exprs = Arc<Vec<Expr>>;
@@ -883,7 +894,7 @@ pub enum ExprX {
883894
/// with field initializers Binders<Expr> and an optional ".." update expression.
884895
/// For tuple-style variants, the fields are named "_0", "_1", etc.
885896
/// Fields can appear **in any order** even for tuple variants.
886-
Ctor(Dt, Ident, Binders<Expr>, Option<Place>),
897+
Ctor(Dt, Ident, Binders<Expr>, Option<CtorUpdateTail>),
887898
/// Primitive 0-argument operation
888899
NullaryOpr(NullaryOpr),
889900
/// Primitive unary operation

source/vir/src/ast_simplify.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ use crate::ast::VarBinders;
88
use crate::ast::VarIdent;
99
use crate::ast::{
1010
AssocTypeImpl, AutospecUsage, BinaryOp, Binder, BuiltinSpecFun, ByRef, CallTarget, ChainedOp,
11-
Constant, CtorPrintStyle, Datatype, DatatypeTransparency, DatatypeX, Dt, Expr, ExprX, Exprs,
12-
Field, FieldOpr, Fun, Function, FunctionKind, Ident, InequalityOp, IntRange, ItemKind, Krate,
13-
KrateX, Mode, MultiOp, Path, Pattern, PatternBinding, PatternX, Place, PlaceX, SpannedTyped,
14-
Stmt, StmtX, TraitImpl, Typ, TypX, UnaryOp, UnaryOpr, Variant, VariantCheck, VirErr,
15-
Visibility,
11+
Constant, CtorPrintStyle, CtorUpdateTail, Datatype, DatatypeTransparency, DatatypeX, Dt, Expr,
12+
ExprX, Exprs, Field, FieldOpr, Fun, Function, FunctionKind, Ident, InequalityOp, IntRange,
13+
ItemKind, Krate, KrateX, Mode, MultiOp, Path, Pattern, PatternBinding, PatternX, Place, PlaceX,
14+
SpannedTyped, Stmt, StmtX, TraitImpl, Typ, TypX, UnaryOp, UnaryOpr, Variant, VariantCheck,
15+
VirErr, Visibility,
1616
};
1717
use crate::ast_util::{
1818
conjoin, disjoin, if_then_else, mk_eq, mk_ineq, place_to_expr, typ_args_for_datatype_typ,
@@ -471,7 +471,8 @@ fn simplify_one_expr(
471471
Ok(SpannedTyped::new(&expr.span, &expr.typ, call))
472472
}
473473
ExprX::Ctor(name, variant, partial_binders, Some(update)) => {
474-
let (temp_decl, update) = small_or_temp(state, &place_to_expr(update));
474+
let CtorUpdateTail { place, taken_fields: _ } = update;
475+
let (temp_decl, update) = small_or_temp(state, &place_to_expr(place));
475476
let mut decls: Vec<Stmt> = Vec::new();
476477
let mut binders: Vec<Binder<Expr>> = Vec::new();
477478
if temp_decl.len() == 0 {

source/vir/src/ast_visitor.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::ast::{
22
Arm, ArmX, Arms, AssocTypeImpl, AssocTypeImplX, BinaryOpr, CallTarget, CallTargetKind,
3-
Datatype, DatatypeX, Expr, ExprX, Exprs, Field, Function, FunctionKind, FunctionX,
4-
GenericBound, GenericBoundX, LoopInvariant, LoopInvariants, MaskSpec, NullaryOpr, Param,
5-
ParamX, Params, Pattern, PatternBinding, PatternX, Place, PlaceX, SpannedTyped, Stmt, StmtX,
6-
Trait, TraitImpl, TraitImplX, TraitX, Typ, TypDecorationArg, TypX, Typs, UnaryOpr, UnwindSpec,
7-
VarBinder, VarBinderX, VarBinders, VarIdent, Variant, VirErr,
3+
CtorUpdateTail, Datatype, DatatypeX, Expr, ExprX, Exprs, Field, Function, FunctionKind,
4+
FunctionX, GenericBound, GenericBoundX, LoopInvariant, LoopInvariants, MaskSpec, NullaryOpr,
5+
Param, ParamX, Params, Pattern, PatternBinding, PatternX, Place, PlaceX, SpannedTyped, Stmt,
6+
StmtX, Trait, TraitImpl, TraitImplX, TraitX, Typ, TypDecorationArg, TypX, Typs, UnaryOpr,
7+
UnwindSpec, VarBinder, VarBinderX, VarBinders, VarIdent, Variant, VirErr,
88
};
99
use crate::def::Spanned;
1010
use crate::messages::Span;
@@ -315,9 +315,9 @@ pub(crate) trait AstVisitor<R: Returner, Err, Scope: Scoper> {
315315
let oe = self.visit_opt_expr(opt_e)?;
316316
R::ret(|| expr_new(ExprX::Call(R::get(ct), R::get_vec_a(es), R::get_opt(oe))))
317317
}
318-
ExprX::Ctor(dt, id, binders, opt_e) => {
318+
ExprX::Ctor(dt, id, binders, opt_tail) => {
319319
let bs = self.visit_binders_expr(binders)?;
320-
let oe = self.visit_opt_place(opt_e)?;
320+
let oe = R::map_opt(opt_tail, &mut |p| self.visit_ctor_update_tail(p))?;
321321
R::ret(|| {
322322
expr_new(ExprX::Ctor(dt.clone(), id.clone(), R::get_vec_a(bs), R::get_opt(oe)))
323323
})
@@ -650,6 +650,15 @@ pub(crate) trait AstVisitor<R: Returner, Err, Scope: Scoper> {
650650
}
651651
}
652652

653+
fn visit_ctor_update_tail(
654+
&mut self,
655+
tail: &CtorUpdateTail,
656+
) -> Result<R::Ret<CtorUpdateTail>, Err> {
657+
let CtorUpdateTail { place, taken_fields } = tail;
658+
let place = self.visit_place(place)?;
659+
R::ret(|| CtorUpdateTail { place: R::get(place), taken_fields: taken_fields.clone() })
660+
}
661+
653662
fn visit_arms(&mut self, arms: &Arms) -> Result<R::Vec<Arm>, Err> {
654663
R::map_vec(arms, &mut |e| self.visit_arm(e))
655664
}

0 commit comments

Comments
 (0)