Skip to content

Commit fc697a7

Browse files
Merge branch 'main' into async
2 parents 320f5dc + 9aa8b4f commit fc697a7

8 files changed

Lines changed: 142 additions & 18 deletions

File tree

source/cargo-verus/src/subcommands.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,19 @@ fn make_cargo_args(opts: &CargoOptions, for_cargo_metadata: bool) -> Vec<String>
246246
args.push(path.to_string_lossy().into_owned());
247247
}
248248

249+
if opts.features.all_features {
250+
args.push("--all-features".to_owned());
251+
}
252+
253+
if opts.features.no_default_features {
254+
args.push("--no-default-features".to_owned());
255+
}
256+
257+
if !opts.features.features.is_empty() {
258+
args.push("--features".to_owned());
259+
args.push(opts.features.features.join(" "));
260+
}
261+
249262
if !for_cargo_metadata {
250263
if let Some(path) = &opts.target_dir {
251264
args.push("--target-dir".to_owned());
@@ -270,19 +283,6 @@ fn make_cargo_args(opts: &CargoOptions, for_cargo_metadata: bool) -> Vec<String>
270283
args.push(exclude.clone());
271284
}
272285

273-
if opts.features.all_features {
274-
args.push("--all-features".to_owned());
275-
}
276-
277-
if opts.features.no_default_features {
278-
args.push("--no-default-features".to_owned());
279-
}
280-
281-
if !opts.features.features.is_empty() {
282-
args.push("--features".to_owned());
283-
args.push(opts.features.features.join(" "));
284-
}
285-
286286
args.extend(opts.cargo_args.iter().cloned());
287287
}
288288

source/cargo-verus/src/test_utils.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct MockPackage {
1313
bin_names: Vec<String>,
1414
example_names: Vec<String>,
1515
deps: Vec<(DepKind, Option<String>, MockDep)>,
16+
features: Vec<String>,
1617
verus_verify: Option<bool>,
1718
}
1819

@@ -121,6 +122,7 @@ impl MockPackage {
121122
bin_names: vec![],
122123
example_names: vec![],
123124
deps: vec![],
125+
features: vec![],
124126
verus_verify: None,
125127
}
126128
}
@@ -145,6 +147,11 @@ impl MockPackage {
145147
self
146148
}
147149

150+
pub fn features(mut self, names: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
151+
self.features.extend(names.into_iter().map(|n| n.as_ref().to_owned()));
152+
self
153+
}
154+
148155
pub fn deps(mut self, deps: impl IntoIterator<Item = MockDep>) -> Self {
149156
self.deps.extend(deps.into_iter().map(|d| (DepKind::Normal, None, d)));
150157
self
@@ -249,6 +256,12 @@ impl MockPackage {
249256
manifest_lines.push("".to_owned());
250257
}
251258

259+
if !self.features.is_empty() {
260+
manifest_lines.push("[features]".to_owned());
261+
manifest_lines.extend(self.features);
262+
manifest_lines.push("".to_owned());
263+
}
264+
252265
if let Some(verus_verify) = self.verus_verify {
253266
manifest_lines.push("[package.metadata.verus]".to_owned());
254267
manifest_lines.push(format!("verify = {verus_verify}"));

source/cargo-verus/tests/test_late_args.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ fn package_before_release_is_ok() {
248248
#[test]
249249
fn features_before_release_is_ok() {
250250
// --features appearing before --release should work fine
251-
let package_dir = MockPackage::new("foo").lib().verify(true).materialize();
251+
let package_dir =
252+
MockPackage::new("foo").lib().verify(true).features(["default=[]"]).materialize();
252253

253254
let (status, _data) = run_cargo_verus(|cmd| {
254255
cmd.current_dir(&package_dir).arg("verify");

source/rust_verify/src/rust_to_vir_func.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2969,8 +2969,13 @@ pub(crate) fn check_item_const_or_static<'tcx>(
29692969
if header.require.len() + header.recommend.len() > 0 {
29702970
return err_span(span, "consts cannot have requires/recommends");
29712971
}
2972-
if ret_mode == Mode::Spec && (header.ensure.0.len() > 0 || header.ensure.1.len() > 0) {
2973-
return err_span(span, "spec consts cannot have ensures");
2972+
2973+
let spec_or_dual = ret_mode == Mode::Spec || func_mode == Mode::Spec;
2974+
if spec_or_dual && (header.ensure.0.len() > 0 || header.ensure.1.len() > 0) {
2975+
return err_span(span, "const cannot have `ensures` unless it is `exec const`");
2976+
}
2977+
if spec_or_dual && header.returns.is_some() {
2978+
return err_span(span, "const cannot have `returns` unless it is `exec const`");
29742979
}
29752980

29762981
let ret_name = air_unique_var(RETURN_VALUE);

source/rust_verify_test/tests/consts.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,3 +560,10 @@ test_verify_one_file! {
560560
}
561561
} => Ok(())
562562
}
563+
564+
test_verify_one_file! {
565+
#[test] const_with_ensures_issue2175 verus_code! {
566+
#[verus_spec(ensures true)]
567+
pub const C: char = 'x';
568+
} => Err(err) => assert_vir_error_msg(err, "const cannot have `ensures` unless it is `exec const`")
569+
}

source/rust_verify_test/tests/external_traits.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,3 +796,32 @@ test_verify_one_file! {
796796
}
797797
} => Ok(())
798798
}
799+
800+
test_verify_one_file! {
801+
#[test] test_impl_trait_direct_use_error verus_code! {
802+
#[verifier::external]
803+
trait T1 {}
804+
805+
#[verifier::external]
806+
impl T1 for u8 {}
807+
808+
#[verifier::external_trait_specification]
809+
#[verifier::external_trait_extension(T1Spec via T1SpecImpl)]
810+
trait ExT1 {
811+
type ExternalTraitSpecificationFor: T1;
812+
813+
spec fn f() -> bool;
814+
}
815+
816+
impl T1SpecImpl for u8 {
817+
spec fn f() -> bool { true }
818+
}
819+
820+
spec fn g() -> bool {
821+
<u8 as T1SpecImpl>::f() // should error: cannot use T1SpecImpl directly
822+
}
823+
} => Err(err) => assert_vir_error_msg(
824+
err,
825+
"cannot use trait `crate::T1SpecImpl` directly; use `crate::T1Spec` instead"
826+
)
827+
}

source/rust_verify_test/tests/state_machines.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,55 @@ test_verify_one_file! {
7676
} => Err(e) => assert_vir_error_msg(e, "`birds_eye` only makes sense for tokenized state machines")
7777
}
7878

79+
test_verify_one_file_with_options! {
80+
#[test] test_custom_err_on_subexpression_is_inert ["--expand-errors"] => verus_code! {
81+
spec fn custom_requires(a: bool, b: bool) -> bool {
82+
(#[verifier(custom_err("Custom error on subexpression"))] a) && b
83+
}
84+
85+
fn caller() {
86+
assert(custom_requires(1 == 2, true));
87+
}
88+
} => Err(err) => {
89+
assert!(err.errors[0].message.contains("assertion failed"));
90+
assert!(!err.errors.iter().any(|diag| {
91+
diag.message.contains("Custom error on subexpression")
92+
|| diag.rendered.contains("Custom error on subexpression")
93+
}));
94+
}
95+
}
96+
97+
test_verify_one_file_with_options! {
98+
#[test] test_state_machine_req_custom_err_is_inert ["--expand-errors"] => IMPORTS.to_string() + verus_code_str! {
99+
state_machine!{ X {
100+
fields {
101+
pub i: int,
102+
}
103+
104+
transition!{
105+
tr() {
106+
require(pre.i > 0);
107+
update i = pre.i + 1;
108+
}
109+
}
110+
}}
111+
112+
proof fn assert_tr() {
113+
let pre = X::State { i: 0 };
114+
let post = X::State { i: 1 };
115+
X::show::tr(pre, post);
116+
assert(X::State::tr(pre, post));
117+
}
118+
} => Err(err) => {
119+
assert!(err.errors[0].message.contains("precondition not satisfied"));
120+
assert!(err.errors[1].message.contains("assertion failed"));
121+
assert!(!err.errors.iter().any(|diag| {
122+
diag.message.contains("cannot prove this condition holds")
123+
|| diag.rendered.contains("cannot prove this condition holds")
124+
}));
125+
}
126+
}
127+
79128
test_verify_one_file! {
80129
#[test] test_birds_eye_guard IMPORTS.to_string() + verus_code_str! {
81130
tokenized_state_machine!{ X {

source/vir/src/traits.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ fn demote_one_expr(
2424
traits: &HashSet<Path>,
2525
internal_traits: &HashSet<Path>,
2626
extension_traits: &HashSet<Path>,
27+
impl_to_spec_traits: &HashMap<Path, Path>,
2728
funs: &HashSet<Fun>,
2829
expr: &Expr,
2930
) -> Result<Expr, VirErr> {
@@ -45,6 +46,16 @@ fn demote_one_expr(
4546
args,
4647
post_args,
4748
) if !traits.contains(&get_trait(fun)) || !funs.contains(fun) => {
49+
if let Some(spec_trait) = impl_to_spec_traits.get(&get_trait(fun)) {
50+
return Err(error(
51+
&expr.span,
52+
format!(
53+
"cannot use trait `{}` directly; use `{}` instead",
54+
path_as_friendly_rust_name(&get_trait(fun)),
55+
path_as_friendly_rust_name(spec_trait),
56+
),
57+
));
58+
}
4859
let ct = CallTarget::Fun(
4960
CallTargetKind::Static,
5061
resolved_fun.clone(),
@@ -137,9 +148,11 @@ pub fn demote_external_traits(
137148
krate.traits.iter().filter(|t| t.x.proxy.is_none()).map(|t| t.x.name.clone()).collect();
138149
let funs: HashSet<Fun> = krate.functions.iter().map(|f| f.x.name.clone()).collect();
139150
let mut extension_traits: HashSet<Path> = HashSet::new();
151+
let mut impl_to_spec_traits: HashMap<Path, Path> = HashMap::new();
140152
for t in krate.traits.iter() {
141-
if let Some((extension, _)) = &t.x.external_trait_extension {
153+
if let Some((extension, imp)) = &t.x.external_trait_extension {
142154
extension_traits.insert(extension.clone());
155+
impl_to_spec_traits.insert(imp.clone(), extension.clone());
143156
}
144157
}
145158

@@ -232,7 +245,14 @@ pub fn demote_external_traits(
232245
&mut map,
233246
&mut (),
234247
&|_state, _, expr| {
235-
demote_one_expr(&traits, &internal_traits, &extension_traits, &funs, expr)
248+
demote_one_expr(
249+
&traits,
250+
&internal_traits,
251+
&extension_traits,
252+
&impl_to_spec_traits,
253+
&funs,
254+
expr,
255+
)
236256
},
237257
&|_state, _, stmt| Ok(vec![stmt.clone()]),
238258
&|_state, typ| Ok(typ.clone()),

0 commit comments

Comments
 (0)