Skip to content

Commit 17528d1

Browse files
authored
Support ? for Option and Result (#950)
* support ? for Option * support Result based on Chris's suggestion
1 parent 3a20c67 commit 17528d1

File tree

7 files changed

+234
-5
lines changed

7 files changed

+234
-5
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate::prelude::*;
2+
use core::ops::Try;
3+
use core::ops::ControlFlow;
4+
use core::ops::FromResidual;
5+
use core::convert::Infallible;
6+
7+
verus!{
8+
9+
#[verifier(external_type_specification)]
10+
#[verifier::accept_recursive_types(B)]
11+
#[verifier::reject_recursive_types_in_ground_variants(C)]
12+
pub struct ExControlFlow<B, C>(ControlFlow<B, C>);
13+
14+
#[verifier(external_type_specification)]
15+
#[verifier(external_body)]
16+
pub struct ExInfallible(Infallible);
17+
18+
19+
#[verifier::external_fn_specification]
20+
pub fn ex_result_branch<T, E>(result: Result<T, E>) -> (cf: ControlFlow<<Result<T, E> as Try>::Residual, <Result<T, E> as Try>::Output>)
21+
ensures
22+
cf === match result {
23+
Ok(v) => ControlFlow::Continue(v),
24+
Err(e) => ControlFlow::Break(Err(e)),
25+
},
26+
{
27+
result.branch()
28+
}
29+
30+
#[verifier::external_fn_specification]
31+
pub fn ex_option_branch<T>(option: Option<T>) -> (cf: ControlFlow<<Option<T> as Try>::Residual, <Option<T> as Try>::Output>)
32+
ensures
33+
cf === match option {
34+
Some(v) => ControlFlow::Continue(v),
35+
None => ControlFlow::Break(None),
36+
},
37+
{
38+
option.branch()
39+
}
40+
41+
#[verifier::external_fn_specification]
42+
pub fn ex_option_from_residual<T>(option: Option<Infallible>) -> (option2: Option<T>)
43+
ensures
44+
option.is_none(),
45+
option2.is_none(),
46+
{
47+
Option::from_residual(option)
48+
}
49+
50+
pub spec fn spec_from<S, T>(value: T, ret: S) -> bool;
51+
52+
#[verifier::broadcast_forall]
53+
#[verifier::external_body]
54+
pub proof fn spec_from_blanket_identity<T>(t: T, s: T)
55+
ensures
56+
spec_from::<T, T>(t, s) ==> t == s
57+
{
58+
}
59+
60+
#[verifier::external_fn_specification]
61+
pub fn ex_result_from_residual<T, E, F: From<E>>(result: Result<Infallible, E>)
62+
-> (result2: Result<T, F>)
63+
ensures
64+
match (result, result2) {
65+
(Err(e), Err(e2)) => spec_from::<F, E>(e, e2),
66+
_ => false,
67+
},
68+
{
69+
Result::from_residual(result)
70+
}
71+
72+
}

source/pervasive/std_specs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod result;
44
pub mod option;
55
pub mod atomic;
66
pub mod bits;
7+
pub mod control_flow;
78

89
#[cfg(feature = "alloc")]
910
pub mod vec;

source/rust_verify/src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub fn enable_default_features_and_verus_attr(
114114
"register_tool",
115115
"tuple_trait",
116116
"custom_inner_attributes",
117+
"try_trait_v2",
117118
] {
118119
rustc_args.push("-Z".to_string());
119120
rustc_args.push(format!("crate-attr=feature({})", feature));

source/rust_verify/src/fn_call_to_vir.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use air::ast_util::str_ident;
2121
use rustc_ast::LitKind;
2222
use rustc_hir::def::Res;
2323
use rustc_hir::{Expr, ExprKind, Node, QPath};
24-
use rustc_middle::ty::{GenericArgKind, TyKind};
24+
use rustc_middle::ty::{GenericArg, GenericArgKind, TyKind};
2525
use rustc_span::def_id::DefId;
2626
use rustc_span::source_map::Spanned;
2727
use rustc_span::Span;
@@ -96,9 +96,6 @@ pub(crate) fn fn_call_to_vir<'tcx>(
9696
),
9797
);
9898
}
99-
Some(RustItem::TryTraitBranch) => {
100-
return err_span(expr.span, "Verus does not yet support the ? operator");
101-
}
10299
Some(RustItem::IntoIterFn) => {
103100
return err_span(
104101
expr.span,
@@ -173,6 +170,8 @@ pub(crate) fn fn_call_to_vir<'tcx>(
173170
// If the resolution is statically known, we record the resolved function for the
174171
// to be used by lifetime_generate.
175172

173+
let node_substs = fix_node_substs(tcx, bctx.types, node_substs, rust_item, &args, expr);
174+
176175
let target_kind = if tcx.trait_of_item(f).is_none() {
177176
vir::ast::CallTargetKind::Static
178177
} else {
@@ -1652,6 +1651,33 @@ fn mk_is_smaller_than<'tcx>(
16521651
return Ok(dec_exp);
16531652
}
16541653

1654+
pub(crate) fn fix_node_substs<'tcx, 'a>(
1655+
tcx: rustc_middle::ty::TyCtxt<'tcx>,
1656+
types: &'tcx rustc_middle::ty::TypeckResults<'tcx>,
1657+
node_substs: &'tcx rustc_middle::ty::List<rustc_middle::ty::GenericArg<'tcx>>,
1658+
rust_item: Option<RustItem>,
1659+
args: &'a [&'tcx Expr<'tcx>],
1660+
expr: &'a Expr<'tcx>,
1661+
) -> &'tcx rustc_middle::ty::List<rustc_middle::ty::GenericArg<'tcx>> {
1662+
match rust_item {
1663+
Some(RustItem::TryTraitBranch) => {
1664+
// I don't understand why, but in this case, node_substs is empty instead
1665+
// of having the type argument. Let's fix it here.
1666+
// `branch` has type `fn branch(self) -> ...`
1667+
// so we can get the Self argument from the first argument.
1668+
let generic_arg = GenericArg::from(types.expr_ty_adjusted(&args[0]));
1669+
tcx.mk_args(&[generic_arg])
1670+
}
1671+
Some(RustItem::ResidualTraitFromResidual) => {
1672+
// `fn from_residual(residual: R) -> Self;`
1673+
let generic_arg0 = GenericArg::from(types.expr_ty(expr));
1674+
let generic_arg1 = GenericArg::from(types.expr_ty_adjusted(&args[0]));
1675+
tcx.mk_args(&[generic_arg0, generic_arg1])
1676+
}
1677+
_ => node_substs,
1678+
}
1679+
}
1680+
16551681
fn mk_typ_args<'tcx>(
16561682
bctx: &BodyCtxt<'tcx>,
16571683
substs: &rustc_middle::ty::List<rustc_middle::ty::GenericArg<'tcx>>,

source/rust_verify/src/lifetime_generate.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,27 @@ fn erase_ty<'tcx>(ctxt: &Context<'tcx>, state: &mut State, ty: &Ty<'tcx>) -> Typ
409409
TyKind::Alias(rustc_middle::ty::AliasKind::Projection, t) => {
410410
// Note: even if rust_to_vir_base decides to normalize t,
411411
// we don't have to normalize t here, since we're generating Rust code, not VIR.
412+
// However, normalizing means we might reach less stuff so it's
413+
// still useful.
414+
415+
// Try normalization:
416+
use crate::rustc_trait_selection::infer::TyCtxtInferExt;
417+
use crate::rustc_trait_selection::traits::NormalizeExt;
418+
if let Some(fun_id) = state.enclosing_fun_id {
419+
let param_env = ctxt.tcx.param_env(fun_id);
420+
let infcx = ctxt.tcx.infer_ctxt().ignoring_regions().build();
421+
let cause = rustc_infer::traits::ObligationCause::dummy();
422+
let at = infcx.at(&cause, param_env);
423+
let resolved_ty = infcx.resolve_vars_if_possible(*ty);
424+
if !rustc_middle::ty::TypeVisitableExt::has_escaping_bound_vars(&resolved_ty) {
425+
let norm = at.normalize(*ty);
426+
if norm.value != *ty {
427+
return erase_ty(ctxt, state, &norm.value);
428+
}
429+
}
430+
}
431+
432+
// If normalization isn't possible:
412433
let assoc_item = ctxt.tcx.associated_item(t.def_id);
413434
let name = state.typ_param(assoc_item.name.to_string(), None);
414435
let trait_def = ctxt.tcx.generics_of(t.def_id).parent;
@@ -722,10 +743,21 @@ fn erase_call<'tcx>(
722743

723744
// Maybe resolve from trait function to a specific implementation
724745

725-
let mut node_substs = node_substs;
746+
let node_substs = node_substs;
726747
let mut fn_def_id = fn_def_id.expect("call id");
727748

728749
let param_env = ctxt.tcx.param_env(state.enclosing_fun_id.expect("enclosing_fun_id"));
750+
751+
let rust_item = crate::verus_items::get_rust_item(ctxt.tcx, fn_def_id);
752+
let mut node_substs = crate::fn_call_to_vir::fix_node_substs(
753+
ctxt.tcx,
754+
ctxt.types(),
755+
node_substs,
756+
rust_item,
757+
&args_slice.iter().collect::<Vec<_>>(),
758+
expr,
759+
);
760+
729761
let normalized_substs = ctxt.tcx.normalize_erasing_regions(param_env, node_substs);
730762
let inst = rustc_middle::ty::Instance::resolve(
731763
ctxt.tcx,
@@ -1738,6 +1770,8 @@ fn erase_fn_common<'tcx>(
17381770
&mut typ_params,
17391771
&mut generic_bounds,
17401772
);
1773+
1774+
state.enclosing_fun_id = Some(id);
17411775
let mut params: Vec<Param> = Vec::new();
17421776
for ((input, param), param_info) in
17431777
inputs.iter().zip(f_vir.x.params.iter()).zip(params_info.iter())
@@ -1772,6 +1806,7 @@ fn erase_fn_common<'tcx>(
17721806
} else {
17731807
Some((None, erase_ty(ctxt, state, &fn_sig.output().skip_binder())))
17741808
};
1809+
state.enclosing_fun_id = None;
17751810
let decl = FunDecl {
17761811
sig_span: sig_span,
17771812
name_span,

source/rust_verify/src/verus_items.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ pub(crate) enum RustItem {
546546
IntIntrinsic(RustIntIntrinsicItem),
547547
AllocGlobal,
548548
TryTraitBranch,
549+
ResidualTraitFromResidual,
549550
IntoIterFn,
550551
Destruct,
551552
}
@@ -582,6 +583,9 @@ pub(crate) fn get_rust_item<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Ru
582583
if tcx.lang_items().branch_fn() == Some(def_id) {
583584
return Some(RustItem::TryTraitBranch);
584585
}
586+
if tcx.lang_items().from_residual_fn() == Some(def_id) {
587+
return Some(RustItem::ResidualTraitFromResidual);
588+
}
585589
if tcx.lang_items().into_iter_fn() == Some(def_id) {
586590
return Some(RustItem::IntoIterFn);
587591
}

source/rust_verify_test/tests/std.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,93 @@ test_verify_one_file! {
195195
}
196196
} => Err(err) => assert_one_fails(err)
197197
}
198+
199+
test_verify_one_file! {
200+
#[test] question_mark_option verus_code! {
201+
use vstd::*;
202+
203+
fn test() -> (res: Option<u32>)
204+
ensures res.is_none()
205+
{
206+
let x: Option<u8> = None;
207+
let y = x?;
208+
209+
assert(false);
210+
return None;
211+
}
212+
213+
fn test2() -> (res: Option<u32>)
214+
{
215+
let x: Option<u8> = Some(5);
216+
let y = x?;
217+
218+
assert(false); // FAILS
219+
return None;
220+
}
221+
222+
fn test3() -> (res: Option<u32>)
223+
ensures res.is_some(),
224+
{
225+
let x: Option<u8> = None;
226+
let y = x?; // FAILS
227+
228+
return Some(13);
229+
}
230+
231+
fn test4() -> (res: Option<u32>)
232+
ensures false,
233+
{
234+
let x: Option<u8> = Some(12);
235+
let y = x?;
236+
237+
assert(y == 12);
238+
239+
loop { }
240+
}
241+
} => Err(err) => assert_fails(err, 2)
242+
}
243+
244+
test_verify_one_file! {
245+
#[test] question_mark_result verus_code! {
246+
use vstd::*;
247+
248+
fn test() -> (res: Result<u32, bool>)
249+
ensures res === Err(false),
250+
{
251+
let x: Result<u8, bool> = Err(false);
252+
let y = x?;
253+
254+
assert(false);
255+
return Err(true);
256+
}
257+
258+
fn test2() -> (res: Result<u32, bool>)
259+
{
260+
let x: Result<u8, bool> = Ok(5);
261+
let y = x?;
262+
263+
assert(false); // FAILS
264+
return Err(false);
265+
}
266+
267+
fn test3() -> (res: Result<u32, bool>)
268+
ensures res.is_ok(),
269+
{
270+
let x: Result<u8, bool> = Err(false);
271+
let y = x?; // FAILS
272+
273+
return Ok(13);
274+
}
275+
276+
fn test4() -> (res: Result<u32, bool>)
277+
ensures false,
278+
{
279+
let x: Result<u8, bool> = Ok(12);
280+
let y = x?;
281+
282+
assert(y == 12);
283+
284+
loop { }
285+
}
286+
} => Err(err) => assert_fails(err, 2)
287+
}

0 commit comments

Comments
 (0)