Skip to content

Commit d62aee4

Browse files
authored
Merge pull request rust-lang#18 from utaal/structural
Use the `builtin::Structural` trait to mark types that support structural equality
2 parents db05188 + 5af730e commit d62aee4

16 files changed

+305
-69
lines changed

verify/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
members = [
33
"air",
44
"builtin",
5+
"builtin_macros",
56
"vir",
67
"rust_verify",
78
"rust_verify_test_macros",

verify/builtin/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ name = "builtin"
33
version = "0.1.0"
44
authors = ["Chris Hawblitzel <[email protected]>"]
55
edition = "2018"
6-
76
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

verify/builtin/src/lib.rs

+30
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![feature(rustc_attrs)]
2+
13
pub fn admit() {
24
unimplemented!();
35
}
@@ -141,3 +143,31 @@ impl std::cmp::Ord for nat {
141143
unimplemented!()
142144
}
143145
}
146+
147+
// TODO(andreal) bake this into the compiler as a lang_item
148+
#[rustc_diagnostic_item = "builtin::Structural"]
149+
pub trait Structural {
150+
#[doc(hidden)]
151+
fn assert_receiver_is_structural(&self) -> () {}
152+
}
153+
154+
#[doc(hidden)]
155+
pub struct AssertParamIsStructural<T: Structural + ?Sized> {
156+
_field: std::marker::PhantomData<T>,
157+
}
158+
159+
macro_rules! impl_structural {
160+
($($t:ty)*) => {
161+
$(
162+
impl Structural for $t { }
163+
)*
164+
}
165+
}
166+
167+
impl_structural! {
168+
int nat
169+
usize u8 u16 u32 u64 u128
170+
isize i8 i16 i32 i64 i128
171+
// TODO: support f32 f64 ?
172+
bool char
173+
}

verify/builtin_macros/Cargo.toml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "builtin_macros"
3+
version = "0.1.0"
4+
authors = ["Chris Hawblitzel <[email protected]>", "Andrea Lattuada <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[dependencies]
11+
proc-macro2 = "1.0"
12+
quote = "1.0"
13+
synstructure = "0.12"
14+
syn = "1.0"

verify/builtin_macros/src/lib.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use quote::quote;
2+
use synstructure::decl_derive;
3+
4+
decl_derive!([Structural] => derive_structural);
5+
6+
fn derive_structural(s: synstructure::Structure) -> proc_macro2::TokenStream {
7+
let assert_receiver_is_structural_body = s
8+
.variants()
9+
.iter()
10+
.flat_map(|v| v.ast().fields)
11+
.map(|f| {
12+
let ty = &f.ty;
13+
quote! {
14+
let _: ::builtin::AssertParamIsStructural<#ty>;
15+
}
16+
})
17+
.collect::<proc_macro2::TokenStream>();
18+
s.gen_impl(quote! {
19+
#[automatically_derived]
20+
gen impl ::builtin::Structural for @Self {
21+
#[inline]
22+
#[doc(hidden)]
23+
fn assert_receiver_is_structural(&self) -> () {
24+
#assert_receiver_is_structural_body
25+
}
26+
}
27+
})
28+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
extern crate builtin;
2+
#[macro_use] extern crate builtin_macros;
3+
use builtin::*;
4+
mod pervasive;
5+
use pervasive::*;
6+
7+
#[derive(Eq)]
8+
struct Thing { }
9+
10+
impl std::cmp::PartialEq for Thing {
11+
fn eq(&self, _: &Self) -> bool { todo!() }
12+
}
13+
14+
#[derive(PartialEq, Eq, Structural)]
15+
struct Car<T> {
16+
passengers: T,
17+
four_doors: bool,
18+
}
19+
20+
fn one() {
21+
let c1 = Car { passengers: Thing { }, four_doors: true };
22+
let c2 = Car { passengers: Thing { }, four_doors: true };
23+
assert(c1 == c2);
24+
}
25+
26+
fn main() { }

verify/rust_verify/src/main.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ pub fn main() {
2525
// Run verifier callback to build VIR tree and run verifier
2626
let mut verifier = Verifier::new(our_args);
2727
let status = rustc_driver::RunCompiler::new(&rustc_args, &mut verifier).run();
28-
println!(
29-
"Verification results:: verified: {} errors: {}",
30-
verifier.count_verified,
31-
verifier.errors.len()
32-
);
28+
if !verifier.encountered_vir_error {
29+
println!(
30+
"Verification results:: verified: {} errors: {}",
31+
verifier.count_verified,
32+
verifier.errors.len()
33+
);
34+
}
3335
match status {
3436
Ok(_) => {}
3537
Err(_) => {

verify/rust_verify/src/rust_to_vir.rs

+58-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::rust_to_vir_adts::{check_item_enum, check_item_struct};
1010
use crate::rust_to_vir_base::{hack_check_def_name, hack_get_def_name};
1111
use crate::rust_to_vir_func::{check_foreign_item_fn, check_item_fn};
1212
use crate::util::unsupported_err_span;
13-
use crate::{unsupported_err, unsupported_err_unless, unsupported_unless};
13+
use crate::{err_unless, unsupported_err, unsupported_err_unless, unsupported_unless};
1414
use rustc_ast::Attribute;
1515
use rustc_hir::{
1616
Crate, ForeignItem, ForeignItemId, ForeignItemKind, HirId, Item, ItemId, ItemKind, ModuleItems,
@@ -62,11 +62,52 @@ fn check_item<'tcx>(
6262
"core",
6363
"marker::StructuralPartialEq"
6464
)
65-
|| hack_check_def_name(tcx, path.res.def_id(), "core", "cmp::PartialEq"),
65+
|| hack_check_def_name(tcx, path.res.def_id(), "core", "cmp::PartialEq")
66+
|| hack_check_def_name(tcx, path.res.def_id(), "builtin", "Structural"),
6667
item.span,
6768
"non_eq_trait_impl",
6869
path
6970
);
71+
if hack_check_def_name(tcx, path.res.def_id(), "builtin", "Structural") {
72+
let ty = {
73+
// TODO extract to rust_to_vir_base, or use
74+
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_typeck/fn.hir_ty_to_ty.html
75+
// ?
76+
let def_id = match impll.self_ty.kind {
77+
rustc_hir::TyKind::Path(QPath::Resolved(None, path)) => {
78+
path.res.def_id()
79+
}
80+
_ => panic!(
81+
"self type of impl is not resolved: {:?}",
82+
impll.self_ty.kind
83+
),
84+
};
85+
tcx.type_of(def_id)
86+
};
87+
// TODO: this may be a bit of a hack: to query the TyCtxt for the StructuralEq impl it seems we need
88+
// a concrete type, so apply ! to all type parameters
89+
let ty_kind_applied_never =
90+
if let rustc_middle::ty::TyKind::Adt(def, substs) = ty.kind() {
91+
rustc_middle::ty::TyKind::Adt(
92+
def,
93+
tcx.mk_substs(substs.iter().map(|g| match g.unpack() {
94+
rustc_middle::ty::subst::GenericArgKind::Type(_) => {
95+
(*tcx).types.never.into()
96+
}
97+
_ => g,
98+
})),
99+
)
100+
} else {
101+
panic!("Structural impl for non-adt type");
102+
};
103+
let ty_applied_never = tcx.mk_ty(ty_kind_applied_never);
104+
err_unless!(
105+
ty_applied_never.is_structural_eq_shallow(tcx),
106+
item.span,
107+
format!("Structural impl for non-structural type {:?}", ty),
108+
ty
109+
);
110+
}
70111
} else {
71112
unsupported_err_unless!(
72113
impll.of_trait.is_none(),
@@ -93,6 +134,15 @@ fn check_item<'tcx>(
93134
}
94135
}
95136
}
137+
ItemKind::Const(_ty, _body_id) => {
138+
unsupported_err_unless!(
139+
hack_get_def_name(tcx, _body_id.hir_id.owner.to_def_id())
140+
.starts_with("_DERIVE_builtin_Structural_FOR_"),
141+
item.span,
142+
"unsupported const",
143+
item
144+
);
145+
}
96146
_ => {
97147
unsupported_err!(item.span, "unsupported item", item);
98148
}
@@ -118,7 +168,8 @@ fn check_module<'tcx>(
118168
unsupported_unless!(
119169
def_name == "assert_receiver_is_total_eq"
120170
|| def_name == "eq"
121-
|| def_name == "ne",
171+
|| def_name == "ne"
172+
|| def_name == "assert_receiver_is_structural",
122173
"impl definition in module",
123174
id
124175
);
@@ -207,7 +258,8 @@ pub fn crate_to_vir<'tcx>(tcx: TyCtxt<'tcx>, krate: &'tcx Crate<'tcx>) -> Result
207258
unsupported_unless!(
208259
impl_item_ident == "assert_receiver_is_total_eq"
209260
|| impl_item_ident == "eq"
210-
|| impl_item_ident == "ne",
261+
|| impl_item_ident == "ne"
262+
|| impl_item_ident == "assert_receiver_is_structural",
211263
"impl definition",
212264
impl_item
213265
);
@@ -217,7 +269,8 @@ pub fn crate_to_vir<'tcx>(tcx: TyCtxt<'tcx>, krate: &'tcx Crate<'tcx>) -> Result
217269
hack_check_def_name(tcx, *id, "core", "marker::StructuralEq")
218270
|| hack_check_def_name(tcx, *id, "core", "cmp::Eq")
219271
|| hack_check_def_name(tcx, *id, "core", "marker::StructuralPartialEq")
220-
|| hack_check_def_name(tcx, *id, "core", "cmp::PartialEq"),
272+
|| hack_check_def_name(tcx, *id, "core", "cmp::PartialEq")
273+
|| hack_check_def_name(tcx, *id, "builtin", "Structural"),
221274
"non_eq_trait_impl",
222275
id
223276
);

verify/rust_verify/src/rust_to_vir_base.rs

+12-30
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::util::{err_span_str, err_span_string, unsupported_err_span, warning_span};
2-
use crate::{unsupported, unsupported_err, unsupported_err_unless, unsupported_unless};
1+
use crate::util::{err_span_str, err_span_string, unsupported_err_span};
2+
use crate::{unsupported, unsupported_err, unsupported_err_unless};
33
use rustc_ast::token::{Token, TokenKind};
44
use rustc_ast::tokenstream::TokenTree;
55
use rustc_ast::{AttrKind, Attribute, IntTy, MacArgs, UintTy};
@@ -12,7 +12,7 @@ use rustc_span::symbol::Ident;
1212
use rustc_span::Span;
1313
use std::rc::Rc;
1414
use vir::ast::{Idents, IntRange, Mode, Path, Typ, TypX, VirErr};
15-
use vir::ast_util::{path_to_string, types_equal};
15+
use vir::ast_util::types_equal;
1616

1717
pub(crate) fn def_to_path<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Path {
1818
Rc::new(tcx.def_path(def_id).data.iter().map(|d| Rc::new(format!("{}", d))).collect::<Vec<_>>())
@@ -268,46 +268,28 @@ pub(crate) fn typ_of_node<'tcx>(ctxt: &Ctxt<'tcx>, id: &HirId) -> Typ {
268268
// Do equality operations on these operands translate into the SMT solver's == operation?
269269
pub(crate) fn is_smt_equality<'tcx>(
270270
ctxt: &Ctxt<'tcx>,
271-
span: Span,
271+
_span: Span,
272272
id1: &HirId,
273273
id2: &HirId,
274274
) -> bool {
275275
let (t1, t2) = (typ_of_node(ctxt, id1), typ_of_node(ctxt, id2));
276276
match (&*t1, &*t2) {
277277
(TypX::Bool, TypX::Bool) => true,
278278
(TypX::Int(_), TypX::Int(_)) => true,
279-
(TypX::Path(p), TypX::Path(_)) if types_equal(&t1, &t2) => {
280-
// TODO: a type may provide a custom PartialEq implementation, or have interior
281-
// mutability; this means that PartialEq::eq may not be the same as structural
282-
// (member-wise) adt equality. We should check whether the PartialEq implementation
283-
// is compatible with adt equality before allowing these. For now, warn that there
284-
// may be unsoundness.
285-
// As used here, StructuralEq is only sufficient for a shallow check.
286-
// In the rust doc for `StructuralEq`:
287-
// > Any type that derives Eq automatically implements this trait,
288-
// > regardless of whether its type parameters implement Eq.
289-
warning_span(
290-
span,
291-
format!(
292-
"the verifier will assume structural equality for {}, which may be un sound",
293-
path_to_string(p)
294-
),
295-
);
296-
let struct_eq_def_id = ctxt
279+
(TypX::Path(_), TypX::Path(_)) if types_equal(&t1, &t2) => {
280+
let structural_def_id = ctxt
297281
.tcx
298-
.lang_items()
299-
.structural_teq_trait()
300-
.expect("structural eq trait is not defined");
282+
.get_diagnostic_item(rustc_span::Symbol::intern("builtin::Structural"))
283+
.expect("structural trait is not defined");
301284
let ty = ctxt.types.node_type(*id1);
302-
let substs_ref = ctxt.tcx.mk_substs_trait(ty, &[]);
303-
let type_impls_struct_eq = ctxt.tcx.type_implements_trait((
304-
struct_eq_def_id,
285+
let substs_ref = ctxt.tcx.mk_substs([].iter());
286+
let ty_impls_structural = ctxt.tcx.type_implements_trait((
287+
structural_def_id,
305288
ty,
306289
substs_ref,
307290
rustc_middle::ty::ParamEnv::empty(),
308291
));
309-
unsupported_unless!(type_impls_struct_eq, "eq_for_non_structural_eq");
310-
true
292+
ty_impls_structural
311293
}
312294
_ => false,
313295
}

verify/rust_verify/src/rust_to_vir_expr.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,9 @@ pub(crate) fn expr_to_vir_inner<'tcx>(
480480
let vlhs = expr_to_vir(ctxt, lhs)?;
481481
let vrhs = expr_to_vir(ctxt, rhs)?;
482482
match op.node {
483-
BinOpKind::Eq | BinOpKind::Ne => unsupported_unless!(
483+
BinOpKind::Eq | BinOpKind::Ne => unsupported_err_unless!(
484484
is_smt_equality(ctxt, expr.span, &lhs.hir_id, &rhs.hir_id),
485+
expr.span,
485486
"==/!= for non smt equality types",
486487
expr
487488
),

verify/rust_verify/src/util.rs

+16
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,22 @@ macro_rules! unsupported_err_unless {
5858
};
5959
}
6060

61+
#[macro_export]
62+
macro_rules! err_unless {
63+
($assertion: expr, $span: expr, $msg: expr) => {
64+
if (!$assertion) {
65+
dbg!();
66+
crate::util::err_span_string($span, $msg)?;
67+
}
68+
};
69+
($assertion: expr, $span: expr, $msg: expr, $info: expr) => {
70+
if (!$assertion) {
71+
dbg!($info);
72+
crate::util::err_span_string($span, $msg)?;
73+
}
74+
};
75+
}
76+
6177
#[macro_export]
6278
macro_rules! unsupported {
6379
($msg: expr) => {{ panic!("The verifier does not yet support the following Rust feature: {}", $msg) }};

0 commit comments

Comments
 (0)