├── .gitignore ├── tests ├── ui │ ├── .gitignore │ ├── fail │ │ ├── simple.rs │ │ ├── fn_poly.rs │ │ ├── assign_twice.rs │ │ ├── reassign.rs │ │ ├── simple_mut.rs │ │ ├── box_.rs │ │ ├── fn_poly_annot.rs │ │ ├── return_mut.rs │ │ ├── add.rs │ │ ├── adt_discr.rs │ │ ├── implicit_reborrow.rs │ │ ├── adt_simple.rs │ │ ├── fn_poly_annot_ref.rs │ │ ├── adt_poly_ref.rs │ │ ├── box_nested.rs │ │ ├── fn_poly_multiple_calls.rs │ │ ├── tuple_annot.rs │ │ ├── swap.rs │ │ ├── fn_poly_ref.rs │ │ ├── fn_poly_nested_calls.rs │ │ ├── fn_poly_annot_multi_inst.rs │ │ ├── incr_twice.rs │ │ ├── fn_poly_annot_stronger.rs │ │ ├── fn_poly_annot_nested.rs │ │ ├── fn_poly_unused_param.rs │ │ ├── fn_poly_mut_ref.rs │ │ ├── fn_poly_annot_complex.rs │ │ ├── fn_poly_double_nested.rs │ │ ├── fn_poly_param_order.rs │ │ ├── annot_exists.rs │ │ ├── fn_poly_annot_recursive.rs │ │ ├── loop.rs │ │ ├── split.rs │ │ ├── recursive.rs │ │ ├── fn_ptr.rs │ │ ├── mut_recursive.rs │ │ ├── list_sum_const.rs │ │ ├── fn_poly_recursive.rs │ │ ├── list_length_const.rs │ │ ├── take_max.rs │ │ ├── annot.rs │ │ ├── just_rec.rs │ │ ├── enum_ref_drop.rs │ │ ├── adt.rs │ │ ├── adt_poly_fn_mono.rs │ │ ├── adt_mut.rs │ │ ├── take_max_annot.rs │ │ └── adt_poly_fn_poly.rs │ └── pass │ │ ├── fn_poly.rs │ │ ├── simple.rs │ │ ├── assign_twice.rs │ │ ├── reassign.rs │ │ ├── simple_mut.rs │ │ ├── fn_poly_annot.rs │ │ ├── return_mut.rs │ │ ├── add.rs │ │ ├── adt_discr.rs │ │ ├── implicit_reborrow.rs │ │ ├── adt_simple.rs │ │ ├── fn_poly_annot_ref.rs │ │ ├── adt_poly_ref.rs │ │ ├── box_nested.rs │ │ ├── tuple_annot.rs │ │ ├── swap.rs │ │ ├── fn_poly_ref.rs │ │ ├── box_.rs │ │ ├── incr_twice.rs │ │ ├── fn_poly_multiple_calls.rs │ │ ├── fn_poly_nested_calls.rs │ │ ├── fn_poly_annot_nested.rs │ │ ├── fn_poly_annot_stronger.rs │ │ ├── fn_poly_unused_param.rs │ │ ├── fn_poly_mut_ref.rs │ │ ├── fn_poly_annot_multi_inst.rs │ │ ├── annot_exists.rs │ │ ├── fn_poly_annot_recursive.rs │ │ ├── fn_poly_annot_complex.rs │ │ ├── loop.rs │ │ ├── fn_poly_double_nested.rs │ │ ├── recursive.rs │ │ ├── fn_poly_param_order.rs │ │ ├── fn_ptr.rs │ │ ├── list_sum_const.rs │ │ ├── list_length_const.rs │ │ ├── split.rs │ │ ├── mut_recursive.rs │ │ ├── take_max.rs │ │ ├── fn_poly_recursive.rs │ │ ├── annot.rs │ │ ├── just_rec.rs │ │ ├── enum_ref_drop.rs │ │ ├── adt.rs │ │ ├── adt_poly_fn_mono.rs │ │ ├── adt_mut.rs │ │ ├── take_max_annot.rs │ │ └── adt_poly_fn_poly.rs └── ui.rs ├── rust-toolchain.toml ├── .devcontainer ├── devcontainer.json └── Dockerfile ├── src ├── lib.rs ├── refine.rs ├── main.rs ├── analyze │ ├── did_cache.rs │ ├── annot.rs │ ├── basic_block │ │ ├── drop_point.rs │ │ └── visitor.rs │ └── crate_.rs ├── refine │ ├── basic_block.rs │ └── template.rs ├── chc │ ├── hoice.rs │ ├── debug.rs │ ├── clause_builder.rs │ ├── unbox.rs │ ├── solver.rs │ ├── format_context.rs │ └── smtlib2.rs ├── rty │ ├── template.rs │ ├── clause_builder.rs │ ├── params.rs │ └── subtyping.rs ├── pretty.rs └── analyze.rs ├── Cargo.toml ├── .github ├── workflows │ ├── docs.yml │ └── ci.yml └── actions │ └── setup-z3 │ └── action.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /tests/ui/.gitignore: -------------------------------------------------------------------------------- 1 | *.stderr 2 | -------------------------------------------------------------------------------- /tests/ui/fail/simple.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn main() { 4 | let a: i64 = 1; 5 | assert!(a == 2); 6 | } 7 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-03-08" 3 | components = [ "rustc-dev", "rust-src", "llvm-tools-preview" ] 4 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn left(x: (T, U)) -> T { 4 | x.0 5 | } 6 | 7 | fn main() { 8 | assert!(left((42, 0)) == 42); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/pass/simple.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn f() -> i64 { 4 | return 1; 5 | } 6 | 7 | fn main() { 8 | let a = f(); 9 | assert!(a == 1); 10 | } 11 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn left(x: (T, U)) -> T { 4 | x.0 5 | } 6 | 7 | fn main() { 8 | assert!(left((42, 0)) == 0); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/pass/assign_twice.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn main() { 4 | let mut x = 0_i64; 5 | let m = &mut x; 6 | *m = 1; 7 | *m = 2; 8 | assert!(x == 2); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/fail/assign_twice.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn main() { 4 | let mut x = 0_i64; 5 | let m = &mut x; 6 | *m = 1; 7 | *m = 2; 8 | assert!(x == 1); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/pass/reassign.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn main() { 5 | let mut x = 1_i64; 6 | x += 1; 7 | x += 1; 8 | assert!(x == 3); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/pass/simple_mut.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn f(x: &mut i64, y: i64) { 4 | *x = y; 5 | } 6 | 7 | fn main() { 8 | let mut a = 1; 9 | f(&mut a, 2); 10 | assert!(a == 2); 11 | } 12 | -------------------------------------------------------------------------------- /tests/ui.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut config = ui_test::Config::rustc("tests/ui/"); 3 | config.program.program = env!("CARGO_BIN_EXE_thrust-rustc").into(); 4 | ui_test::run_tests(config).unwrap(); 5 | } 6 | -------------------------------------------------------------------------------- /tests/ui/fail/reassign.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn main() { 5 | let mut x = 1_i64; 6 | x += 1; 7 | x += 1; 8 | assert!(x == 1); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/fail/simple_mut.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn f(x: &mut i64, y: i64) { 4 | *x = y; 5 | } 6 | 7 | fn main() { 8 | let mut a = 1; 9 | f(&mut a, 2); 10 | assert!(a == 1); 11 | } 12 | -------------------------------------------------------------------------------- /tests/ui/fail/box_.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn main() { 5 | let mut x = Box::new(1_i64); 6 | let m: &mut i64 = &mut *x; 7 | *m = 2; 8 | assert!(*x == 1); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_annot.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result == x.0)] 5 | fn left(x: (T, U)) -> T { 6 | x.0 7 | } 8 | 9 | fn main() { 10 | assert!(left((42, 0)) == 42); 11 | } 12 | -------------------------------------------------------------------------------- /tests/ui/pass/return_mut.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn id<'a>(m: &'a mut i64) -> &'a mut i64 { 4 | m 5 | } 6 | 7 | fn main() { 8 | let mut x = 1_i64; 9 | let m = id(&mut x); 10 | *m = 2; 11 | assert!(x == 2); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/pass/add.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn add(x: i64, y: i64) -> i64 { 5 | x + y 6 | } 7 | 8 | fn main() { 9 | let x = 1; 10 | let y = 2; 11 | assert!(add(x, y) == 3); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/pass/adt_discr.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(i64), 6 | B(bool), 7 | } 8 | 9 | fn main() { 10 | let x = X::B(true); 11 | assert!(matches!(x, X::B(b))); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/pass/implicit_reborrow.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn set(m: &mut i64) { 4 | *m = 1; 5 | } 6 | 7 | fn main() { 8 | let mut x = 0_i64; 9 | let m = &mut x; 10 | set(m); 11 | set(m); 12 | assert!(x == 1); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_annot.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result != x.0)] 5 | fn left(x: (T, U)) -> T { 6 | x.0 7 | } 8 | 9 | fn main() { 10 | assert!(left((42, 0)) == 42); 11 | } 12 | -------------------------------------------------------------------------------- /tests/ui/fail/return_mut.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn id<'a>(m: &'a mut i64) -> &'a mut i64 { 4 | m 5 | } 6 | 7 | fn main() { 8 | let mut x = 1_i64; 9 | let m = id(&mut x); 10 | *m = 2; 11 | assert!(x != 2); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/pass/adt_simple.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(i64), 6 | B(bool), 7 | } 8 | 9 | fn main() { 10 | let x = X::A(1); 11 | assert!(matches!(x, X::A(i) if i == 1)); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/fail/add.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn add(x: i64, y: i64) -> i64 { 5 | x + y 6 | } 7 | 8 | fn main() { 9 | let x = 1; 10 | let y = 2; 11 | assert!(add(x, y) == 2); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/fail/adt_discr.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(i64), 6 | B(bool), 7 | } 8 | 9 | fn main() { 10 | let x = X::A(1); 11 | assert!(matches!(x, X::B(b))); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/fail/implicit_reborrow.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn set(m: &mut i64) { 4 | *m = 1; 5 | } 6 | 7 | fn main() { 8 | let mut x = 0_i64; 9 | let m = &mut x; 10 | set(m); 11 | set(m); 12 | assert!(x == 0); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/fail/adt_simple.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(i64), 6 | B(bool), 7 | } 8 | 9 | fn main() { 10 | let x = X::A(1); 11 | assert!(matches!(x, X::A(i) if i == 2)); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_annot_ref.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result == x)] 5 | fn id_ref(x: &T) -> &T { 6 | x 7 | } 8 | 9 | fn main() { 10 | let val = 42; 11 | let r = id_ref(&val); 12 | assert!(*r == 42); 13 | } 14 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "dockerfile": "Dockerfile" 4 | }, 5 | "customizations": { 6 | "vscode": { 7 | "extensions": [ 8 | "rust-lang.rust-analyzer" 9 | ] 10 | } 11 | }, 12 | "postCreateCommand": "rustup show" 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/pass/adt_poly_ref.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | enum X<'a, T> { 5 | A(&'a T), 6 | } 7 | 8 | fn main() { 9 | let i = 42; 10 | let x = X::A(&i); 11 | match x { 12 | X::A(i) => assert!(*i == 42), 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/ui/pass/box_nested.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn incr(x: &mut i64) { 5 | *x += 1; 6 | } 7 | 8 | fn main() { 9 | let mut x = Box::new(Box::new(1)); 10 | let m = &mut **x; 11 | incr(m); 12 | assert!(**x == 2); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/pass/tuple_annot.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(^x == ((*x).1, (*x).0))] 6 | fn swap_tuple(x: &mut (i32, i32)) { 7 | let v = *x; 8 | *x = (v.1, v.0); 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_annot_ref.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result != x)] 5 | fn id_ref(x: &T) -> &T { 6 | x 7 | } 8 | 9 | fn main() { 10 | let val = 42; 11 | let r = id_ref(&val); 12 | assert!(*r == 42); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/pass/swap.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn swap(x: &mut i64, y: &mut i64) { 4 | let v = *x; 5 | *x = *y; 6 | *y = v; 7 | } 8 | 9 | fn main() { 10 | let mut a = 1; 11 | let mut b = 3; 12 | swap(&mut a, &mut b); 13 | assert!(a == 3); 14 | assert!(b == 1); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/fail/adt_poly_ref.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | enum X<'a, T> { 5 | A(&'a T), 6 | } 7 | 8 | fn main() { 9 | let i = 42; 10 | let x = X::A(&i); 11 | match x { 12 | X::A(i) => assert!(*i == 41), 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/ui/fail/box_nested.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn incr(x: &mut i64) { 5 | *x += 1; 6 | } 7 | 8 | fn main() { 9 | let mut x = Box::new(Box::new(1)); 10 | let m = &mut **x; 11 | incr(m); 12 | assert!(**x == 1); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_multiple_calls.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn first(pair: (T, U)) -> T { 4 | pair.0 5 | } 6 | 7 | fn main() { 8 | let x = first((42, true)); 9 | let y = first((true, 100)); 10 | 11 | assert!(x == 42); 12 | assert!(y == false); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/fail/tuple_annot.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(^x == ((*x).1, (*x).1))] 6 | fn swap_tuple(x: &mut (i32, i32)) { 7 | let v = *x; 8 | *x = (v.1, v.0); 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_ref.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn identity_ref(x: &T) -> &T { 4 | x 5 | } 6 | 7 | fn chain_ref(x: &T) -> &T { 8 | identity_ref(identity_ref(x)) 9 | } 10 | 11 | fn main() { 12 | let val = 42; 13 | let r = chain_ref(&val); 14 | assert!(*r == 42); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/fail/swap.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn swap(x: &mut i64, y: &mut i64) { 4 | let v = *x; 5 | *x = *y; 6 | *y = v; 7 | } 8 | 9 | fn main() { 10 | let mut a = 1; 11 | let mut b = 3; 12 | swap(&mut a, &mut b); 13 | assert!(a == 1); 14 | assert!(b == 3); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_ref.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn identity_ref(x: &T) -> &T { 4 | x 5 | } 6 | 7 | fn chain_ref(x: &T) -> &T { 8 | identity_ref(identity_ref(x)) 9 | } 10 | 11 | fn main() { 12 | let val = 42; 13 | let r = chain_ref(&val); 14 | assert!(*r == 43); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/pass/box_.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn main() { 5 | let mut x = Box::new(1_i64); 6 | let m: &mut i64 = &mut *x; 7 | *m = 2; 8 | assert!(*x == 2); 9 | let m: &mut Box = &mut x; 10 | *m = Box::new(3); 11 | assert!(*x == 3); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/pass/incr_twice.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn incr(m: &mut i64, x: i64) { 5 | let v = *m; 6 | *m = v + x; 7 | } 8 | 9 | fn main() { 10 | let mut x = 0_i64; 11 | let m = &mut x; 12 | incr(m, 1); 13 | incr(m, 2); 14 | assert!(x == 3); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_nested_calls.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn id(x: T) -> T { 4 | x 5 | } 6 | 7 | fn apply_twice(x: T) -> T { 8 | id(id(x)) 9 | } 10 | 11 | fn apply_thrice(x: T) -> T { 12 | id(apply_twice(x)) 13 | } 14 | 15 | fn main() { 16 | assert!(apply_thrice(42) == 43); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_annot_multi_inst.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result == x)] 5 | fn id(x: T) -> T { 6 | x 7 | } 8 | 9 | fn main() { 10 | let a = id(42); 11 | assert!(a == 42); 12 | 13 | let b = id(true); 14 | assert!(b == false); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/fail/incr_twice.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn incr(m: &mut i64, x: i64) { 5 | let v = *m; 6 | *m = v + x; 7 | } 8 | 9 | fn main() { 10 | let mut x = 0_i64; 11 | let m = &mut x; 12 | incr(m, 1); 13 | incr(m, 2); 14 | assert!(x == 2); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_multiple_calls.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn first(pair: (T, U)) -> T { 4 | pair.0 5 | } 6 | 7 | fn main() { 8 | let x = first((42, true)); 9 | let y = first((true, 100)); 10 | let z = first(((1, 2), 3)); 11 | 12 | assert!(x == 42); 13 | assert!(y == true); 14 | assert!(z.0 == 1); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_nested_calls.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn id(x: T) -> T { 4 | x 5 | } 6 | 7 | fn apply_twice(x: T) -> T { 8 | id(id(x)) 9 | } 10 | 11 | fn apply_thrice(x: T) -> T { 12 | id(apply_twice(x)) 13 | } 14 | 15 | fn main() { 16 | assert!(apply_thrice(42) == 42); 17 | assert!(apply_twice(true) == true); 18 | } 19 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_annot_nested.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result == x)] 5 | fn id(x: T) -> T { 6 | x 7 | } 8 | 9 | #[thrust::requires(true)] 10 | #[thrust::ensures(result == x)] 11 | fn apply_twice(x: T) -> T { 12 | id(id(x)) 13 | } 14 | 15 | fn main() { 16 | assert!(apply_twice(42) == 42); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_annot_stronger.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(x > 0)] 5 | #[thrust::ensures((result == x) && (result > 0))] 6 | fn pass_positive(x: i32, _dummy: T) -> i32 { 7 | x 8 | } 9 | 10 | fn main() { 11 | let result = pass_positive(42, true); 12 | assert!(result == 42); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_unused_param.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn project_first(triple: (T, U, V)) -> T { 4 | triple.0 5 | } 6 | 7 | fn chain(x: A, _phantom_b: B, _phantom_c: C) -> A { 8 | x 9 | } 10 | 11 | fn main() { 12 | let x = project_first((42, true, 100)); 13 | let y = chain(x, (1, 2), false); 14 | assert!(y == 42); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_mut_ref.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn update(x: &mut T, new_val: T) { 4 | *x = new_val; 5 | } 6 | 7 | fn chain_update(x: &mut T, temp: T, final_val: T) { 8 | update(x, temp); 9 | update(x, final_val); 10 | } 11 | 12 | fn main() { 13 | let mut val = 42; 14 | chain_update(&mut val, 100, 200); 15 | assert!(val == 200); 16 | } 17 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_annot_stronger.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(x > 0)] 5 | #[thrust::ensures((result == x) && (result > 0))] 6 | fn pass_positive(x: i32, _dummy: T) -> i32 { 7 | x 8 | } 9 | 10 | fn main() { 11 | let result = pass_positive(-5, true); 12 | assert!(result == -5); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_annot_multi_inst.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result == x)] 5 | fn id(x: T) -> T { 6 | x 7 | } 8 | 9 | fn main() { 10 | let a = id(42); 11 | assert!(a == 42); 12 | 13 | let b = id(true); 14 | assert!(b == true); 15 | 16 | let c = id((1, 2)); 17 | assert!(c.0 == 1); 18 | } 19 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_annot_nested.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result == x)] 5 | fn id(x: T) -> T { 6 | x 7 | } 8 | 9 | #[thrust::requires(true)] 10 | #[thrust::ensures(result != x)] 11 | fn apply_twice(x: T) -> T { 12 | id(id(x)) 13 | } 14 | 15 | fn main() { 16 | assert!(apply_twice(42) == 42); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_unused_param.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn project_first(triple: (T, U, V)) -> T { 4 | triple.0 5 | } 6 | 7 | fn chain(x: A, _phantom_b: B, _phantom_c: C) -> A { 8 | x 9 | } 10 | 11 | fn main() { 12 | let x = project_first((42, true, 100)); 13 | let y = chain(x, (1, 2), false); 14 | assert!(y == 43); 15 | } 16 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_mut_ref.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn update(x: &mut T, new_val: T) { 4 | *x = new_val; 5 | } 6 | 7 | fn chain_update(x: &mut T, temp: T, final_val: T) { 8 | update(x, temp); 9 | update(x, final_val); 10 | } 11 | 12 | fn main() { 13 | let mut val = 42; 14 | chain_update(&mut val, 100, 200); 15 | assert!(val == 42); 16 | } 17 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_annot_complex.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | #[thrust::requires((x.0 > 0) && (x.1 > 0))] 4 | #[thrust::ensures((result.0 == x.1) && (result.1 == x.0))] 5 | fn swap_positive(x: (i32, i32, T, U)) -> (i32, i32, U, T) { 6 | (x.1, x.0, x.3, x.2) 7 | } 8 | 9 | fn main() { 10 | let result = swap_positive((-5, 10, true, 42)); 11 | assert!(result.0 == 10); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_double_nested.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn id(x: T) -> T { 4 | x 5 | } 6 | 7 | fn apply_twice(x: T) -> T { 8 | id(id(x)) 9 | } 10 | 11 | fn apply_thrice(x: T) -> T { 12 | apply_twice(id(x)) 13 | } 14 | 15 | fn apply_four(x: T) -> T { 16 | apply_twice(apply_twice(x)) 17 | } 18 | 19 | fn main() { 20 | assert!(apply_four(42) == 43); 21 | } 22 | -------------------------------------------------------------------------------- /tests/ui/pass/annot_exists.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | //@rustc-env: THRUST_SOLVER=thrust-pcsat-wrapper 4 | 5 | #[thrust::trusted] 6 | #[thrust::callable] 7 | fn rand() -> i32 { unimplemented!() } 8 | 9 | #[thrust::requires(true)] 10 | #[thrust::ensures(exists x:int. result == 2 * x)] 11 | fn f() -> i32 { 12 | let x = rand(); 13 | x + x 14 | } 15 | 16 | fn main() {} 17 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_param_order.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | fn select(a: T, b: U, c: V, which: i32) -> T { 4 | if which == 0 { 5 | a 6 | } else { 7 | a 8 | } 9 | } 10 | 11 | fn rotate(triple: (A, B, C)) -> (B, C, A) { 12 | (triple.1, triple.2, triple.0) 13 | } 14 | 15 | fn main() { 16 | let x = rotate((1, true, 42)); 17 | assert!(x.0 == false); 18 | } 19 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_annot_recursive.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(n >= 0)] 5 | #[thrust::ensures(result == value)] 6 | fn repeat(n: i32, value: T) -> T { 7 | if n == 0 { 8 | value 9 | } else { 10 | repeat(n - 1, value) 11 | } 12 | } 13 | 14 | fn main() { 15 | let result = repeat(5, 42); 16 | assert!(result == 42); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_annot_complex.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires((n > 0) && (m > 0))] 5 | #[thrust::ensures((result.0 == m) && (result.1 == n))] 6 | fn swap_pair(n: i32, m: i32, _phantom: T) -> (i32, i32) { 7 | (m, n) 8 | } 9 | 10 | fn main() { 11 | let result = swap_pair(5, 10, true); 12 | assert!(result.0 == 10); 13 | assert!(result.1 == 5); 14 | } 15 | -------------------------------------------------------------------------------- /tests/ui/fail/annot_exists.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | //@rustc-env: THRUST_SOLVER=thrust-pcsat-wrapper 4 | 5 | #[thrust::trusted] 6 | #[thrust::callable] 7 | fn rand() -> i32 { unimplemented!() } 8 | 9 | #[thrust::requires(true)] 10 | #[thrust::ensures(exists x:int. result == 2 * x)] 11 | fn f() -> i32 { 12 | let x = rand(); 13 | x + x + x 14 | } 15 | 16 | fn main() {} 17 | -------------------------------------------------------------------------------- /tests/ui/pass/loop.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn main() { 10 | let mut x = 1_i64; 11 | let mut y = 1_i64; 12 | while rand() == 0 { 13 | let t1 = x; 14 | let t2 = y; 15 | x = t1 + t2; 16 | y = t1 + t2; 17 | } 18 | assert!(y >= 1); 19 | } 20 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_annot_recursive.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(n >= 0)] 5 | #[thrust::ensures(result == value)] 6 | fn repeat(n: i32, value: T) -> T { 7 | if n == 0 { 8 | value 9 | } else { 10 | repeat(n - 1, value) 11 | } 12 | } 13 | 14 | fn main() { 15 | let result = repeat(-1, 42); 16 | assert!(result == 42); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_double_nested.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn id(x: T) -> T { 4 | x 5 | } 6 | 7 | fn apply_twice(x: T) -> T { 8 | id(id(x)) 9 | } 10 | 11 | fn apply_thrice(x: T) -> T { 12 | apply_twice(id(x)) 13 | } 14 | 15 | fn apply_four(x: T) -> T { 16 | apply_twice(apply_twice(x)) 17 | } 18 | 19 | fn main() { 20 | assert!(apply_four(42) == 42); 21 | assert!(apply_thrice(true) == true); 22 | } 23 | -------------------------------------------------------------------------------- /tests/ui/fail/loop.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn main() { 10 | let mut x = 1_i64; 11 | let mut y = 1_i64; 12 | while rand() == 0 { 13 | let t1 = x; 14 | let t2 = y; 15 | x = t1 + t2; 16 | y = t1 + t2; 17 | } 18 | assert!(y >= 2); 19 | } 20 | -------------------------------------------------------------------------------- /tests/ui/pass/recursive.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn sum(i: i64) -> i64 { 10 | if i == 0 { 11 | 0 12 | } else { 13 | sum(i - 1) + 1 14 | } 15 | } 16 | 17 | fn main() { 18 | let x = rand(); 19 | let y = sum(x); 20 | assert!(y == x); 21 | } 22 | -------------------------------------------------------------------------------- /tests/ui/fail/split.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::trusted] 5 | #[thrust::requires(true)] 6 | #[thrust::ensures(true)] 7 | fn rand() -> i32 { unimplemented!() } 8 | 9 | fn split<'a>((a, b): &'a mut (i32, i32)) -> (&'a mut i32, &'a mut i32) { 10 | (a, b) 11 | } 12 | 13 | fn main() { 14 | let mut p = (1, 1); 15 | let (a, b) = split(&mut p); 16 | *a += 1; 17 | assert!(p.0 == 1); 18 | } 19 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_param_order.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | fn select(a: T, b: U, c: V, which: i32) -> T { 4 | if which == 0 { 5 | a 6 | } else { 7 | a 8 | } 9 | } 10 | 11 | fn rotate(triple: (A, B, C)) -> (B, C, A) { 12 | (triple.1, triple.2, triple.0) 13 | } 14 | 15 | fn main() { 16 | let x = rotate((1, true, 42)); 17 | assert!(x.0 == true); 18 | assert!(x.1 == 42); 19 | assert!(x.2 == 1); 20 | } 21 | -------------------------------------------------------------------------------- /tests/ui/fail/recursive.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn sum(i: i64) -> i64 { 10 | if i == 0 { 11 | 0 12 | } else { 13 | sum(i - 1) + 1 14 | } 15 | } 16 | 17 | fn main() { 18 | let x = rand(); 19 | let y = sum(x); 20 | assert!(y == x + 1); 21 | } 22 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_ptr.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn incr(m: &mut i64) { 10 | *m += 1; 11 | } 12 | 13 | fn app(f: fn(&mut i64), mut x: i64) -> i64 { 14 | f(&mut x); 15 | x 16 | } 17 | 18 | fn main() { 19 | let i = rand(); 20 | let x = app(incr, i); 21 | assert!(x == i + 1); 22 | } 23 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_ptr.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn incr(m: &mut i64) { 10 | *m += 1; 11 | } 12 | 13 | fn app(f: fn(&mut i64), mut x: i64) -> i64 { 14 | f(&mut x); 15 | x 16 | } 17 | 18 | fn main() { 19 | let i = rand(); 20 | let x = app(incr, i); 21 | assert!(x == i); 22 | } 23 | -------------------------------------------------------------------------------- /tests/ui/pass/list_sum_const.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off -C opt-level=3 3 | //@rustc-env: THRUST_SOLVER_TIMEOUT_SECS=120 4 | 5 | enum List { 6 | Cons(i32, Box), 7 | Nil, 8 | } 9 | 10 | fn sum(la: &List) -> i32 { 11 | match la { 12 | List::Cons(x, lb) => sum(lb) + *x, 13 | List::Nil => 0, 14 | } 15 | } 16 | 17 | fn main() { 18 | let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); 19 | assert!(sum(&list) == 3); 20 | } 21 | -------------------------------------------------------------------------------- /tests/ui/fail/mut_recursive.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn sum(a: &mut i64, i: i64) { 10 | if i == 0 { 11 | return; 12 | } 13 | *a += 1; 14 | sum(a, i - 1); 15 | } 16 | 17 | fn main() { 18 | let x = rand(); 19 | let mut y = 0; 20 | sum(&mut y, x); 21 | assert!(y == x + 1); 22 | } 23 | -------------------------------------------------------------------------------- /tests/ui/pass/list_length_const.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off -C opt-level=3 3 | //@rustc-env: THRUST_SOLVER_TIMEOUT_SECS=120 4 | 5 | enum List { 6 | Cons(i32, Box), 7 | Nil, 8 | } 9 | 10 | fn length(la: &List) -> i32 { 11 | match la { 12 | List::Cons(_, lb) => length(lb) + 1, 13 | List::Nil => 0, 14 | } 15 | } 16 | 17 | fn main() { 18 | let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); 19 | assert!(length(&list) == 2); 20 | } 21 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM mcr.microsoft.com/devcontainers/rust:1-bookworm 4 | 5 | ADD --checksum=sha256:bc31ad12446d7db1bd9d0ac82dec9d7b5129b8b8dd6e44b571a83ac6010d2f9b \ 6 | https://github.com/Z3Prover/z3/releases/download/z3-4.13.0/z3-4.13.0-x64-glibc-2.31.zip \ 7 | /tmp/ 8 | RUN unzip /tmp/z3-4.13.0-x64-glibc-2.31.zip -d /opt/ \ 9 | && rm /tmp/z3-4.13.0-x64-glibc-2.31.zip \ 10 | && echo 'PATH=$PATH:/opt/z3-4.13.0-x64-glibc-2.31/bin' > /etc/profile.d/99-z3.sh 11 | -------------------------------------------------------------------------------- /tests/ui/fail/list_sum_const.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off -C opt-level=3 3 | //@rustc-env: THRUST_SOLVER_TIMEOUT_SECS=120 4 | 5 | enum List { 6 | Cons(i32, Box), 7 | Nil, 8 | } 9 | 10 | fn sum(la: &List) -> i32 { 11 | match la { 12 | List::Cons(x, lb) => sum(lb) + *x, 13 | List::Nil => 0, 14 | } 15 | } 16 | 17 | fn main() { 18 | let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); 19 | assert!(sum(&list) == 2); 20 | } 21 | -------------------------------------------------------------------------------- /tests/ui/pass/split.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::trusted] 5 | #[thrust::requires(true)] 6 | #[thrust::ensures(true)] 7 | fn rand() -> i32 { unimplemented!() } 8 | 9 | fn split<'a>((a, b): &'a mut (i32, i32)) -> (&'a mut i32, &'a mut i32) { 10 | (a, b) 11 | } 12 | 13 | fn main() { 14 | let a = rand(); 15 | let b = rand(); 16 | let mut p = (a, b); 17 | let (ma, mb) = split(&mut p); 18 | *ma += 1; 19 | assert!(p.0 == a + 1); 20 | } 21 | -------------------------------------------------------------------------------- /tests/ui/fail/fn_poly_recursive.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn repeat(n: i32, value: T) -> T { 5 | if n <= 1 { 6 | value 7 | } else { 8 | repeat(n - 1, repeat(1, value)) 9 | } 10 | } 11 | 12 | fn identity_loop(depth: i32, x: T) -> T { 13 | if depth == 0 { 14 | x 15 | } else { 16 | identity_loop(depth - 1, identity_loop(0, x)) 17 | } 18 | } 19 | 20 | fn main() { 21 | assert!(repeat(5, 42) == 43); 22 | } 23 | -------------------------------------------------------------------------------- /tests/ui/fail/list_length_const.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off -C opt-level=3 3 | //@rustc-env: THRUST_SOLVER_TIMEOUT_SECS=120 4 | 5 | enum List { 6 | Cons(i32, Box), 7 | Nil, 8 | } 9 | 10 | fn length(la: &List) -> i32 { 11 | match la { 12 | List::Cons(_, lb) => length(lb) + 1, 13 | List::Nil => 0, 14 | } 15 | } 16 | 17 | fn main() { 18 | let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); 19 | assert!(length(&list) == 1); 20 | } 21 | -------------------------------------------------------------------------------- /tests/ui/pass/mut_recursive.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | //@rustc-env: THRUST_SOLVER_TIMEOUT_SECS=60 4 | 5 | #[thrust::requires(true)] 6 | #[thrust::ensures(true)] 7 | #[thrust::trusted] 8 | fn rand() -> i64 { unimplemented!() } 9 | 10 | fn sum(a: &mut i64, i: i64) { 11 | if i == 0 { 12 | return; 13 | } 14 | *a += 1; 15 | sum(a, i - 1); 16 | } 17 | 18 | fn main() { 19 | let x = rand(); 20 | let mut y = 0; 21 | sum(&mut y, x); 22 | assert!(y == x); 23 | } 24 | -------------------------------------------------------------------------------- /tests/ui/pass/take_max.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn take_max<'a>(ma: &'a mut i64, mb: &'a mut i64) -> &'a mut i64 { 10 | if *ma >= *mb { 11 | ma 12 | } else { 13 | mb 14 | } 15 | } 16 | 17 | fn main() { 18 | let mut a = rand(); 19 | let mut b = rand(); 20 | let mc = take_max(&mut a, &mut b); 21 | *mc += 1; 22 | assert!(a != b); 23 | } 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | 3 | extern crate rustc_ast; 4 | extern crate rustc_data_structures; 5 | extern crate rustc_hir; 6 | extern crate rustc_index; 7 | extern crate rustc_middle; 8 | extern crate rustc_mir_dataflow; 9 | extern crate rustc_span; 10 | extern crate rustc_target; 11 | 12 | // works with MIR 13 | mod analyze; 14 | 15 | // TODO: remove refine module 16 | mod refine; 17 | 18 | // pure logic 19 | mod annot; 20 | mod chc; 21 | mod rty; 22 | 23 | // utility 24 | mod pretty; 25 | 26 | pub use analyze::Analyzer; 27 | -------------------------------------------------------------------------------- /tests/ui/fail/take_max.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | fn take_max<'a>(ma: &'a mut i64, mb: &'a mut i64) -> &'a mut i64 { 10 | if *ma >= *mb { 11 | ma 12 | } else { 13 | mb 14 | } 15 | } 16 | 17 | fn main() { 18 | let mut a = rand(); 19 | let mut b = rand(); 20 | let mc = take_max(&mut a, &mut b); 21 | *mc += 1; 22 | assert!(a == b); 23 | } 24 | -------------------------------------------------------------------------------- /tests/ui/pass/fn_poly_recursive.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | fn repeat(n: i32, value: T) -> T { 5 | if n <= 1 { 6 | value 7 | } else { 8 | repeat(n - 1, repeat(1, value)) 9 | } 10 | } 11 | 12 | fn identity_loop(depth: i32, x: T) -> T { 13 | if depth == 0 { 14 | x 15 | } else { 16 | identity_loop(depth - 1, identity_loop(0, x)) 17 | } 18 | } 19 | 20 | fn main() { 21 | assert!(repeat(5, 42) == 42); 22 | assert!(identity_loop(3, true) == true); 23 | } 24 | -------------------------------------------------------------------------------- /tests/ui/pass/annot.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result != x)] 5 | fn rand_except(x: i64) -> i64 { 6 | if x == 0 { 7 | 1 8 | } else { 9 | 0 10 | } 11 | } 12 | 13 | #[thrust::requires(true)] 14 | #[thrust::ensures((result == 1) || (result == -1))] 15 | fn f(x: i64) -> i64 { 16 | let y = rand_except(x); 17 | if y > x { 18 | 1 19 | } else if y < x { 20 | -1 21 | } else { 22 | 0 23 | } 24 | } 25 | 26 | fn main() { 27 | assert!(rand_except(1) != 1); 28 | } 29 | -------------------------------------------------------------------------------- /tests/ui/fail/annot.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | #[thrust::requires(true)] 4 | #[thrust::ensures(result != x)] 5 | fn rand_except(x: i64) -> i64 { 6 | if x == 0 { 7 | 1 8 | } else { 9 | 0 10 | } 11 | } 12 | 13 | #[thrust::requires(true)] 14 | #[thrust::ensures((result == 1) || (result == -1) && result == 0)] 15 | fn f(x: i64) -> i64 { 16 | let y = rand_except(x); 17 | if y > x { 18 | 1 19 | } else if y < x { 20 | -1 21 | } else { 22 | 0 23 | } 24 | } 25 | 26 | fn main() { 27 | assert!(rand_except(1) == 0); 28 | } 29 | -------------------------------------------------------------------------------- /tests/ui/pass/just_rec.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::trusted] 5 | #[thrust::requires(true)] 6 | #[thrust::ensures(true)] 7 | fn rand() -> i32 { unimplemented!() } 8 | 9 | #[thrust::trusted] 10 | #[thrust::requires(true)] 11 | #[thrust::ensures(true)] 12 | fn rand_bool() -> bool { unimplemented!() } 13 | 14 | fn just_rec(ma: &mut i32) { 15 | if rand_bool() { 16 | return; 17 | } 18 | let mut b = rand(); 19 | just_rec(&mut b); 20 | } 21 | fn main() { 22 | let mut a = rand(); 23 | let a0 = a; 24 | just_rec(&mut a); 25 | assert!(a0 == a); 26 | } 27 | -------------------------------------------------------------------------------- /tests/ui/pass/enum_ref_drop.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X<'a, 'b> { 5 | A(&'a mut i64), 6 | B(&'b mut i64), 7 | } 8 | 9 | #[thrust::trusted] 10 | #[thrust::requires(true)] 11 | #[thrust::ensures(true)] 12 | fn rand() -> i64 { unimplemented!() } 13 | 14 | fn x(i: &mut i64) -> X { 15 | if *i >= 0 { 16 | X::A(i) 17 | } else { 18 | X::B(i) 19 | } 20 | } 21 | 22 | fn main() { 23 | let mut i = rand(); 24 | match x(&mut i) { 25 | X::A(a) => *a += 1, 26 | X::B(b) => *b = -*b, 27 | } 28 | assert!(i > 0); 29 | } 30 | -------------------------------------------------------------------------------- /tests/ui/fail/just_rec.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::trusted] 5 | #[thrust::requires(true)] 6 | #[thrust::ensures(true)] 7 | fn rand() -> i32 { unimplemented!() } 8 | 9 | #[thrust::trusted] 10 | #[thrust::requires(true)] 11 | #[thrust::ensures(true)] 12 | fn rand_bool() -> bool { unimplemented!() } 13 | 14 | fn just_rec(ma: &mut i32) { 15 | if rand_bool() { 16 | return; 17 | } 18 | let mut b = rand(); 19 | just_rec(&mut b); 20 | } 21 | fn main() { 22 | let mut a = rand(); 23 | let a0 = a; 24 | just_rec(&mut a); 25 | assert!(a0 > a); 26 | } 27 | -------------------------------------------------------------------------------- /tests/ui/fail/enum_ref_drop.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X<'a, 'b> { 5 | A(&'a mut i64), 6 | B(&'b mut i64), 7 | } 8 | 9 | #[thrust::trusted] 10 | #[thrust::requires(true)] 11 | #[thrust::ensures(true)] 12 | fn rand() -> i64 { unimplemented!() } 13 | 14 | fn x(i: &mut i64) -> X { 15 | if *i >= 0 { 16 | X::A(i) 17 | } else { 18 | X::B(i) 19 | } 20 | } 21 | 22 | fn main() { 23 | let mut i = rand(); 24 | match x(&mut i) { 25 | X::A(a) => *a += 1, 26 | X::B(b) => *b = -*b, 27 | } 28 | assert!(i > 1); 29 | } 30 | -------------------------------------------------------------------------------- /tests/ui/pass/adt.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(i64), 6 | B(bool), 7 | } 8 | 9 | #[thrust::trusted] 10 | #[thrust::requires(true)] 11 | #[thrust::ensures(true)] 12 | fn rand() -> X { unimplemented!() } 13 | 14 | fn inv(x: X) -> X { 15 | match x { 16 | X::A(i) => X::A(-i), 17 | X::B(b) => X::B(!b), 18 | } 19 | } 20 | 21 | fn is_pos(x: &X) -> bool { 22 | match x { 23 | X::A(i) => *i > 0, 24 | X::B(b) => *b, 25 | } 26 | } 27 | 28 | fn main() { 29 | let x = rand(); 30 | if is_pos(&x) { 31 | assert!(!is_pos(&inv(x))); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/ui/fail/adt.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(i64), 6 | B(bool), 7 | } 8 | 9 | #[thrust::trusted] 10 | #[thrust::requires(true)] 11 | #[thrust::ensures(true)] 12 | fn rand() -> X { unimplemented!() } 13 | 14 | fn inv(x: X) -> X { 15 | match x { 16 | X::A(i) => X::A(-i), 17 | X::B(b) => X::B(!b), 18 | } 19 | } 20 | 21 | fn is_pos(x: &X) -> bool { 22 | match x { 23 | X::A(i) => *i > 0, 24 | X::B(b) => *b, 25 | } 26 | } 27 | 28 | fn main() { 29 | let x = rand(); 30 | if is_pos(&x) { 31 | assert!(is_pos(&inv(x))); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/ui/pass/adt_poly_fn_mono.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(T), 6 | } 7 | 8 | #[thrust::trusted] 9 | #[thrust::requires(true)] 10 | #[thrust::ensures(true)] 11 | fn rand() -> X { unimplemented!() } 12 | 13 | fn inv(x: X) -> X { 14 | match x { 15 | X::A(i) => X::A(-i), 16 | } 17 | } 18 | 19 | fn is_pos(x: &X) -> bool { 20 | match x { 21 | X::A(i) => *i > 0, 22 | } 23 | } 24 | 25 | fn pos() -> X { 26 | let x = rand(); 27 | if !is_pos(&x) { loop {} } 28 | x 29 | } 30 | 31 | fn main() { 32 | let x = pos(); 33 | assert!(!is_pos(&inv(x))); 34 | } 35 | -------------------------------------------------------------------------------- /tests/ui/pass/adt_mut.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(i64), 6 | B(bool), 7 | } 8 | 9 | #[thrust::trusted] 10 | #[thrust::requires(true)] 11 | #[thrust::ensures(true)] 12 | fn rand() -> X { unimplemented!() } 13 | 14 | fn inv(x: &mut X) { 15 | match x { 16 | X::A(i) => *i = -*i, 17 | X::B(b) => *b = !*b, 18 | } 19 | } 20 | 21 | fn is_pos(x: &X) -> bool { 22 | match x { 23 | X::A(i) => *i > 0, 24 | X::B(b) => *b, 25 | } 26 | } 27 | 28 | fn main() { 29 | let mut x = rand(); 30 | if is_pos(&x) { 31 | inv(&mut x); 32 | assert!(!is_pos(&x)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/ui/pass/take_max_annot.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | #[thrust::requires(true)] 10 | #[thrust::ensures( 11 | *ma >= *mb && *mb == ^mb && ma == result || 12 | *ma < *mb && *ma == ^ma && mb == result 13 | )] 14 | fn take_max<'a>(ma: &'a mut i64, mb: &'a mut i64) -> &'a mut i64 { 15 | if *ma >= *mb { 16 | ma 17 | } else { 18 | mb 19 | } 20 | } 21 | 22 | fn main() { 23 | let mut a = rand(); 24 | let mut b = rand(); 25 | let mc = take_max(&mut a, &mut b); 26 | *mc += 1; 27 | assert!(a != b); 28 | } 29 | -------------------------------------------------------------------------------- /tests/ui/fail/adt_poly_fn_mono.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(T), 6 | } 7 | 8 | #[thrust::trusted] 9 | #[thrust::requires(true)] 10 | #[thrust::ensures(true)] 11 | fn rand() -> X { unimplemented!() } 12 | 13 | fn inv(x: X) -> X { 14 | match x { 15 | X::A(i) => X::A(-i), 16 | } 17 | } 18 | 19 | fn is_pos(x: &X) -> bool { 20 | match x { 21 | X::A(i) => *i > 0, 22 | } 23 | } 24 | 25 | fn pos() -> X { 26 | let x = rand(); 27 | if !is_pos(&x) { loop {} } 28 | x 29 | } 30 | 31 | fn main() { 32 | let x = pos(); 33 | assert!(is_pos(&inv(x))); 34 | } 35 | -------------------------------------------------------------------------------- /tests/ui/fail/adt_mut.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | pub enum X { 5 | A(i64), 6 | B(bool), 7 | } 8 | 9 | #[thrust::trusted] 10 | #[thrust::requires(true)] 11 | #[thrust::ensures(true)] 12 | fn rand() -> X { unimplemented!() } 13 | 14 | fn inv(x: &mut X) { 15 | match x { 16 | X::A(i) => *i = -*i, 17 | X::B(b) => *b = !*b, 18 | } 19 | } 20 | 21 | fn is_pos(x: &X) -> bool { 22 | match x { 23 | X::A(i) => *i > 0, 24 | X::B(b) => *b, 25 | } 26 | } 27 | 28 | fn main() { 29 | let mut x = rand(); 30 | if is_pos(&x) { 31 | inv(&mut x); 32 | assert!(is_pos(&x)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/ui/fail/take_max_annot.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | //@compile-flags: -C debug-assertions=off 3 | 4 | #[thrust::requires(true)] 5 | #[thrust::ensures(true)] 6 | #[thrust::trusted] 7 | fn rand() -> i64 { unimplemented!() } 8 | 9 | #[thrust::requires(true)] 10 | #[thrust::ensures( 11 | *ma >= *mb && *ma == ^ma && ma == result || 12 | *ma < *mb && *mb == ^mb && mb == result 13 | )] 14 | fn take_max<'a>(ma: &'a mut i64, mb: &'a mut i64) -> &'a mut i64 { 15 | if *ma >= *mb { 16 | ma 17 | } else { 18 | mb 19 | } 20 | } 21 | 22 | fn main() { 23 | let mut a = rand(); 24 | let mut b = rand(); 25 | let mc = take_max(&mut a, &mut b); 26 | *mc += 1; 27 | assert!(a != b); 28 | } 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thrust" 3 | version = "0.1.0" 4 | authors = ["coord_e "] 5 | edition = "2021" 6 | 7 | [[bin]] 8 | name = "thrust-rustc" 9 | path = "src/main.rs" 10 | test = false 11 | 12 | [lib] 13 | # TODO: why is this necessary? 14 | test = false 15 | 16 | [[test]] 17 | name = "ui" 18 | harness = false 19 | 20 | [dependencies] 21 | anyhow = "1.0.80" 22 | pretty = { version = "0.12.1", features = ["termcolor"] } 23 | tempfile = "3.10.1" 24 | thiserror = "1.0.57" 25 | tracing = "0.1.40" 26 | tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } 27 | process_control = "5.0.0" 28 | 29 | [dev-dependencies] 30 | ui_test = "0.23.0" 31 | 32 | [package.metadata.rust-analyzer] 33 | rustc_private = true 34 | -------------------------------------------------------------------------------- /tests/ui/pass/adt_poly_fn_poly.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | pub enum X { 4 | A(T), 5 | B(T), 6 | } 7 | 8 | #[thrust::trusted] 9 | #[thrust::requires(true)] 10 | #[thrust::ensures(true)] 11 | fn rand() -> X { unimplemented!() } 12 | 13 | fn is_a(x: &X) -> bool { 14 | match x { 15 | X::A(_) => true, 16 | X::B(_) => false, 17 | } 18 | } 19 | 20 | fn inv(x: X) -> X { 21 | match x { 22 | X::A(i) => X::B(i), 23 | X::B(i) => X::A(i), 24 | } 25 | } 26 | 27 | fn rand_a() -> X { 28 | let x = rand(); 29 | if !is_a(&x) { loop {} } 30 | x 31 | } 32 | 33 | #[thrust::callable] 34 | fn check() { 35 | let x = rand_a::(); 36 | assert!(!is_a(&inv(x))); 37 | } 38 | 39 | fn main() {} 40 | -------------------------------------------------------------------------------- /tests/ui/fail/adt_poly_fn_poly.rs: -------------------------------------------------------------------------------- 1 | //@error-in-other-file: Unsat 2 | 3 | pub enum X { 4 | A(T), 5 | B(T), 6 | } 7 | 8 | #[thrust::trusted] 9 | #[thrust::requires(true)] 10 | #[thrust::ensures(true)] 11 | fn rand() -> X { unimplemented!() } 12 | 13 | fn is_a(x: &X) -> bool { 14 | match x { 15 | X::A(_) => true, 16 | X::B(_) => false, 17 | } 18 | } 19 | 20 | fn inv(x: X) -> X { 21 | match x { 22 | X::A(i) => X::B(i), 23 | X::B(i) => X::A(i), 24 | } 25 | } 26 | 27 | fn rand_a() -> X { 28 | let x = rand(); 29 | if !is_a(&x) { loop {} } 30 | x 31 | } 32 | 33 | #[thrust::callable] 34 | fn check() { 35 | let x = rand_a::(); 36 | assert!(is_a(&inv(x))); 37 | } 38 | 39 | fn main() {} 40 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: {} 7 | 8 | concurrency: 9 | group: ${{ github.workflow }} 10 | 11 | jobs: 12 | docs: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | environment: 19 | name: github-pages 20 | url: ${{ steps.deployment.outputs.page_url }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | - run: cargo doc --no-deps --document-private-items 24 | - run: echo '' > target/doc/index.html 25 | - uses: actions/configure-pages@v5 26 | - uses: actions/upload-pages-artifact@v3 27 | with: 28 | path: 'target/doc' 29 | - id: deployment 30 | uses: actions/deploy-pages@v4 31 | -------------------------------------------------------------------------------- /src/refine.rs: -------------------------------------------------------------------------------- 1 | //! Core logic of refinement typing. 2 | //! 3 | //! This module includes the definition of the refinement typing environment and the template 4 | //! type generation from MIR types. 5 | //! 6 | //! This module is used by the [`crate::analyze`] module. There is currently no clear boundary between 7 | //! the `analyze` and `refine` modules, so it is a TODO to integrate this into the `analyze` 8 | //! module and remove this one. 9 | 10 | mod template; 11 | pub use template::{TemplateRegistry, TemplateScope, TypeBuilder}; 12 | 13 | mod basic_block; 14 | pub use basic_block::BasicBlockType; 15 | 16 | mod env; 17 | pub use env::{Assumption, Env, PlaceType, PlaceTypeBuilder, PlaceTypeVar, TempVarIdx, Var}; 18 | 19 | use crate::chc::DatatypeSymbol; 20 | use rustc_middle::ty as mir_ty; 21 | use rustc_span::def_id::DefId; 22 | 23 | pub fn datatype_symbol(tcx: mir_ty::TyCtxt<'_>, did: DefId) -> DatatypeSymbol { 24 | DatatypeSymbol::new(tcx.def_path_str(did).replace("::", ".")) 25 | } 26 | -------------------------------------------------------------------------------- /.github/actions/setup-z3/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Z3 2 | description: Sets up Z3 CLI in your GitHub Actions workflow. 3 | inputs: 4 | z3-version: 5 | description: The version of Z3 to install. 6 | required: false 7 | default: 4.13.0 8 | z3-variant: 9 | description: The variant of Z3 to install. 10 | required: false 11 | default: glibc-2.35 12 | z3-sha256sum: 13 | description: Expected sha256sum of downloaded binary archive. 14 | required: false 15 | default: | 16 | 884e3a411fa11b125522fc35eba3391b772517c047f8064fb4b656d92a73b38c z3-4.13.0-x64-glibc-2.35.zip 17 | bc31ad12446d7db1bd9d0ac82dec9d7b5129b8b8dd6e44b571a83ac6010d2f9b z3-4.13.0-x64-glibc-2.31.zip 18 | 50fae93d74689bc3460bec939273cf602a4c6f60a047f2a9cf48f609dc444d48 z3-4.13.0-arm64-glibc-2.35.zip 19 | runs: 20 | using: composite 21 | steps: 22 | - id: arch 23 | shell: bash 24 | run: echo "z3-arch=$RUNNER_ARCH" | tr '[:upper:]' '[:lower:]' >> $GITHUB_OUTPUT 25 | - shell: bash 26 | run: | 27 | wget -q "$RELEASE_URL/$RELEASE_NAME.zip" 28 | echo "$INPUT_Z3_SHA256SUM" | grep "$RELEASE_NAME.zip" | sha256sum --check --strict 29 | unzip "$RELEASE_NAME.zip" 30 | rm -f "$RELEASE_NAME.zip" 31 | mv "$RELEASE_NAME" ~/.setup-z3 32 | echo ~/.setup-z3/bin >> $GITHUB_PATH 33 | env: 34 | RELEASE_URL: https://github.com/Z3Prover/z3/releases/download/z3-${{ inputs.z3-version }} 35 | RELEASE_NAME: z3-${{ inputs.z3-version }}-${{ steps.arch.outputs.z3-arch }}-${{ inputs.z3-variant }} 36 | INPUT_Z3_SHA256SUM: ${{ inputs.z3-sha256sum }} 37 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | 3 | extern crate rustc_driver; 4 | extern crate rustc_interface; 5 | extern crate rustc_session; 6 | 7 | use rustc_driver::{Callbacks, Compilation, RunCompiler}; 8 | use rustc_interface::interface::{Compiler, Config}; 9 | use rustc_interface::Queries; 10 | 11 | struct CompilerCalls {} 12 | 13 | impl Callbacks for CompilerCalls { 14 | fn config(&mut self, config: &mut Config) { 15 | let attrs = &mut config.opts.unstable_opts.crate_attr; 16 | attrs.push("feature(register_tool)".to_owned()); 17 | attrs.push("register_tool(thrust)".to_owned()); 18 | } 19 | 20 | fn after_analysis<'tcx>( 21 | &mut self, 22 | _compiler: &Compiler, 23 | queries: &'tcx Queries<'tcx>, 24 | ) -> Compilation { 25 | queries.global_ctxt().unwrap().enter(|tcx| { 26 | let mut ctx = thrust::Analyzer::new(tcx); 27 | ctx.register_well_known_defs(); 28 | ctx.crate_analyzer().run(); 29 | ctx.solve(); 30 | }); 31 | Compilation::Stop 32 | } 33 | } 34 | 35 | pub fn main() { 36 | let args = std::env::args().collect::>(); 37 | 38 | use tracing_subscriber::{filter::EnvFilter, prelude::*}; 39 | tracing_subscriber::registry() 40 | .with( 41 | tracing_subscriber::fmt::layer() 42 | .with_writer(std::io::stderr) 43 | .compact() 44 | .without_time() 45 | .with_filter(EnvFilter::from_default_env()), 46 | ) 47 | .init(); 48 | 49 | let code = 50 | rustc_driver::catch_with_exit_code(|| RunCompiler::new(&args, &mut CompilerCalls {}).run()); 51 | std::process::exit(code); 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | 4 | permissions: {} 5 | 6 | jobs: 7 | fmt: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | steps: 12 | - uses: actions/checkout@v4 13 | - run: rustup component add rustfmt 14 | - run: cargo fmt --all -- --check 15 | clippy: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | steps: 20 | - uses: actions/checkout@v4 21 | - run: rustup component add clippy 22 | - run: cargo clippy -- -D warnings 23 | test: 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | env: 28 | COAR_IMAGE: ghcr.io/hiroshi-unno/coar@sha256:73144ed27a02b163d1a71b41b58f3b5414f12e91326015600cfdca64ff19f011 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: ./.github/actions/setup-z3 32 | - name: Setup thrust-pcsat-wrapper 33 | run: | 34 | docker pull "$COAR_IMAGE" 35 | 36 | cat <<"EOF" > thrust-pcsat-wrapper 37 | #!/bin/bash 38 | 39 | smt2=$(mktemp -p . --suffix .smt2) 40 | trap "rm -f $smt2" EXIT 41 | cp "$1" "$smt2" 42 | out=$( 43 | docker run --rm -v "$PWD:/mnt" -w /root/coar "$COAR_IMAGE" \ 44 | main.exe -c ./config/solver/pcsat_tbq_ar.json -p pcsp "/mnt/$smt2" 45 | ) 46 | exit_code=$? 47 | echo "${out%,*}" 48 | exit "$exit_code" 49 | EOF 50 | chmod +x thrust-pcsat-wrapper 51 | 52 | mkdir -p ~/.local/bin 53 | mv thrust-pcsat-wrapper ~/.local/bin/thrust-pcsat-wrapper 54 | - run: rustup show 55 | - uses: Swatinem/rust-cache@v2 56 | - run: cargo test 57 | -------------------------------------------------------------------------------- /src/analyze/did_cache.rs: -------------------------------------------------------------------------------- 1 | //! Retrieves and caches well-known [`DefId`]s. 2 | 3 | use std::cell::OnceCell; 4 | 5 | use rustc_middle::ty::TyCtxt; 6 | use rustc_span::def_id::DefId; 7 | use rustc_target::abi::FieldIdx; 8 | 9 | #[derive(Debug, Clone, Default)] 10 | struct DefIds { 11 | unique: OnceCell>, 12 | nonnull: OnceCell>, 13 | } 14 | 15 | /// Retrieves and caches well-known [`DefId`]s. 16 | /// 17 | /// [`DefId`]s of some well-known types can be retrieved as lang items or via the definition of 18 | /// lang items. This struct implements that logic and caches the results. 19 | #[derive(Clone)] 20 | pub struct DefIdCache<'tcx> { 21 | tcx: TyCtxt<'tcx>, 22 | def_ids: DefIds, 23 | } 24 | 25 | impl<'tcx> DefIdCache<'tcx> { 26 | pub fn new(tcx: TyCtxt<'tcx>) -> Self { 27 | Self { 28 | tcx, 29 | def_ids: DefIds::default(), 30 | } 31 | } 32 | 33 | pub fn box_(&self) -> Option { 34 | self.tcx.lang_items().owned_box() 35 | } 36 | 37 | pub fn unique(&self) -> Option { 38 | *self.def_ids.unique.get_or_init(|| { 39 | let box_did = self.box_()?; 40 | let box_first_did = 41 | self.tcx.adt_def(box_did).non_enum_variant().fields[FieldIdx::from_u32(0)].did; 42 | let unique_def = self 43 | .tcx 44 | .type_of(box_first_did) 45 | .instantiate_identity() 46 | .ty_adt_def() 47 | .expect("expected Box to contain Unique"); 48 | Some(unique_def.did()) 49 | }) 50 | } 51 | 52 | pub fn nonnull(&self) -> Option { 53 | *self.def_ids.nonnull.get_or_init(|| { 54 | let unique_did = self.unique()?; 55 | let unique_first_did = 56 | self.tcx.adt_def(unique_did).non_enum_variant().fields[FieldIdx::from_u32(0)].did; 57 | let nonnull_def = self 58 | .tcx 59 | .type_of(unique_first_did) 60 | .instantiate_identity() 61 | .ty_adt_def() 62 | .expect("expected Unique to contain NonNull"); 63 | Some(nonnull_def.did()) 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/refine/basic_block.rs: -------------------------------------------------------------------------------- 1 | //! The refinement type for a basic block. 2 | 3 | use pretty::{termcolor, Pretty}; 4 | use rustc_index::IndexVec; 5 | use rustc_middle::mir::Local; 6 | use rustc_middle::ty as mir_ty; 7 | 8 | use crate::rty; 9 | 10 | /// A special case of [`rty::FunctionType`] whose parameters are associated with [`Local`]s. 11 | /// 12 | /// Thrust handles basic blocks as functions, but it needs to associate function 13 | /// parameters with MIR [`Local`]s during its analysis. [`BasicBlockType`] includes this mapping 14 | /// from function parameters to [`Local`]s, along with the underlying function type. 15 | #[derive(Debug, Clone)] 16 | pub struct BasicBlockType { 17 | // TODO: make this completely private by exposing appropriate ctor 18 | pub(super) ty: rty::FunctionType, 19 | pub(super) locals: IndexVec, 20 | } 21 | 22 | impl<'a, 'b, D> Pretty<'a, D, termcolor::ColorSpec> for &'b BasicBlockType 23 | where 24 | D: pretty::DocAllocator<'a, termcolor::ColorSpec>, 25 | D::Doc: Clone, 26 | { 27 | fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { 28 | let separator = allocator.text(",").append(allocator.line()); 29 | let params = self 30 | .ty 31 | .params 32 | .iter() 33 | .zip(&self.locals) 34 | .map(|(ty, (local, mutbl))| { 35 | allocator 36 | .text(format!("{}{:?}:", mutbl.prefix_str(), local)) 37 | .append(allocator.space()) 38 | .append(ty.pretty(allocator)) 39 | }); 40 | allocator 41 | .intersperse(params, separator) 42 | .parens() 43 | .append(allocator.space()) 44 | .append(allocator.text("→")) 45 | .append(allocator.line()) 46 | .append(self.ty.ret.pretty(allocator)) 47 | .group() 48 | } 49 | } 50 | 51 | impl AsRef for BasicBlockType { 52 | fn as_ref(&self) -> &rty::FunctionType { 53 | &self.ty 54 | } 55 | } 56 | 57 | impl BasicBlockType { 58 | pub fn local_of_param(&self, idx: rty::FunctionParamIdx) -> Option { 59 | self.locals.get(idx).map(|(local, _)| *local) 60 | } 61 | 62 | pub fn mutbl_of_param(&self, idx: rty::FunctionParamIdx) -> Option { 63 | self.locals.get(idx).map(|(_, mutbl)| *mutbl) 64 | } 65 | 66 | pub fn to_function_ty(&self) -> rty::FunctionType { 67 | self.ty.clone() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/chc/hoice.rs: -------------------------------------------------------------------------------- 1 | //! A workaround for a bug in the Hoice CHC solver. 2 | 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | use crate::chc; 6 | 7 | struct SortDatatypes<'a> { 8 | defs: &'a [chc::Datatype], 9 | 10 | visited: HashSet, 11 | result: Vec, 12 | } 13 | 14 | fn symbol_references(ty: &chc::Datatype) -> Vec { 15 | let mut res = Vec::new(); 16 | for ctor in &ty.ctors { 17 | for selector in &ctor.selectors { 18 | selector.sort.walk(|s| { 19 | // TODO: stop using format_context::Sort directly 20 | res.push(chc::format_context::Sort::new(s).to_symbol()); 21 | }); 22 | } 23 | } 24 | res 25 | } 26 | 27 | impl<'a> SortDatatypes<'a> { 28 | pub fn new(defs: &'a [chc::Datatype]) -> Self { 29 | Self { 30 | defs, 31 | visited: Default::default(), 32 | result: Default::default(), 33 | } 34 | } 35 | 36 | fn sorted(mut self) -> Vec { 37 | for ty in self.defs { 38 | self.sort_visit(ty); 39 | } 40 | self.result 41 | } 42 | 43 | fn sort_visit(&mut self, ty: &chc::Datatype) { 44 | if self.visited.contains(&ty.symbol) { 45 | return; 46 | } 47 | self.visited.insert(ty.symbol.clone()); 48 | for sym in symbol_references(ty) { 49 | if let Some(referred_ty) = self.defs.iter().find(|t| t.symbol == sym) { 50 | self.sort_visit(referred_ty); 51 | } 52 | } 53 | self.result.push(ty.symbol.clone()); 54 | } 55 | } 56 | 57 | /// Rename to ensure the referring datatype name is lexicographically larger than the referred one. 58 | /// 59 | /// Workaround for . Applied indirectly via 60 | /// [`crate::chc::format_context::FormatContext`] when formatting [`crate::chc::System`] as SMT-LIB2. 61 | #[derive(Debug, Clone, Default)] 62 | pub struct HoiceDatatypeRenamer { 63 | prefixes: HashMap, 64 | } 65 | 66 | impl HoiceDatatypeRenamer { 67 | pub fn new(tys: &[chc::Datatype]) -> Self { 68 | let sorted = SortDatatypes::new(tys).sorted(); 69 | let prefixes = ('A'..='Z') 70 | .chain('a'..='z') 71 | .flat_map(|p| ('0'..='9').map(move |c| format!("{}{}", p, c))); 72 | let prefixes = sorted.into_iter().zip(prefixes).collect(); 73 | HoiceDatatypeRenamer { prefixes } 74 | } 75 | 76 | pub fn rename(&self, sym: &chc::DatatypeSymbol) -> chc::DatatypeSymbol { 77 | if let Some(prefix) = self.prefixes.get(sym) { 78 | chc::DatatypeSymbol::new(format!("{}_{}", prefix, sym)) 79 | } else { 80 | sym.clone() 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/rty/template.rs: -------------------------------------------------------------------------------- 1 | //! "Template" refinement types with unknown predicates to be inferred. 2 | //! 3 | //! A [`Template`] is used to generate a [`RefinedType`] with a refinement consisting of a 4 | //! single atom with a fresh predicate variable. The unknown predicate can carry dependencies, 5 | //! which are the arguments to the predicate. When Thrust infers refinement types, it 6 | //! first generates template refinement types with unknown refinements, and then 7 | //! generates constraints on the predicate variables in these templates. 8 | 9 | use std::collections::BTreeMap; 10 | 11 | use crate::chc; 12 | 13 | use super::{RefinedType, RefinedTypeVar, Type}; 14 | 15 | /// A template of a refinement type. 16 | /// 17 | /// This is a refinement type in the form of `{ T | P(x1, ..., xn) }` where `P` is a 18 | /// predicate variable, `T` is a type, and `x1, ..., xn` are the dependencies. The predicate 19 | /// variable is actually allocated when [`Template::into_refined_type`] is called. 20 | #[derive(Debug, Clone)] 21 | pub struct Template { 22 | pred_sig: chc::PredSig, 23 | atom_args: Vec>>, 24 | ty: Type, 25 | } 26 | 27 | impl Template { 28 | pub fn into_refined_type(self, pred_var_generator: F) -> RefinedType 29 | where 30 | F: FnOnce(chc::PredSig) -> chc::PredVarId, 31 | { 32 | let pred_var = pred_var_generator(self.pred_sig); 33 | RefinedType::new( 34 | self.ty, 35 | chc::Atom::new(pred_var.into(), self.atom_args).into(), 36 | ) 37 | } 38 | } 39 | 40 | /// A builder for a [`Template`]. 41 | /// 42 | /// Note that we have a convenient mechanism in the [`crate::refine`] module 43 | /// to prepare a [`TemplateBuilder`] with variables in scope, and we usually don't 44 | /// construct a [`TemplateBuilder`] directly. 45 | #[derive(Debug, Clone)] 46 | pub struct TemplateBuilder { 47 | dependencies: BTreeMap, chc::Sort>, 48 | } 49 | 50 | impl Default for TemplateBuilder { 51 | fn default() -> Self { 52 | TemplateBuilder { 53 | dependencies: Default::default(), 54 | } 55 | } 56 | } 57 | 58 | impl TemplateBuilder 59 | where 60 | FV: chc::Var, 61 | { 62 | pub fn add_dependency(&mut self, v: FV, sort: chc::Sort) { 63 | self.dependencies.insert(RefinedTypeVar::Free(v), sort); 64 | } 65 | 66 | pub fn build(mut self, ty: Type) -> Template { 67 | self.dependencies 68 | .insert(RefinedTypeVar::Value, ty.to_sort()); 69 | let mut atom_args = Vec::new(); 70 | let mut pred_sig = chc::PredSig::new(); 71 | for (v, sort) in self.dependencies.into_iter() { 72 | if sort.is_singleton() { 73 | continue; 74 | } 75 | atom_args.push(chc::Term::Var(v)); 76 | pred_sig.push(sort); 77 | } 78 | Template { 79 | pred_sig, 80 | atom_args, 81 | ty, 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/analyze/annot.rs: -------------------------------------------------------------------------------- 1 | //! Supporting implementation for parsing Thrust annotations. 2 | 3 | use rustc_ast::ast::Attribute; 4 | use rustc_ast::tokenstream::TokenStream; 5 | use rustc_index::IndexVec; 6 | use rustc_span::symbol::{Ident, Symbol}; 7 | 8 | use crate::annot; 9 | use crate::chc; 10 | use crate::rty; 11 | 12 | pub fn requires_path() -> [Symbol; 2] { 13 | [Symbol::intern("thrust"), Symbol::intern("requires")] 14 | } 15 | 16 | pub fn ensures_path() -> [Symbol; 2] { 17 | [Symbol::intern("thrust"), Symbol::intern("ensures")] 18 | } 19 | 20 | pub fn param_path() -> [Symbol; 2] { 21 | [Symbol::intern("thrust"), Symbol::intern("param")] 22 | } 23 | 24 | pub fn ret_path() -> [Symbol; 2] { 25 | [Symbol::intern("thrust"), Symbol::intern("ret")] 26 | } 27 | 28 | pub fn trusted_path() -> [Symbol; 2] { 29 | [Symbol::intern("thrust"), Symbol::intern("trusted")] 30 | } 31 | 32 | pub fn callable_path() -> [Symbol; 2] { 33 | [Symbol::intern("thrust"), Symbol::intern("callable")] 34 | } 35 | 36 | /// A [`annot::Resolver`] implementation for resolving function parameters. 37 | /// 38 | /// The parameter names and their sorts needs to be configured via 39 | /// [`ParamResolver::push_param`] before use. 40 | #[derive(Debug, Clone, Default)] 41 | pub struct ParamResolver { 42 | params: IndexVec, 43 | } 44 | 45 | impl annot::Resolver for ParamResolver { 46 | type Output = rty::FunctionParamIdx; 47 | fn resolve(&self, ident: Ident) -> Option<(Self::Output, chc::Sort)> { 48 | self.params 49 | .iter_enumerated() 50 | .find(|(_, (name, _))| name == &ident.name) 51 | .map(|(idx, (_, sort))| (idx, sort.clone())) 52 | } 53 | } 54 | 55 | impl ParamResolver { 56 | pub fn push_param(&mut self, name: Symbol, sort: chc::Sort) { 57 | self.params.push((name, sort)); 58 | } 59 | } 60 | 61 | /// A [`annot::Resolver`] implementation for resolving the special identifier `result`. 62 | /// 63 | /// The `result` identifier is used to refer to [`rty::RefinedTypeVar::Value`] in postconditions, denoting 64 | /// the return value of a function. 65 | #[derive(Debug, Clone)] 66 | pub struct ResultResolver { 67 | result_symbol: Symbol, 68 | result_sort: chc::Sort, 69 | } 70 | 71 | impl annot::Resolver for ResultResolver { 72 | type Output = rty::RefinedTypeVar; 73 | fn resolve(&self, ident: Ident) -> Option<(Self::Output, chc::Sort)> { 74 | if ident.name == self.result_symbol { 75 | Some((rty::RefinedTypeVar::Value, self.result_sort.clone())) 76 | } else { 77 | None 78 | } 79 | } 80 | } 81 | 82 | impl ResultResolver { 83 | pub fn new(result_sort: chc::Sort) -> Self { 84 | let result_symbol = Symbol::intern("result"); 85 | Self { 86 | result_symbol, 87 | result_sort, 88 | } 89 | } 90 | } 91 | 92 | pub fn extract_annot_tokens(attr: Attribute) -> TokenStream { 93 | use rustc_ast::{AttrArgs, AttrKind, DelimArgs}; 94 | 95 | let AttrKind::Normal(attr) = &attr.kind else { 96 | panic!("invalid attribute"); 97 | }; 98 | 99 | let AttrArgs::Delimited(DelimArgs { tokens, .. }, ..) = &attr.item.args else { 100 | panic!("invalid attribute"); 101 | }; 102 | 103 | tokens.clone() 104 | } 105 | 106 | pub fn split_param(ts: &TokenStream) -> (Ident, TokenStream) { 107 | use rustc_ast::token::TokenKind; 108 | use rustc_ast::tokenstream::TokenTree; 109 | 110 | let mut cursor = ts.trees(); 111 | let (ident, _) = match cursor.next() { 112 | Some(TokenTree::Token(t, _)) => t.ident().expect("expected parameter name"), 113 | _ => panic!("expected parameter name"), 114 | }; 115 | match cursor.next() { 116 | Some(TokenTree::Token(t, _)) if t.kind == TokenKind::Colon => {} 117 | _ => panic!("expected :"), 118 | } 119 | (ident, cursor.cloned().collect()) 120 | } 121 | -------------------------------------------------------------------------------- /src/rty/clause_builder.rs: -------------------------------------------------------------------------------- 1 | //! Helpers to build [`crate::chc::Clause`] from the refinement types. 2 | //! 3 | //! This module provides an extension trait named [`ClauseBuilderExt`] for [`chc::ClauseBuilder`] 4 | //! that allows it to work with refinement types. It provides a convenient way to generate CHC clauses from 5 | //! [`Refinement`]s by handling the mapping of [`super::RefinedTypeVar`] to [`chc::TermVarIdx`]. 6 | //! This is primarily used to generate clauses from [`super::subtyping`] constraints between refinement types. 7 | 8 | use crate::chc; 9 | 10 | use super::{Refinement, Type}; 11 | 12 | /// An extension trait for [`chc::ClauseBuilder`] to work with refinement types. 13 | /// 14 | /// We implement a custom logic to deal with [`Refinement`]s in [`RefinementClauseBuilder`], 15 | /// and this extension trait provides methods to create [`RefinementClauseBuilder`]s while 16 | /// specifying how to handle [`super::RefinedTypeVar::Value`] during the instantiation. 17 | pub trait ClauseBuilderExt { 18 | fn with_value_var<'a, T>(&'a mut self, ty: &Type) -> RefinementClauseBuilder<'a>; 19 | fn with_mapped_value_var(&mut self, v: T) -> RefinementClauseBuilder<'_> 20 | where 21 | T: chc::Var; 22 | } 23 | 24 | impl ClauseBuilderExt for chc::ClauseBuilder { 25 | fn with_value_var<'a, T>(&'a mut self, ty: &Type) -> RefinementClauseBuilder<'a> { 26 | let ty_sort = ty.to_sort(); 27 | let value_var = (!ty_sort.is_singleton()).then(|| self.add_var(ty_sort)); 28 | RefinementClauseBuilder { 29 | builder: self, 30 | value_var, 31 | } 32 | } 33 | 34 | fn with_mapped_value_var(&mut self, v: T) -> RefinementClauseBuilder<'_> 35 | where 36 | T: chc::Var, 37 | { 38 | let value_var = self.find_mapped_var(v); 39 | RefinementClauseBuilder { 40 | builder: self, 41 | value_var, 42 | } 43 | } 44 | } 45 | 46 | /// A builder for a CHC clause with a refinement. 47 | /// 48 | /// You can supply [`Refinement`]s as the body and head of the clause directly, and this builder 49 | /// will take care of mapping the variables appropriately. 50 | pub struct RefinementClauseBuilder<'a> { 51 | builder: &'a mut chc::ClauseBuilder, 52 | value_var: Option, 53 | } 54 | 55 | impl<'a> RefinementClauseBuilder<'a> { 56 | pub fn add_body(self, refinement: Refinement) -> Self 57 | where 58 | T: chc::Var, 59 | { 60 | let existentials: Vec<_> = refinement 61 | .existentials() 62 | .map(|(ev, sort)| (ev, sort.clone())) 63 | .collect(); 64 | let mut instantiator = refinement 65 | .map_var(|v| self.builder.mapped_var(v)) 66 | .instantiate(); 67 | for (ev, sort) in existentials { 68 | let tv = self.builder.add_var(sort); 69 | instantiator.existential(ev, tv); 70 | } 71 | if let Some(value_var) = self.value_var { 72 | instantiator.value_var(value_var); 73 | } 74 | let chc::Body { atoms, formula } = instantiator.instantiate(); 75 | for atom in atoms { 76 | self.builder.add_body(atom); 77 | } 78 | self.builder.add_body(formula); 79 | self 80 | } 81 | 82 | pub fn head(self, refinement: Refinement) -> Vec 83 | where 84 | T: chc::Var, 85 | { 86 | if refinement.has_existentials() { 87 | panic!("head refinement must not contain existentials"); 88 | } 89 | let mut instantiator = refinement 90 | .map_var(|v| self.builder.mapped_var(v)) 91 | .instantiate(); 92 | if let Some(value_var) = self.value_var { 93 | instantiator.value_var(value_var); 94 | } 95 | let chc::Body { atoms, formula } = instantiator.instantiate(); 96 | let mut cs = atoms 97 | .into_iter() 98 | .map(|a| self.builder.head(a)) 99 | .collect::>(); 100 | if !formula.is_top() { 101 | cs.push({ 102 | let mut builder = self.builder.clone(); 103 | builder.add_body(formula.not()).head(chc::Atom::bottom()) 104 | }); 105 | } 106 | cs 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/chc/debug.rs: -------------------------------------------------------------------------------- 1 | //! Attachable debug information for CHC clauses. 2 | //! 3 | //! The [`DebugInfo`] struct captures contextual information (like `tracing` spans) at the time 4 | //! of a clause's creation. This information is then pretty-printed as comments in the 5 | //! generated SMT-LIB2 file, which helps in tracing a clause back to its origin in the 6 | //! Thrust codebase. 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct Display<'a> { 10 | inner: &'a DebugInfo, 11 | line_head: &'static str, 12 | } 13 | 14 | impl<'a> std::fmt::Display for Display<'a> { 15 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 16 | write!(f, "{}", self.line_head)?; 17 | for (key, value) in &self.inner.contexts { 18 | let mut lines = value.lines(); 19 | write!(f, "{}={}", key, lines.next().unwrap_or_default())?; 20 | for line in lines { 21 | write!(f, "\n{}{}", self.line_head, line)?; 22 | } 23 | write!(f, " ")?; 24 | } 25 | Ok(()) 26 | } 27 | } 28 | 29 | /// A purely informational metadata that can be attached to a clause. 30 | #[derive(Debug, Clone, Default)] 31 | pub struct DebugInfo { 32 | contexts: Vec<(String, String)>, 33 | } 34 | 35 | fn strip_ansi_colors(s: &str) -> String { 36 | let mut line = s.to_owned(); 37 | let mut start = None; 38 | let mut offset = 0; 39 | for (i, b) in s.bytes().enumerate() { 40 | if b == b'\x1b' { 41 | start = Some(i); 42 | } 43 | if let Some(start_idx) = start { 44 | if b == b'm' { 45 | line.drain((start_idx - offset)..=(i - offset)); 46 | offset += i - start_idx + 1; 47 | start = None; 48 | } 49 | } 50 | } 51 | line 52 | } 53 | 54 | impl DebugInfo { 55 | pub fn from_current_span() -> Self { 56 | let mut debug_info = Self::default(); 57 | debug_info.context_from_current_span(); 58 | debug_info 59 | } 60 | 61 | pub fn context_from_current_span(&mut self) { 62 | // XXX: hack 63 | tracing::dispatcher::get_default(|d| { 64 | let current_span = d.current_span(); 65 | if let Some(metadata) = current_span.metadata() { 66 | self.context("span", metadata.name()); 67 | } 68 | let Some(registry) = d.downcast_ref::() else { 69 | return; 70 | }; 71 | use tracing_subscriber::registry::{LookupSpan, SpanData}; 72 | type Extension = tracing_subscriber::fmt::FormattedFields< 73 | tracing_subscriber::fmt::format::DefaultFields, 74 | >; 75 | let mut span_id = current_span.id().cloned(); 76 | while let Some(id) = span_id { 77 | let Some(data) = registry.span_data(&id) else { 78 | break; 79 | }; 80 | let exts = data.extensions(); 81 | if let Some(fields) = exts.get::() { 82 | self.context_from_formatted_fields(&fields.fields); 83 | } 84 | span_id = data.parent().cloned(); 85 | } 86 | }); 87 | } 88 | 89 | fn context_from_formatted_fields(&mut self, fields: &str) { 90 | let mut value = None; 91 | for field in fields.rsplit("\x1b[2m=\x1b[0m") { 92 | let field = strip_ansi_colors(field); 93 | if let Some(prev_value) = value { 94 | if let Some((next_value, key)) = field.rsplit_once(' ') { 95 | self.context(key, prev_value); 96 | value = Some(next_value.to_owned()); 97 | } else { 98 | self.context(&field, prev_value); 99 | break; 100 | } 101 | } else { 102 | value = Some(field); 103 | continue; 104 | } 105 | } 106 | } 107 | 108 | pub fn is_empty(&self) -> bool { 109 | self.contexts.is_empty() 110 | } 111 | 112 | pub fn context(&mut self, key: &str, value: impl Into) -> &mut Self { 113 | self.contexts.push((key.to_owned(), value.into())); 114 | self 115 | } 116 | 117 | pub fn with_context(mut self, key: &str, value: impl Into) -> Self { 118 | self.contexts.push((key.to_owned(), value.into())); 119 | self 120 | } 121 | 122 | pub fn display(&self, line_head: &'static str) -> Display { 123 | Display { 124 | inner: self, 125 | line_head, 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/rty/params.rs: -------------------------------------------------------------------------------- 1 | //! Data structures for type parameters and substitutions. 2 | 3 | use std::collections::BTreeMap; 4 | 5 | use pretty::{termcolor, Pretty}; 6 | use rustc_index::IndexVec; 7 | 8 | use crate::chc; 9 | 10 | use super::{Closed, RefinedType, Type}; 11 | 12 | rustc_index::newtype_index! { 13 | /// An index representing a type parameter. 14 | /// 15 | /// ## Note on indexing of type parameters 16 | /// 17 | /// The index of [`rustc_middle::ty::ParamTy`] is based on all generic parameters in 18 | /// the definition, including lifetimes. Given the following definition: 19 | /// 20 | /// ```rust 21 | /// struct X<'a, T> { f: &'a T } 22 | /// ``` 23 | /// 24 | /// The type of field `f` is `&T1` (not `&T0`) in MIR. However, in Thrust, we ignore lifetime 25 | /// parameters and the index of [`rty::ParamType`](super::ParamType) is based on type parameters only, giving `f` 26 | /// the type `&T0`. [`TypeBuilder`](crate::refine::TypeBuilder) takes care of this difference when translating MIR 27 | /// types to Thrust types. 28 | #[orderable] 29 | #[debug_format = "T{}"] 30 | pub struct TypeParamIdx { } 31 | } 32 | 33 | impl std::fmt::Display for TypeParamIdx { 34 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 35 | write!(f, "T{}", self.index()) 36 | } 37 | } 38 | 39 | impl<'a, 'b, D> Pretty<'a, D, termcolor::ColorSpec> for &'b TypeParamIdx 40 | where 41 | D: pretty::DocAllocator<'a, termcolor::ColorSpec>, 42 | { 43 | fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { 44 | allocator 45 | .as_string(self) 46 | .annotate(TypeParamIdx::color_spec()) 47 | } 48 | } 49 | 50 | impl TypeParamIdx { 51 | fn color_spec() -> termcolor::ColorSpec { 52 | termcolor::ColorSpec::new() 53 | } 54 | } 55 | 56 | pub type RefinedTypeArgs = IndexVec>; 57 | pub type TypeArgs = IndexVec>; 58 | 59 | /// A substitution for type parameters that maps type parameters to refinement types. 60 | #[derive(Debug, Clone)] 61 | pub struct TypeParamSubst { 62 | subst: BTreeMap>, 63 | } 64 | 65 | impl Default for TypeParamSubst { 66 | fn default() -> Self { 67 | Self { 68 | subst: Default::default(), 69 | } 70 | } 71 | } 72 | 73 | impl From> for TypeParamSubst { 74 | fn from(params: TypeArgs) -> Self { 75 | let subst = params 76 | .into_iter_enumerated() 77 | .map(|(idx, ty)| (idx, RefinedType::unrefined(ty))) 78 | .collect(); 79 | Self { subst } 80 | } 81 | } 82 | 83 | impl From> for TypeParamSubst { 84 | fn from(params: RefinedTypeArgs) -> Self { 85 | let subst = params.into_iter_enumerated().collect(); 86 | Self { subst } 87 | } 88 | } 89 | 90 | impl std::ops::Index for TypeParamSubst { 91 | type Output = RefinedType; 92 | 93 | fn index(&self, idx: TypeParamIdx) -> &Self::Output { 94 | &self.subst[&idx] 95 | } 96 | } 97 | 98 | impl TypeParamSubst { 99 | pub fn new(subst: BTreeMap>) -> Self { 100 | Self { subst } 101 | } 102 | 103 | pub fn singleton(idx: TypeParamIdx, ty: RefinedType) -> Self { 104 | let mut subst = BTreeMap::default(); 105 | subst.insert(idx, ty); 106 | Self { subst } 107 | } 108 | 109 | pub fn get(&self, idx: TypeParamIdx) -> Option<&RefinedType> { 110 | self.subst.get(&idx) 111 | } 112 | 113 | pub fn compose(&mut self, other: Self) 114 | where 115 | T: chc::Var, 116 | { 117 | for (idx, mut t1) in other.subst { 118 | t1.subst_ty_params(self); 119 | if let Some(t2) = self.subst.remove(&idx) { 120 | t1.refinement.push_conj(t2.refinement); 121 | } 122 | self.subst.insert(idx, t1); 123 | } 124 | } 125 | 126 | pub fn into_args(mut self, expected_len: usize, mut default: F) -> RefinedTypeArgs 127 | where 128 | T: chc::Var, 129 | F: FnMut(TypeParamIdx) -> RefinedType, 130 | { 131 | let mut args = RefinedTypeArgs::new(); 132 | for idx in 0..expected_len { 133 | let ty = self 134 | .subst 135 | .remove(&idx.into()) 136 | .unwrap_or_else(|| default(idx.into())); 137 | args.push(ty); 138 | } 139 | args 140 | } 141 | 142 | pub fn strip_refinement(self) -> TypeParamSubst { 143 | TypeParamSubst { 144 | subst: self 145 | .subst 146 | .into_iter() 147 | .map(|(idx, ty)| (idx, RefinedType::unrefined(ty.strip_refinement()))) 148 | .collect(), 149 | } 150 | } 151 | } 152 | 153 | impl TypeParamSubst { 154 | pub fn vacuous(self) -> TypeParamSubst { 155 | TypeParamSubst { 156 | subst: self 157 | .subst 158 | .into_iter() 159 | .map(|(idx, ty)| (idx, ty.vacuous())) 160 | .collect(), 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/chc/clause_builder.rs: -------------------------------------------------------------------------------- 1 | //! A builder for [`Clause`]s. 2 | //! 3 | //! This module provides [`ClauseBuilder`], a helper for constructing [`Clause`]s. It is 4 | //! particularly useful for managing the universally quantified variables of a clause. It can 5 | //! automatically create fresh [`TermVarIdx`] for variables from other domains (e.g., 6 | //! [`crate::rty::FunctionParamIdx`]), simplifying the generation of clauses from higher-level 7 | //! representations. 8 | 9 | use std::any::{Any, TypeId}; 10 | use std::collections::HashMap; 11 | use std::fmt::Debug; 12 | use std::hash::Hash; 13 | use std::rc::Rc; 14 | 15 | use rustc_index::IndexVec; 16 | 17 | use super::{Atom, Body, Clause, DebugInfo, Sort, TermVarIdx}; 18 | 19 | /// A convenience trait to represent constraints on variables used in [`ClauseBuilder`] at once. 20 | pub trait Var: Eq + Ord + Hash + Copy + Debug + 'static {} 21 | impl Var for T {} 22 | 23 | // https://stackoverflow.com/questions/64838355/how-do-i-create-a-hashmap-with-type-erased-keys 24 | trait Key { 25 | fn eq(&self, other: &dyn Key) -> bool; 26 | fn hash(&self) -> u64; 27 | fn as_any(&self) -> &dyn Any; 28 | } 29 | 30 | impl Key for T { 31 | fn eq(&self, other: &dyn Key) -> bool { 32 | if let Some(other) = other.as_any().downcast_ref::() { 33 | return self == other; 34 | } 35 | false 36 | } 37 | 38 | fn hash(&self) -> u64 { 39 | let mut h = std::hash::DefaultHasher::new(); 40 | // mix the typeid of T into the hash to make distinct types 41 | // provide distinct hashes 42 | Hash::hash(&(TypeId::of::(), self), &mut h); 43 | std::hash::Hasher::finish(&h) 44 | } 45 | 46 | fn as_any(&self) -> &dyn Any { 47 | self 48 | } 49 | } 50 | 51 | impl PartialEq for dyn Key { 52 | fn eq(&self, other: &Self) -> bool { 53 | Key::eq(self, other) 54 | } 55 | } 56 | 57 | impl Eq for dyn Key {} 58 | 59 | impl Hash for dyn Key { 60 | fn hash(&self, state: &mut H) { 61 | let key_hash = Key::hash(self); 62 | std::hash::Hasher::write_u64(state, key_hash); 63 | } 64 | } 65 | 66 | /// A builder for a [`Clause`]. 67 | /// 68 | /// [`Clause`] contains a list of universally quantified variables, a head atom, and a body formula. 69 | /// When building the head and body, we usually have some formulas that represents variables using 70 | /// something other than [`TermVarIdx`] (e.g. [`crate::rty::FunctionParamIdx`] or [`crate::refine::Var`]). 71 | /// These variables are usually OK to be universally quantified in the clause, so we want to keep 72 | /// the mapping of them to [`TermVarIdx`] and use it to convert the variables in the formulas 73 | /// during the construction of the clause. 74 | /// 75 | /// Also see [`crate::rty::ClauseBuilderExt`], which provides a higher-level API on top of this 76 | /// to build clauses from [`crate::rty::Refinement`]s. 77 | #[derive(Clone, Default)] 78 | pub struct ClauseBuilder { 79 | vars: IndexVec, 80 | mapped_var_indices: HashMap, TermVarIdx>, 81 | body: Body, 82 | } 83 | 84 | impl ClauseBuilder { 85 | pub fn add_mapped_var(&mut self, v: T, sort: Sort) 86 | where 87 | T: Var, 88 | { 89 | let idx = self.vars.push(sort); 90 | self.mapped_var_indices.insert(Rc::new(v), idx); 91 | } 92 | 93 | pub fn add_var(&mut self, sort: Sort) -> TermVarIdx { 94 | self.vars.push(sort) 95 | } 96 | 97 | pub fn find_mapped_var(&self, v: T) -> Option 98 | where 99 | T: Var, 100 | { 101 | let k: &dyn Key = &v; 102 | self.mapped_var_indices.get(k).copied() 103 | } 104 | 105 | pub fn mapped_var(&self, v: T) -> TermVarIdx 106 | where 107 | T: Var + Debug, 108 | { 109 | let k: &dyn Key = &v; 110 | self.mapped_var_indices 111 | .get(k) 112 | .copied() 113 | .unwrap_or_else(|| panic!("unbound var {:?}", v)) 114 | } 115 | 116 | pub fn add_body(&mut self, body: impl Into>) -> &mut Self { 117 | self.body.push_conj(body); 118 | self 119 | } 120 | 121 | pub fn head(&self, head: Atom) -> Clause { 122 | let vars = self.vars.clone(); 123 | let mut body = self.body.clone(); 124 | body.simplify(); 125 | Clause { 126 | vars, 127 | head, 128 | body, 129 | debug_info: DebugInfo::from_current_span(), 130 | } 131 | } 132 | 133 | // Currently, variables are mapped by rty::Instantiator built from `mapped_var` 134 | // (see rty::RefinementClauseBuilder) 135 | // Maybe we should remove chc::ClauseBuilder and integrate it into rty::RefinementClauseBuilder 136 | // 137 | // pub fn add_body_mapped(&mut self, atom: Atom) -> &mut Self 138 | // where 139 | // T: Var + Debug, 140 | // { 141 | // self.add_body(atom.map_var(|v| self.mapped_var(v))) 142 | // } 143 | // 144 | // pub fn add_body_formula_mapped(&mut self, formula: Formula) -> &mut Self 145 | // where 146 | // T: Var + Debug, 147 | // { 148 | // self.add_body_formula(formula.map_var(|v| self.mapped_var(v))) 149 | // } 150 | // 151 | // pub fn head_mapped(&self, head: Atom) -> Clause 152 | // where 153 | // T: Var + Debug, 154 | // { 155 | // self.head(head.map_var(|v| self.mapped_var(v))) 156 | // } 157 | } 158 | -------------------------------------------------------------------------------- /src/chc/unbox.rs: -------------------------------------------------------------------------------- 1 | //! An optimization that removes `Box` sorts and terms from a CHC system. 2 | 3 | use super::*; 4 | 5 | fn unbox_term(term: Term) -> Term { 6 | match term { 7 | Term::Var(_) | Term::Bool(_) | Term::Int(_) | Term::String(_) | Term::Null => term, 8 | Term::Box(t) => unbox_term(*t), 9 | Term::Mut(t1, t2) => Term::Mut(Box::new(unbox_term(*t1)), Box::new(unbox_term(*t2))), 10 | Term::BoxCurrent(t) => unbox_term(*t), 11 | Term::MutCurrent(t) => Term::MutCurrent(Box::new(unbox_term(*t))), 12 | Term::MutFinal(t) => Term::MutFinal(Box::new(unbox_term(*t))), 13 | Term::App(fun, args) => Term::App(fun, args.into_iter().map(unbox_term).collect()), 14 | Term::Tuple(ts) => Term::Tuple(ts.into_iter().map(unbox_term).collect()), 15 | Term::TupleProj(t, i) => Term::TupleProj(Box::new(unbox_term(*t)), i), 16 | Term::DatatypeCtor(s1, s2, args) => { 17 | Term::DatatypeCtor(s1, s2, args.into_iter().map(unbox_term).collect()) 18 | } 19 | Term::DatatypeDiscr(sym, arg) => Term::DatatypeDiscr(sym, Box::new(unbox_term(*arg))), 20 | Term::FormulaExistentialVar(sort, name) => { 21 | Term::FormulaExistentialVar(unbox_sort(sort), name) 22 | } 23 | } 24 | } 25 | 26 | fn unbox_atom(atom: Atom) -> Atom { 27 | let Atom { pred, args } = atom; 28 | let args = args.into_iter().map(unbox_term).collect(); 29 | Atom { pred, args } 30 | } 31 | 32 | fn unbox_datatype_sort(sort: DatatypeSort) -> DatatypeSort { 33 | let DatatypeSort { symbol, args } = sort; 34 | let args = args.into_iter().map(unbox_sort).collect(); 35 | DatatypeSort { symbol, args } 36 | } 37 | 38 | fn unbox_sort(sort: Sort) -> Sort { 39 | match sort { 40 | Sort::Null => Sort::Null, 41 | Sort::Int => Sort::Int, 42 | Sort::Bool => Sort::Bool, 43 | Sort::String => Sort::String, 44 | Sort::Param(i) => Sort::Param(i), 45 | Sort::Box(inner) => unbox_sort(*inner), 46 | Sort::Mut(inner) => Sort::Mut(Box::new(unbox_sort(*inner))), 47 | Sort::Tuple(sorts) => Sort::Tuple(sorts.into_iter().map(unbox_sort).collect()), 48 | Sort::Datatype(sort) => Sort::Datatype(unbox_datatype_sort(sort)), 49 | } 50 | } 51 | 52 | fn unbox_formula(formula: Formula) -> Formula { 53 | match formula { 54 | Formula::Atom(atom) => Formula::Atom(unbox_atom(atom)), 55 | Formula::Not(fo) => Formula::Not(Box::new(unbox_formula(*fo))), 56 | Formula::And(fs) => Formula::And(fs.into_iter().map(unbox_formula).collect()), 57 | Formula::Or(fs) => Formula::Or(fs.into_iter().map(unbox_formula).collect()), 58 | Formula::Exists(vars, fo) => { 59 | let vars = vars.into_iter().map(|(v, s)| (v, unbox_sort(s))).collect(); 60 | Formula::Exists(vars, Box::new(unbox_formula(*fo))) 61 | } 62 | } 63 | } 64 | 65 | fn unbox_body(body: Body) -> Body { 66 | let Body { atoms, formula } = body; 67 | let atoms = atoms.into_iter().map(unbox_atom).collect(); 68 | let formula = unbox_formula(formula); 69 | Body { atoms, formula } 70 | } 71 | 72 | fn unbox_clause(clause: Clause) -> Clause { 73 | let Clause { 74 | vars, 75 | head, 76 | body, 77 | debug_info, 78 | } = clause; 79 | let vars = vars.into_iter().map(unbox_sort).collect(); 80 | let head = unbox_atom(head); 81 | let body = unbox_body(body); 82 | Clause { 83 | vars, 84 | head, 85 | body, 86 | debug_info, 87 | } 88 | } 89 | 90 | fn unbox_pred_var_def(pred_var_def: PredVarDef) -> PredVarDef { 91 | let PredVarDef { sig, debug_info } = pred_var_def; 92 | let sig = sig.into_iter().map(unbox_sort).collect(); 93 | PredVarDef { sig, debug_info } 94 | } 95 | 96 | fn unbox_datatype_selector(selector: DatatypeSelector) -> DatatypeSelector { 97 | let DatatypeSelector { symbol, sort } = selector; 98 | let sort = unbox_sort(sort); 99 | DatatypeSelector { symbol, sort } 100 | } 101 | 102 | fn unbox_datatype_ctor(ctor: DatatypeCtor) -> DatatypeCtor { 103 | let DatatypeCtor { 104 | symbol, 105 | selectors, 106 | discriminant, 107 | } = ctor; 108 | let selectors = selectors.into_iter().map(unbox_datatype_selector).collect(); 109 | DatatypeCtor { 110 | symbol, 111 | selectors, 112 | discriminant, 113 | } 114 | } 115 | 116 | fn unbox_datatype(datatype: Datatype) -> Datatype { 117 | let Datatype { 118 | symbol, 119 | params, 120 | ctors, 121 | } = datatype; 122 | let ctors = ctors.into_iter().map(unbox_datatype_ctor).collect(); 123 | Datatype { 124 | symbol, 125 | params, 126 | ctors, 127 | } 128 | } 129 | 130 | /// Remove all `Box` sorts and `Box`/`BoxCurrent` terms from the system. 131 | /// 132 | /// The box values in Thrust represent an owned pointer, but are logically equivalent to the inner type. 133 | /// This pass removes them to reduce the complexity of the CHCs sent to the solver. 134 | /// This function traverses a [`System`] and removes all `Box` related constructs. 135 | pub fn unbox(system: System) -> System { 136 | let System { 137 | clauses, 138 | pred_vars, 139 | datatypes, 140 | } = system; 141 | let datatypes = datatypes.into_iter().map(unbox_datatype).collect(); 142 | let clauses = clauses.into_iter().map(unbox_clause).collect(); 143 | let pred_vars = pred_vars.into_iter().map(unbox_pred_var_def).collect(); 144 | System { 145 | clauses, 146 | pred_vars, 147 | datatypes, 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/analyze/basic_block/drop_point.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use rustc_index::bit_set::BitSet; 4 | use rustc_middle::mir::{self, BasicBlock, Body, Local}; 5 | use rustc_mir_dataflow::{impls::MaybeLiveLocals, ResultsCursor}; 6 | 7 | #[derive(Debug, Clone, Default)] 8 | pub struct DropPoints { 9 | // TODO: ad-hoc 10 | pub before_statements: Vec, 11 | after_statements: Vec>, 12 | after_terminator: HashMap>, 13 | } 14 | 15 | impl DropPoints { 16 | pub fn builder<'mir, 'tcx>(body: &'mir Body<'tcx>) -> DropPointsBuilder<'mir, 'tcx> { 17 | DropPointsBuilder { 18 | body, 19 | bb_ins_cache: HashMap::new(), 20 | } 21 | } 22 | 23 | pub fn position(&self, local: Local) -> Option { 24 | self.after_statements 25 | .iter() 26 | .position(|s| s.contains(local)) 27 | .or_else(|| { 28 | self.after_terminator 29 | .values() 30 | .any(|s| s.contains(local)) 31 | .then_some(self.after_statements.len()) 32 | }) 33 | } 34 | 35 | pub fn remove_after_statement(&mut self, statement_index: usize, local: Local) -> bool { 36 | self.after_statements[statement_index].remove(local) 37 | } 38 | 39 | pub fn insert_after_statement(&mut self, statement_index: usize, local: Local) -> bool { 40 | self.after_statements[statement_index].insert(local) 41 | } 42 | 43 | pub fn after_statement(&self, statement_index: usize) -> BitSet { 44 | self.after_statements[statement_index].clone() 45 | } 46 | 47 | pub fn after_terminator(&self, target: &BasicBlock) -> BitSet { 48 | let mut t = self.after_terminator[target].clone(); 49 | t.union(self.after_statements.last().unwrap()); 50 | t 51 | } 52 | } 53 | 54 | #[derive(Debug, Clone)] 55 | pub struct DropPointsBuilder<'mir, 'tcx> { 56 | body: &'mir Body<'tcx>, 57 | bb_ins_cache: HashMap>, 58 | } 59 | 60 | fn def_local<'a, 'tcx, T: mir::visit::MirVisitable<'tcx> + ?Sized>( 61 | visitable: &'a T, 62 | ) -> Option { 63 | struct Visitor { 64 | local: Option, 65 | } 66 | impl<'tcx> mir::visit::Visitor<'tcx> for Visitor { 67 | fn visit_local( 68 | &mut self, 69 | local: Local, 70 | ctxt: mir::visit::PlaceContext, 71 | _location: mir::Location, 72 | ) { 73 | if ctxt.is_place_assignment() { 74 | let old = self.local.replace(local); 75 | assert!(old.is_none()); 76 | } 77 | } 78 | } 79 | let mut visitor = Visitor { local: None }; 80 | visitable.apply(mir::Location::START, &mut visitor); 81 | visitor.local 82 | } 83 | 84 | impl<'mir, 'tcx> DropPointsBuilder<'mir, 'tcx> { 85 | pub fn build( 86 | &mut self, 87 | results: &mut ResultsCursor<'mir, 'tcx, MaybeLiveLocals>, 88 | bb: BasicBlock, 89 | ) -> DropPoints { 90 | let data = &self.body.basic_blocks[bb]; 91 | 92 | let mut after_terminator = HashMap::new(); 93 | let mut after_statements = Vec::new(); 94 | after_statements.resize_with(data.statements.len() + 1, || BitSet::new_empty(0)); 95 | 96 | results.seek_to_block_end(bb); 97 | let live_locals_after_terminator = results.get().clone(); 98 | 99 | use rustc_data_structures::graph::WithSuccessors as _; 100 | let mut ins = BitSet::new_empty(self.body.local_decls.len()); 101 | for succ_bb in self.body.basic_blocks.successors(bb) { 102 | self.bb_ins_cache.entry(succ_bb).or_insert_with(|| { 103 | results.seek_to_block_start(succ_bb); 104 | results.get().clone() 105 | }); 106 | let edge_drops = { 107 | let mut t = live_locals_after_terminator.clone(); 108 | t.subtract(&self.bb_ins_cache[&succ_bb]); 109 | t 110 | }; 111 | after_terminator.insert(succ_bb, edge_drops); 112 | ins.union(&self.bb_ins_cache[&succ_bb]); 113 | } 114 | 115 | tracing::debug!(?live_locals_after_terminator, ?ins); 116 | // FIXME: isn't it appropriate to use live_locals_after_terminator here? but it lacks 117 | // some locals from successor ins... 118 | let mut last_live_locals = ins; 119 | // TODO: we may use seek_before_primary_effect here 120 | for statement_index in (0..=data.statements.len()).rev() { 121 | let loc = mir::Location { 122 | statement_index, 123 | block: bb, 124 | }; 125 | results.seek_after_primary_effect(loc); 126 | let live_locals = results.get().clone(); 127 | tracing::debug!(?live_locals, ?loc); 128 | after_statements[statement_index] = { 129 | let mut t = live_locals.clone(); 130 | if let Some(def) = def_local(data.visitable(statement_index)) { 131 | t.insert(def); 132 | } 133 | t.subtract(&last_live_locals); 134 | t 135 | }; 136 | last_live_locals = live_locals; 137 | } 138 | 139 | self.bb_ins_cache.insert(bb, last_live_locals.clone()); 140 | 141 | tracing::info!( 142 | ?bb, 143 | ?after_statements, 144 | ?after_terminator, 145 | "analyzed implicit drop points" 146 | ); 147 | DropPoints { 148 | before_statements: Default::default(), 149 | after_statements, 150 | after_terminator, 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/analyze/basic_block/visitor.rs: -------------------------------------------------------------------------------- 1 | use rustc_middle::mir::{self, Local}; 2 | use rustc_middle::ty::{self as mir_ty, TyCtxt}; 3 | 4 | use crate::analyze::ReplacePlacesVisitor; 5 | 6 | pub struct ReborrowVisitor<'a, 'tcx, 'ctx> { 7 | tcx: TyCtxt<'tcx>, 8 | analyzer: &'a mut super::Analyzer<'tcx, 'ctx>, 9 | } 10 | 11 | impl<'tcx> ReborrowVisitor<'_, 'tcx, '_> { 12 | fn insert_borrow(&mut self, place: mir::Place<'tcx>, inner_ty: mir_ty::Ty<'tcx>) -> Local { 13 | let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); 14 | let ty = mir_ty::Ty::new_mut_ref(self.tcx, r, inner_ty); 15 | let decl = mir::LocalDecl::new(ty, Default::default()).immutable(); 16 | let new_local = self.analyzer.local_decls.push(decl); 17 | let new_local_ty = self.analyzer.borrow_place_(place, inner_ty); 18 | self.analyzer.bind_local(new_local, new_local_ty); 19 | tracing::info!(old_place = ?place, ?new_local, "implicitly borrowed"); 20 | new_local 21 | } 22 | 23 | fn insert_reborrow(&mut self, place: mir::Place<'tcx>, inner_ty: mir_ty::Ty<'tcx>) -> Local { 24 | let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); 25 | let ty = mir_ty::Ty::new_mut_ref(self.tcx, r, inner_ty); 26 | let decl = mir::LocalDecl::new(ty, Default::default()).immutable(); 27 | let new_local = self.analyzer.local_decls.push(decl); 28 | let new_local_ty = self.analyzer.borrow_place_(place, inner_ty); 29 | self.analyzer.bind_local(new_local, new_local_ty); 30 | tracing::info!(old_place = ?place, ?new_local, "implicitly reborrowed"); 31 | new_local 32 | } 33 | } 34 | 35 | impl<'a, 'tcx, 'ctx> mir::visit::MutVisitor<'tcx> for ReborrowVisitor<'a, 'tcx, 'ctx> { 36 | fn tcx(&self) -> TyCtxt<'tcx> { 37 | self.tcx 38 | } 39 | 40 | fn visit_assign( 41 | &mut self, 42 | place: &mut mir::Place<'tcx>, 43 | rvalue: &mut mir::Rvalue<'tcx>, 44 | location: mir::Location, 45 | ) { 46 | if !self.analyzer.is_defined(place.local) { 47 | self.super_assign(place, rvalue, location); 48 | return; 49 | } 50 | 51 | if place.projection.is_empty() && self.analyzer.is_mut_local(place.local) { 52 | let ty = self.analyzer.local_decls[place.local].ty; 53 | let new_local = self.insert_borrow(place.local.into(), ty); 54 | let new_place = self.tcx.mk_place_deref(new_local.into()); 55 | ReplacePlacesVisitor::with_replacement(self.tcx, place.local.into(), new_place) 56 | .visit_rvalue(rvalue, location); 57 | *place = new_place; 58 | self.super_assign(place, rvalue, location); 59 | return; 60 | } 61 | 62 | let inner_place = if place.projection.last() == Some(&mir::PlaceElem::Deref) { 63 | // *m = *m + 1 => m1 = &mut m; *m1 = *m + 1 64 | let mut projection = place.projection.as_ref().to_vec(); 65 | projection.pop(); 66 | mir::Place { 67 | local: place.local, 68 | projection: self.tcx.mk_place_elems(&projection), 69 | } 70 | } else { 71 | // s.0 = s.0 + 1 => m1 = &mut s.0; *m1 = *m1 + 1 72 | *place 73 | }; 74 | 75 | let ty = inner_place.ty(&self.analyzer.local_decls, self.tcx).ty; 76 | let (new_local, new_place) = match ty.kind() { 77 | mir_ty::TyKind::Ref(_, inner_ty, m) if m.is_mut() => { 78 | let new_local = self.insert_reborrow(*place, *inner_ty); 79 | (new_local, new_local.into()) 80 | } 81 | mir_ty::TyKind::Adt(adt, args) if adt.is_box() => { 82 | let inner_ty = args.type_at(0); 83 | let new_local = self.insert_borrow(*place, inner_ty); 84 | (new_local, new_local.into()) 85 | } 86 | _ => { 87 | let new_local = self.insert_borrow(*place, ty); 88 | (new_local, self.tcx.mk_place_deref(new_local.into())) 89 | } 90 | }; 91 | 92 | ReplacePlacesVisitor::with_replacement(self.tcx, inner_place, new_place) 93 | .visit_rvalue(rvalue, location); 94 | *place = self.tcx.mk_place_deref(new_local.into()); 95 | self.super_assign(place, rvalue, location); 96 | } 97 | 98 | // TODO: is it always true that the operand is not referred again in rvalue 99 | fn visit_operand(&mut self, operand: &mut mir::Operand<'tcx>, location: mir::Location) { 100 | let Some(p) = operand.place() else { 101 | self.super_operand(operand, location); 102 | return; 103 | }; 104 | 105 | let mir_ty::TyKind::Ref(_, inner_ty, m) = 106 | p.ty(&self.analyzer.local_decls, self.tcx).ty.kind() 107 | else { 108 | self.super_operand(operand, location); 109 | return; 110 | }; 111 | 112 | if m.is_mut() { 113 | let new_local = self.insert_reborrow(self.tcx.mk_place_deref(p), *inner_ty); 114 | *operand = mir::Operand::Move(new_local.into()); 115 | } 116 | 117 | self.super_operand(operand, location); 118 | } 119 | } 120 | 121 | impl<'a, 'tcx, 'ctx> ReborrowVisitor<'a, 'tcx, 'ctx> { 122 | pub fn new(analyzer: &'a mut super::Analyzer<'tcx, 'ctx>) -> Self { 123 | let tcx = analyzer.tcx; 124 | Self { analyzer, tcx } 125 | } 126 | 127 | pub fn visit_statement(&mut self, stmt: &mut mir::Statement<'tcx>) { 128 | // dummy location 129 | mir::visit::MutVisitor::visit_statement(self, stmt, mir::Location::START); 130 | } 131 | 132 | pub fn visit_terminator(&mut self, term: &mut mir::Terminator<'tcx>) { 133 | // dummy location 134 | mir::visit::MutVisitor::visit_terminator(self, term, mir::Location::START); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/rty/subtyping.rs: -------------------------------------------------------------------------------- 1 | //! Translation of subtyping relations into CHC constraints. 2 | 3 | use crate::chc; 4 | use crate::pretty::PrettyDisplayExt; 5 | 6 | use super::{ClauseBuilderExt as _, Closed, PointerKind, RefKind, RefinedType, Type}; 7 | 8 | /// A scope for building clauses. 9 | /// 10 | /// The construction of CHC clauses requires knowledge of the current 11 | /// environment to determine variable sorts and include necessary premises. 12 | /// This trait abstracts the preparation of a [`chc::ClauseBuilder`] to allow an 13 | /// environment defined outside of this module (in Thrust, [`crate::refine::Env`]) 14 | /// to build a [`chc::ClauseBuilder`] equipped with in-scope variables and assumptions. 15 | pub trait ClauseScope { 16 | fn build_clause(&self) -> chc::ClauseBuilder; 17 | } 18 | 19 | impl ClauseScope for &T 20 | where 21 | T: ClauseScope, 22 | { 23 | fn build_clause(&self) -> chc::ClauseBuilder { 24 | T::build_clause(self) 25 | } 26 | } 27 | 28 | impl ClauseScope for chc::ClauseBuilder { 29 | fn build_clause(&self) -> chc::ClauseBuilder { 30 | self.clone() 31 | } 32 | } 33 | 34 | /// Produces CHC constraints for subtyping relations. 35 | pub trait Subtyping { 36 | #[must_use] 37 | fn relate_sub_type( 38 | &self, 39 | got: &Type, 40 | expected: &Type, 41 | ) -> Vec; 42 | 43 | #[must_use] 44 | fn relate_sub_refined_type( 45 | &self, 46 | got: &RefinedType, 47 | expected: &RefinedType, 48 | ) -> Vec; 49 | 50 | #[must_use] 51 | fn relate_equal_refined_type( 52 | &self, 53 | got: &RefinedType, 54 | expected: &RefinedType, 55 | ) -> Vec; 56 | } 57 | 58 | impl Subtyping for C 59 | where 60 | C: ClauseScope, 61 | { 62 | fn relate_sub_type(&self, got: &Type, expected: &Type) -> Vec 63 | where 64 | T: chc::Var, 65 | U: chc::Var, 66 | { 67 | tracing::debug!(got = %got.display(), expected = %expected.display(), "sub_type"); 68 | 69 | let mut clauses = Vec::new(); 70 | match (got, expected) { 71 | (Type::Int, Type::Int) 72 | | (Type::Bool, Type::Bool) 73 | | (Type::String, Type::String) 74 | | (Type::Never, Type::Never) => {} 75 | (Type::Enum(got), Type::Enum(expected)) if got.symbol() == expected.symbol() => { 76 | for (got_ty, expected_ty) in got.args.iter().zip(expected.args.iter()) { 77 | let cs = self.relate_sub_refined_type(got_ty, expected_ty); 78 | clauses.extend(cs); 79 | } 80 | } 81 | (Type::Tuple(got), Type::Tuple(expected)) 82 | if got.elems.len() == expected.elems.len() => 83 | { 84 | for (got_ty, expected_ty) in got.elems.iter().zip(expected.elems.iter()) { 85 | let cs = self.relate_sub_refined_type(got_ty, expected_ty); 86 | clauses.extend(cs); 87 | } 88 | } 89 | (Type::Pointer(got), Type::Pointer(expected)) if got.kind == expected.kind => { 90 | match got.kind { 91 | PointerKind::Ref(RefKind::Immut) => { 92 | let cs = self.relate_sub_refined_type(&got.elem, &expected.elem); 93 | clauses.extend(cs); 94 | } 95 | PointerKind::Own | PointerKind::Ref(RefKind::Mut) => { 96 | let cs = self.relate_equal_refined_type(&got.elem, &expected.elem); 97 | clauses.extend(cs); 98 | } 99 | } 100 | } 101 | (Type::Function(got), Type::Function(expected)) => { 102 | // TODO: check length is equal 103 | let mut builder = chc::ClauseBuilder::default(); 104 | for (param_idx, param_rty) in got.params.iter_enumerated() { 105 | let param_sort = param_rty.ty.to_sort(); 106 | if !param_sort.is_singleton() { 107 | builder.add_mapped_var(param_idx, param_sort); 108 | } 109 | } 110 | for (got_ty, expected_ty) in got.params.iter().zip(expected.params.iter()) { 111 | let cs = builder.relate_sub_refined_type(expected_ty, got_ty); 112 | clauses.extend(cs); 113 | } 114 | let cs = builder.relate_sub_refined_type(&got.ret, &expected.ret); 115 | clauses.extend(cs); 116 | } 117 | _ => panic!( 118 | "inconsistent types: got={}, expected={}", 119 | got.display(), 120 | expected.display() 121 | ), 122 | } 123 | clauses 124 | } 125 | 126 | fn relate_sub_refined_type( 127 | &self, 128 | got: &RefinedType, 129 | expected: &RefinedType, 130 | ) -> Vec 131 | where 132 | T: chc::Var, 133 | U: chc::Var, 134 | { 135 | tracing::debug!(got = %got.display(), expected = %expected.display(), "sub_refined_type"); 136 | 137 | let mut clauses = self.relate_sub_type(&got.ty, &expected.ty); 138 | 139 | let cs = self 140 | .build_clause() 141 | .with_value_var(&got.ty) 142 | .add_body(got.refinement.clone()) 143 | .head(expected.refinement.clone()); 144 | clauses.extend(cs); 145 | clauses 146 | } 147 | 148 | fn relate_equal_refined_type( 149 | &self, 150 | got: &RefinedType, 151 | expected: &RefinedType, 152 | ) -> Vec 153 | where 154 | T: chc::Var, 155 | U: chc::Var, 156 | { 157 | tracing::debug!(got = %got.display(), expected = %expected.display(), "equal_refined_type"); 158 | 159 | let mut clauses = self.relate_sub_refined_type(got, expected); 160 | clauses.extend(self.relate_sub_refined_type(expected, got)); 161 | clauses 162 | } 163 | } 164 | 165 | #[must_use] 166 | pub fn relate_sub_closed_type(got: &Type, expected: &Type) -> Vec { 167 | tracing::debug!(got = %got.display(), expected = %expected.display(), "relate_sub_closed_type"); 168 | chc::ClauseBuilder::default().relate_sub_type(got, expected) 169 | } 170 | -------------------------------------------------------------------------------- /src/pretty.rs: -------------------------------------------------------------------------------- 1 | //! A set of utilities for pretty-printing various data structures. 2 | //! 3 | //! It uses the [`pretty`] crate to provide a flexible and configurable way to format complex 4 | //! data structures for display. The main entry point is the [`PrettyDisplayExt`] trait, 5 | //! which provides a [`PrettyDisplayExt::display`] method that returns a [`Display`] object to 6 | //! turn [`Pretty`] values into [`std::fmt::Display`] that can be used with standard formatting macros. 7 | //! 8 | //! This is primarily used for debugging and logging purposes, to make the output of the tool 9 | //! more readable. 10 | 11 | use rustc_index::{IndexSlice, IndexVec}; 12 | 13 | use pretty::{termcolor, BuildDoc, DocAllocator, DocPtr, Pretty}; 14 | 15 | /// A wrapper around a [`crate::rty::FunctionType`] that provides a [`Pretty`] implementation. 16 | pub struct FunctionType<'a, FV> { 17 | pub params: 18 | &'a rustc_index::IndexVec>, 19 | pub ret: &'a crate::rty::RefinedType, 20 | } 21 | 22 | impl<'a, 'b, 'c, D, FV> Pretty<'a, D, termcolor::ColorSpec> for &'b FunctionType<'c, FV> 23 | where 24 | FV: crate::chc::Var, 25 | D: pretty::DocAllocator<'a, termcolor::ColorSpec>, 26 | D::Doc: Clone, 27 | { 28 | fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { 29 | let separator = allocator.text(",").append(allocator.line()); 30 | allocator 31 | .intersperse(self.params.iter().map(|ty| ty.pretty(allocator)), separator) 32 | .parens() 33 | .append(allocator.space()) 34 | .append(allocator.text("→")) 35 | .append(allocator.line()) 36 | .append(self.ret.pretty(allocator)) 37 | .group() 38 | } 39 | } 40 | 41 | impl<'a, FV> FunctionType<'a, FV> { 42 | pub fn new( 43 | params: &'a IndexVec>, 44 | ret: &'a crate::rty::RefinedType, 45 | ) -> FunctionType<'a, FV> { 46 | FunctionType { params, ret } 47 | } 48 | } 49 | 50 | /// A wrapper around a slice that provides a [`Pretty`] implementation. 51 | #[derive(Debug, Clone)] 52 | pub struct PrettySlice<'a, T> { 53 | slice: &'a [T], 54 | } 55 | 56 | impl<'a, 'b, 'c, D, T> Pretty<'a, D, termcolor::ColorSpec> for &'c PrettySlice<'b, T> 57 | where 58 | &'b T: Pretty<'a, D, termcolor::ColorSpec>, 59 | D: pretty::DocAllocator<'a, termcolor::ColorSpec>, 60 | D::Doc: Clone, 61 | { 62 | fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { 63 | let separator = allocator.text(",").append(allocator.line()); 64 | allocator 65 | .intersperse(self.slice.iter().map(|ty| ty.pretty(allocator)), separator) 66 | .group() 67 | .brackets() 68 | } 69 | } 70 | 71 | // useful for debugging 72 | #[allow(dead_code)] 73 | pub trait PrettySliceExt { 74 | type Item; 75 | fn pretty_slice(&self) -> PrettySlice; 76 | } 77 | 78 | impl PrettySliceExt for [T] { 79 | type Item = T; 80 | fn pretty_slice(&self) -> PrettySlice { 81 | PrettySlice { slice: self } 82 | } 83 | } 84 | 85 | impl PrettySliceExt for Vec { 86 | type Item = T; 87 | fn pretty_slice(&self) -> PrettySlice { 88 | PrettySlice { 89 | slice: self.as_slice(), 90 | } 91 | } 92 | } 93 | 94 | impl PrettySliceExt for IndexSlice { 95 | type Item = T; 96 | fn pretty_slice(&self) -> PrettySlice { 97 | PrettySlice { slice: &self.raw } 98 | } 99 | } 100 | 101 | impl PrettySliceExt for IndexVec { 102 | type Item = T; 103 | fn pretty_slice(&self) -> PrettySlice { 104 | PrettySlice { 105 | slice: self.raw.as_slice(), 106 | } 107 | } 108 | } 109 | 110 | /// A wrapper around a type that provides a [`std::fmt::Display`] implementation via [`Pretty`]. 111 | #[derive(Debug, Clone)] 112 | pub struct Display<'a, T> { 113 | value: &'a T, 114 | width: usize, 115 | } 116 | 117 | impl<'a, T> std::fmt::Display for Display<'a, T> 118 | where 119 | &'a T: for<'b> Pretty<'b, pretty::Arena<'b, termcolor::ColorSpec>, termcolor::ColorSpec>, 120 | { 121 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 122 | self.to_doc(&pretty::Arena::new()).render_fmt(self.width, f) 123 | } 124 | } 125 | 126 | pub trait PrettyDisplayExt: Sized { 127 | fn display(&self) -> Display; 128 | } 129 | 130 | impl

PrettyDisplayExt for P 131 | where 132 | for<'a, 'b> &'b P: Pretty<'a, pretty::Arena<'a, termcolor::ColorSpec>, termcolor::ColorSpec>, 133 | { 134 | fn display(&self) -> Display { 135 | Display::new(self) 136 | } 137 | } 138 | 139 | const DEFAULT_WIDTH: usize = 150; 140 | 141 | #[allow(dead_code)] 142 | impl<'a, T> Display<'a, T> { 143 | fn new(value: &'a T) -> Self { 144 | Display { 145 | value, 146 | width: DEFAULT_WIDTH, 147 | } 148 | } 149 | 150 | fn to_doc<'b, D>(&self, alloc: &'b D) -> BuildDoc<'b, D::Doc, termcolor::ColorSpec> 151 | where 152 | &'a T: Pretty<'b, D, termcolor::ColorSpec>, 153 | D: DocAllocator<'b, termcolor::ColorSpec>, 154 | D::Doc: DocPtr<'b, termcolor::ColorSpec> + Clone, 155 | { 156 | self.value.pretty(alloc).1 157 | } 158 | 159 | fn to_doc_newline<'b, D>(&self, alloc: &'b D) -> BuildDoc<'b, D::Doc, termcolor::ColorSpec> 160 | where 161 | &'a T: Pretty<'b, D, termcolor::ColorSpec>, 162 | D: DocAllocator<'b, termcolor::ColorSpec>, 163 | D::Doc: DocPtr<'b, termcolor::ColorSpec> + Clone, 164 | { 165 | self.value.pretty(alloc).append(alloc.hardline()).1 166 | } 167 | 168 | pub fn render(&self, writer: W) -> std::io::Result<()> 169 | where 170 | &'a T: for<'b> Pretty<'b, pretty::Arena<'b, termcolor::ColorSpec>, termcolor::ColorSpec>, 171 | W: termcolor::WriteColor, 172 | { 173 | let width = self.width; 174 | self.to_doc_newline(&pretty::Arena::new()) 175 | .render_colored(width, writer) 176 | } 177 | 178 | pub fn render_stdout(&self) -> std::io::Result<()> 179 | where 180 | &'a T: for<'b> Pretty<'b, pretty::Arena<'b, termcolor::ColorSpec>, termcolor::ColorSpec>, 181 | { 182 | self.render(termcolor::StandardStream::stdout( 183 | termcolor::ColorChoice::Auto, 184 | )) 185 | } 186 | 187 | pub fn width(&mut self, width: usize) -> &mut Self { 188 | self.width = width; 189 | self 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thrust 2 | 3 | Thrust is a refinement type checking and inference tool for Rust. 4 | 5 | ## Getting Started 6 | 7 | - Make sure [Z3](https://github.com/Z3Prover/z3) is installed. 8 | - The main binary, `thrust-rustc`, behaves like `rustc` but includes Thrust's verification. 9 | - In the following instructions, we assume the Thrust source code is cloned locally and commands are executed within it. 10 | 11 | Take the following Rust code (`gcd.rs`): 12 | 13 | ```rust 14 | fn gcd(mut a: i32, mut b: i32) -> i32 { 15 | while a != b { 16 | let (l, r) = if a < b { 17 | (&mut a, &b) 18 | } else { 19 | (&mut b, &a) 20 | }; 21 | *l -= *r; 22 | } 23 | a 24 | } 25 | 26 | #[thrust::callable] 27 | fn check_gcd(a: i32, b: i32) { 28 | assert!(gcd(a, b) <= a); 29 | } 30 | 31 | fn main() {} 32 | ``` 33 | 34 | Let Thrust verify that the program is correct. Here, we use `cargo run` in the Thrust source tree to build and run `thrust-rustc`. Note that you need to disable the debug overflow assertions in rustc, as they are currently not supported in Thrust. 35 | 36 | ```console 37 | $ cargo run -- -Adead_code -C debug-assertions=false gcd.rs && echo 'safe' 38 | Compiling thrust v0.1.0 (/home/coord_e/rust-refinement/thrust) 39 | Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s 40 | Running `target/debug/thrust-rustc -Adead_code -C debug-assertions=false gcd.rs` 41 | error: verification error: Unsat 42 | 43 | error: aborting due to 1 previous error 44 | ``` 45 | 46 | Thrust says the program is not safe (possible to panic). In fact, we have a bug in our `gcd` function: 47 | 48 | ```diff 49 | fn gcd(mut a: i32, mut b: i32) -> i32 { 50 | while a != b { 51 | - let (l, r) = if a < b { 52 | + let (l, r) = if a > b { 53 | (&mut a, &b) 54 | } else { 55 | (&mut b, &a) 56 | ``` 57 | 58 | Now Thrust verifies the program is actually safe. 59 | 60 | ```console 61 | $ cargo run -- -Adead_code -C debug-assertions=false gcd_fixed.rs && echo 'safe' 62 | Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s 63 | Running `target/debug/thrust-rustc -Adead_code -C debug-assertions=false gcd.rs` 64 | safe 65 | ``` 66 | 67 | Integration test examples are located under `tests/ui/` and can be executed using `cargo test`. You can review these examples to understand what the current Thrust implementation can handle. 68 | 69 | ## Annotation 70 | 71 | Thrust can verify a wide range of programs without explicit annotations, but you can use `#[thrust::requires(expr)]` and `#[thrust::ensures(expr)]` to annotate the precondition and postcondition of a function, aiding in verification or specifying the specification. Here, `expr` is a logical expression that supports basic integer, boolean, and reference operations. 72 | 73 | ```rust 74 | #[thrust::requires(n >= 0)] 75 | #[thrust::ensures((result * 2) == n * (n + 1))] 76 | fn sum(n: i32) -> i32 { 77 | if n == 0 { 78 | 0 79 | } else { 80 | n + sum(n - 1) 81 | } 82 | } 83 | ``` 84 | 85 | You can use the `^` unary operator to denote the value of a mutable reference after the function is called. Note that in the current implementation, you need to specify both `requires` and `ensures` when using either one. 86 | 87 | ```rust 88 | #[thrust::requires(true)] 89 | #[thrust::ensures(^ma == *ma + a)] 90 | fn add(ma: &mut i32, a: i32) { 91 | *ma += a; 92 | } 93 | ``` 94 | 95 | The conditions on `thrust::requires` and `thrust::ensures` are internally encoded as refinement types for the parameter and return types. You can also specify these refinement types directly using `#[thrust::param(param: type)]` and `#[thrust::ret(type)]`. 96 | 97 | ```rust 98 | #[thrust::param(n: { x: int | x >= 0 })] 99 | #[thrust::ret({ x: int | (x * 2) == n * (n + 1) })] 100 | fn sum(n: i32) -> i32 { 101 | if n == 0 { 102 | 0 103 | } else { 104 | n + sum(n - 1) 105 | } 106 | } 107 | ``` 108 | 109 | The bodies of functions marked with `#[thrust::trusted]` are not analyzed by Thrust. Additionally, `#[thrust::callable]` is an alias for `#[thrust::requires(true)]` and `#[thrust::ensures(true)]`. 110 | 111 | ```rust 112 | #[thrust::trusted] 113 | #[thrust::callable] 114 | fn rand() -> i32 { unimplemented!() } 115 | ``` 116 | 117 | ## Configuration 118 | 119 | Several environment variables are used by Thrust to configure its behavior: 120 | 121 | - `THRUST_SOLVER`: A CHC solver command used to solve CHC constraints generated by Thrust. Default: `z3` 122 | - `THRUST_SOLVER_ARGS`: Whitespace-separated command-line flags passed to the solver. The default is `fp.spacer.global=true fp.validate=true` when the solver is `z3`. 123 | - `THRUST_SOLVER_TIMEOUT_SECS`: Timeout for waiting on results from the solver. Default: `30` 124 | - `THRUST_OUTPUT_DIR`: When configured, Thrust outputs intermediate smtlib2 files into this directory. 125 | - `THRUST_ENUM_EXPANSION_DEPTH_LIMIT`: When Thrust works with enums, it "expands" the structure of the enum value onto its environment. This configuration value sets the limit on the depth of recursion during this expansion to handle enums that are defined recursively. It is our future work to discover a sensible value for this automatically. Default: `2` 126 | 127 | ## Development 128 | 129 | The implementation of the Thrust is largely divided into the following modules. 130 | 131 | - `analyze`: MIR analysis. Further divided into the modules corresponding to the program units: `analyze::crate_`, `analyze::local_def`, and `analyze::basic_block`. 132 | - `refine`: Typing environment and related implementations. 133 | - `rty`: Refinement type primitives. 134 | - `chc`: CHC and logic primitives, and it also implements an invocation of the underlying CHC solver. 135 | - `annot`: Refinement type annotation parser. 136 | 137 | The implementation generates subtyping constraints in the form of CHCs (`chc::System`). The entry point is `analyze::crate_::Analyzer::run`, followed by `analyze::local_def::Analyzer::run` and `analyze::basic_block::Analyzer::run`, while accumulating the necessary information in `analyze::Analyzer`. Once `chc::System` is collected for the entire input, it invokes an external CHC solver via the `chc::solver` module and subsequently reports the result. 138 | 139 | ## Publication 140 | 141 | Hiromi Ogawa, Taro Sekiyama, and Hiroshi Unno. Thrust: A Prophecy-based Refinement Type System for Rust. PLDI 2025. 142 | 143 | ## License 144 | 145 | Licensed under either of 146 | 147 | * Apache License, Version 2.0 148 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 149 | * MIT license 150 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 151 | 152 | at your option. 153 | 154 | ## Contribution 155 | 156 | Unless you explicitly state otherwise, any contribution intentionally submitted 157 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 158 | dual licensed as above, without any additional terms or conditions. 159 | -------------------------------------------------------------------------------- /src/chc/solver.rs: -------------------------------------------------------------------------------- 1 | //! A generic interface for running external command-line CHC solvers. 2 | //! 3 | //! This module provides the [`Config`] struct for configuring and running an external 4 | //! CHC solver. It supports setting the solver command, arguments, and timeout, and can 5 | //! be configured through environment variables. 6 | 7 | /// An error that can occur when solving a [`crate::chc::System`]. 8 | #[derive(Debug, thiserror::Error)] 9 | pub enum CheckSatError { 10 | #[error("unsat")] 11 | Unsat, 12 | #[error("solver error: stdout: {stdout} stderr: {stderr}")] 13 | Error { stdout: String, stderr: String }, 14 | #[error("unknown output: {stdout}")] 15 | Unknown { stdout: String }, 16 | #[error("timed out after {0:?}")] 17 | Timeout(std::time::Duration), 18 | #[error("io error")] 19 | Io(#[from] std::io::Error), 20 | } 21 | 22 | /// A configuration for running a command-line CHC solver. 23 | #[derive(Debug, Clone)] 24 | pub struct CommandConfig { 25 | pub name: String, 26 | pub args: Vec, 27 | pub timeout: Option, 28 | } 29 | 30 | impl CommandConfig { 31 | fn load_args(&mut self, env: &str) { 32 | if let Ok(args) = std::env::var(env) { 33 | self.args = args.split_whitespace().map(|s| s.to_owned()).collect(); 34 | } 35 | } 36 | 37 | fn load_timeout(&mut self, env: &str) { 38 | if let Ok(timeout) = std::env::var(env) { 39 | let timeout_secs = timeout.parse().unwrap(); 40 | if timeout_secs == 0 { 41 | self.timeout = None; 42 | } else { 43 | self.timeout = Some(std::time::Duration::from_secs(timeout_secs)); 44 | } 45 | } 46 | } 47 | 48 | fn wait_child( 49 | &self, 50 | child: std::process::Child, 51 | ) -> Result<(std::process::Output, std::time::Duration), CheckSatError> { 52 | use process_control::{ChildExt as _, Control as _}; 53 | 54 | let start = std::time::Instant::now(); 55 | tracing::info!(timeout = ?self.timeout, pid = child.id(), "waiting"); 56 | let mut child = child.controlled_with_output().terminate_for_timeout(); 57 | if let Some(timeout) = self.timeout { 58 | child = child.time_limit(timeout); 59 | } 60 | let output = match child.wait()? { 61 | None => return Err(CheckSatError::Timeout(self.timeout.unwrap())), 62 | Some(output) => output, 63 | }; 64 | let elapsed = std::time::Instant::now() - start; 65 | Ok((output.into_std_lossy(), elapsed)) 66 | } 67 | 68 | fn run( 69 | &self, 70 | path_arg: impl AsRef, 71 | stdout: impl Into, 72 | ) -> Result { 73 | let path_arg = path_arg.as_ref(); 74 | let child = std::process::Command::new(&self.name) 75 | .args(&self.args) 76 | .arg(path_arg) 77 | .stdout(stdout) 78 | .spawn()?; 79 | tracing::info!(program = self.name, args = ?self.args, path = %path_arg.to_string_lossy(), pid = child.id(), "spawned"); 80 | 81 | let (output, elapsed) = self.wait_child(child)?; 82 | let stdout = String::from_utf8_lossy(&output.stdout); 83 | let stderr = String::from_utf8_lossy(&output.stderr); 84 | tracing::info!(status = %output.status, ?elapsed, "exited"); 85 | if !output.status.success() { 86 | return Err(CheckSatError::Error { 87 | stdout: stdout.into_owned(), 88 | stderr: stderr.into_owned(), 89 | }); 90 | } 91 | Ok(stdout.into_owned()) 92 | } 93 | } 94 | 95 | /// A configuration for solving a [`crate::chc::System`]. 96 | /// 97 | /// This struct holds the configuration for the solver, including the solver command, its 98 | /// arguments, and a timeout. It can also be configured to run a preprocessor on the SMT-LIB2 99 | /// file before passing it to the solver. 100 | /// 101 | /// The configuration can be loaded from environment variables using [`Config::from_env`]. 102 | #[derive(Debug, Clone)] 103 | pub struct Config { 104 | pub solver: CommandConfig, 105 | pub preprocessor: Option, 106 | pub output_dir: Option, 107 | } 108 | 109 | impl Default for Config { 110 | fn default() -> Config { 111 | Config { 112 | solver: CommandConfig { 113 | name: "z3".to_owned(), 114 | args: vec![ 115 | "fp.spacer.global=true".to_owned(), 116 | "fp.validate=true".to_owned(), 117 | ], 118 | timeout: Some(std::time::Duration::from_secs(30)), 119 | }, 120 | preprocessor: None, 121 | output_dir: None, 122 | } 123 | } 124 | } 125 | 126 | impl Config { 127 | pub fn from_env() -> Config { 128 | let mut config = Config::default(); 129 | if let Ok(solver) = std::env::var("THRUST_SOLVER") { 130 | config.solver.name = solver; 131 | } 132 | if config.solver.name != "z3" { 133 | config.solver.args.clear(); 134 | } 135 | config.solver.load_args("THRUST_SOLVER_ARGS"); 136 | config.solver.load_timeout("THRUST_SOLVER_TIMEOUT_SECS"); 137 | if let Ok(preproc) = std::env::var("THRUST_PREPROCESSOR") { 138 | let mut preproc_config = CommandConfig { 139 | name: preproc, 140 | args: vec![], 141 | timeout: Some(std::time::Duration::from_secs(30)), 142 | }; 143 | preproc_config.load_args("THRUST_PREPROCESSOR_ARGS"); 144 | preproc_config.load_timeout("THRUST_PREPROCESSOR_TIMEOUT_SECS"); 145 | config.preprocessor = Some(preproc_config); 146 | } 147 | if let Ok(dir) = std::env::var("THRUST_OUTPUT_DIR") { 148 | config.output_dir = Some(dir.into()); 149 | } 150 | config 151 | } 152 | 153 | pub fn check_sat(&self, problem: impl std::fmt::Display) -> Result<(), CheckSatError> { 154 | use std::io::{Seek as _, Write as _}; 155 | let smt2 = format!("{}\n(check-sat)\n", problem); 156 | let mut file = tempfile::Builder::new() 157 | .prefix("thrust_tmp_") 158 | .suffix(".smt2") 159 | .tempfile()?; 160 | write!(file, "{}", smt2)?; 161 | file.flush()?; 162 | if let Some(dir) = &self.output_dir { 163 | std::fs::copy(&file, dir.join("thrust_output.smt2"))?; 164 | } 165 | 166 | if let Some(preproc) = &self.preprocessor { 167 | let output = preproc.run(file.path(), std::process::Stdio::piped())?; 168 | file.as_file_mut().set_len(0)?; 169 | file.rewind()?; 170 | write!(file, "{}", output)?; 171 | if let Some(dir) = &self.output_dir { 172 | std::fs::copy(&file, dir.join("preproc_output.smt2"))?; 173 | } 174 | } 175 | 176 | let output = self.solver.run(file.path(), std::process::Stdio::piped())?; 177 | drop(file); 178 | match output.trim() { 179 | "sat" => Ok(()), 180 | "unsat" => Err(CheckSatError::Unsat), 181 | _ => Err(CheckSatError::Unknown { stdout: output }), 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/analyze/crate_.rs: -------------------------------------------------------------------------------- 1 | //! Analyze a local crate. 2 | 3 | use std::collections::HashSet; 4 | 5 | use rustc_hir::def::DefKind; 6 | use rustc_index::IndexVec; 7 | use rustc_middle::ty::{self as mir_ty, TyCtxt}; 8 | use rustc_span::def_id::{DefId, LocalDefId}; 9 | 10 | use crate::analyze; 11 | use crate::chc; 12 | use crate::refine::{self, TypeBuilder}; 13 | use crate::rty::{self, ClauseBuilderExt as _}; 14 | 15 | /// An implementation of local crate analysis. 16 | /// 17 | /// The entry point is [`Analyzer::run`], which performs the following steps in order: 18 | /// 19 | /// 1. Register enum definitions found in the crate. 20 | /// 2. Give initial refinement types to local function definitions based on their signatures and 21 | /// annotations. This generates template refinement types with predicate variables for parameters and 22 | /// return types that are not known via annotations. 23 | /// 3. Type local function definition bodies via [`super::local_def::Analyzer`] using the refinement types 24 | /// generated in the previous step. 25 | pub struct Analyzer<'tcx, 'ctx> { 26 | tcx: TyCtxt<'tcx>, 27 | ctx: &'ctx mut analyze::Analyzer<'tcx>, 28 | trusted: HashSet, 29 | } 30 | 31 | impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { 32 | fn refine_local_defs(&mut self) { 33 | for local_def_id in self.tcx.mir_keys(()) { 34 | if self.tcx.def_kind(*local_def_id).is_fn_like() { 35 | self.refine_fn_def(*local_def_id); 36 | } 37 | } 38 | } 39 | 40 | #[tracing::instrument(skip(self), fields(def_id = %self.tcx.def_path_str(local_def_id)))] 41 | fn refine_fn_def(&mut self, local_def_id: LocalDefId) { 42 | let mut analyzer = self.ctx.local_def_analyzer(local_def_id); 43 | 44 | if analyzer.is_annotated_as_trusted() { 45 | assert!(analyzer.is_fully_annotated()); 46 | self.trusted.insert(local_def_id.to_def_id()); 47 | } 48 | 49 | let sig = self 50 | .tcx 51 | .fn_sig(local_def_id) 52 | .instantiate_identity() 53 | .skip_binder(); 54 | use mir_ty::TypeVisitableExt as _; 55 | if sig.has_param() && !analyzer.is_fully_annotated() { 56 | self.ctx.register_deferred_def(local_def_id.to_def_id()); 57 | } else { 58 | let expected = analyzer.expected_ty(); 59 | self.ctx.register_def(local_def_id.to_def_id(), expected); 60 | } 61 | } 62 | 63 | fn analyze_local_defs(&mut self) { 64 | for local_def_id in self.tcx.mir_keys(()) { 65 | if !self.tcx.def_kind(*local_def_id).is_fn_like() { 66 | continue; 67 | }; 68 | if self.trusted.contains(&local_def_id.to_def_id()) { 69 | tracing::info!(?local_def_id, "trusted"); 70 | continue; 71 | } 72 | let Some(expected) = self.ctx.concrete_def_ty(local_def_id.to_def_id()) else { 73 | // when the local_def_id is deferred it would be skipped 74 | continue; 75 | }; 76 | 77 | // check polymorphic function def by replacing type params with some opaque type 78 | // (and this is no-op if the function is mono) 79 | let mut expected = expected.clone(); 80 | let subst = rty::TypeParamSubst::new( 81 | expected 82 | .free_ty_params() 83 | .into_iter() 84 | .map(|ty_param| (ty_param, rty::RefinedType::unrefined(rty::Type::int()))) 85 | .collect(), 86 | ); 87 | expected.subst_ty_params(&subst); 88 | let generic_args = self.placeholder_generic_args(*local_def_id); 89 | self.ctx 90 | .local_def_analyzer(*local_def_id) 91 | .generic_args(generic_args) 92 | .run(&expected); 93 | } 94 | } 95 | 96 | fn placeholder_generic_args(&self, local_def_id: LocalDefId) -> mir_ty::GenericArgsRef<'tcx> { 97 | let mut constrained_params = HashSet::new(); 98 | let predicates = self.tcx.predicates_of(local_def_id); 99 | let sized_trait = self.tcx.lang_items().sized_trait().unwrap(); 100 | for (clause, _) in predicates.predicates { 101 | let mir_ty::ClauseKind::Trait(pred) = clause.kind().skip_binder() else { 102 | continue; 103 | }; 104 | if pred.def_id() == sized_trait { 105 | continue; 106 | }; 107 | for arg in pred.trait_ref.args.iter().flat_map(|ty| ty.walk()) { 108 | let Some(ty) = arg.as_type() else { 109 | continue; 110 | }; 111 | let mir_ty::TyKind::Param(param_ty) = ty.kind() else { 112 | continue; 113 | }; 114 | constrained_params.insert(param_ty.index); 115 | } 116 | } 117 | 118 | let mut args: Vec> = Vec::new(); 119 | 120 | let generics = self.tcx.generics_of(local_def_id); 121 | for idx in 0..generics.count() { 122 | let param = generics.param_at(idx, self.tcx); 123 | let arg = match param.kind { 124 | mir_ty::GenericParamDefKind::Type { .. } => { 125 | if constrained_params.contains(¶m.index) { 126 | panic!( 127 | "unable to check generic function with constrained type parameter: {}", 128 | self.tcx.def_path_str(local_def_id) 129 | ); 130 | } 131 | self.tcx.types.i32.into() 132 | } 133 | mir_ty::GenericParamDefKind::Const { .. } => { 134 | unimplemented!() 135 | } 136 | mir_ty::GenericParamDefKind::Lifetime { .. } => self.tcx.lifetimes.re_erased.into(), 137 | }; 138 | args.push(arg); 139 | } 140 | 141 | self.tcx.mk_args(&args) 142 | } 143 | 144 | fn assert_callable_entry(&mut self) { 145 | if let Some((def_id, _)) = self.tcx.entry_fn(()) { 146 | // we want to assert entry function is safe to execute without any assumption 147 | // TODO: replace code here with relate_* in Env + Refine context (created with empty env) 148 | let entry_ty = self 149 | .ctx 150 | .concrete_def_ty(def_id) 151 | .unwrap() 152 | .ty 153 | .as_function() 154 | .unwrap() 155 | .clone(); 156 | let mut builder = chc::ClauseBuilder::default(); 157 | for (param_idx, param_ty) in entry_ty.params.iter_enumerated() { 158 | let param_sort = param_ty.ty.to_sort(); 159 | if !param_sort.is_singleton() { 160 | builder.add_mapped_var(param_idx, param_sort); 161 | } 162 | } 163 | builder.add_body(chc::Atom::top()); 164 | for param_ty in entry_ty.params { 165 | let cs = builder 166 | .clone() 167 | .with_value_var(¶m_ty.ty) 168 | .head(param_ty.refinement); 169 | self.ctx.extend_clauses(cs); 170 | } 171 | } 172 | } 173 | 174 | fn register_enum_defs(&mut self) { 175 | for local_def_id in self.tcx.iter_local_def_id() { 176 | let DefKind::Enum = self.tcx.def_kind(local_def_id) else { 177 | continue; 178 | }; 179 | let adt = self.tcx.adt_def(local_def_id); 180 | 181 | let name = refine::datatype_symbol(self.tcx, local_def_id.to_def_id()); 182 | let variants: IndexVec<_, _> = adt 183 | .variants() 184 | .iter() 185 | .map(|variant| { 186 | let name = refine::datatype_symbol(self.tcx, variant.def_id); 187 | // TODO: consider using TyCtxt::tag_for_variant 188 | let discr = analyze::resolve_discr(self.tcx, variant.discr); 189 | let field_tys = variant 190 | .fields 191 | .iter() 192 | .map(|field| { 193 | let field_ty = self.tcx.type_of(field.did).instantiate_identity(); 194 | TypeBuilder::new(self.tcx, local_def_id.to_def_id()).build(field_ty) 195 | }) 196 | .collect(); 197 | rty::EnumVariantDef { 198 | name, 199 | discr, 200 | field_tys, 201 | } 202 | }) 203 | .collect(); 204 | 205 | let generics = self.tcx.generics_of(local_def_id); 206 | let ty_params = (0..generics.count()) 207 | .filter(|idx| { 208 | matches!( 209 | generics.param_at(*idx, self.tcx).kind, 210 | mir_ty::GenericParamDefKind::Type { .. } 211 | ) 212 | }) 213 | .count(); 214 | tracing::debug!(?local_def_id, ?name, ?ty_params, "ty_params count"); 215 | 216 | let def = rty::EnumDatatypeDef { 217 | name, 218 | ty_params, 219 | variants, 220 | }; 221 | self.ctx.register_enum_def(local_def_id.to_def_id(), def); 222 | } 223 | } 224 | } 225 | 226 | impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { 227 | pub fn new(ctx: &'ctx mut analyze::Analyzer<'tcx>) -> Self { 228 | let tcx = ctx.tcx; 229 | let trusted = HashSet::default(); 230 | Self { ctx, tcx, trusted } 231 | } 232 | 233 | pub fn run(&mut self) { 234 | let span = tracing::debug_span!("crate", krate = %self.tcx.crate_name(rustc_span::def_id::LOCAL_CRATE)); 235 | let _guard = span.enter(); 236 | 237 | self.register_enum_defs(); 238 | self.refine_local_defs(); 239 | self.analyze_local_defs(); 240 | self.assert_callable_entry(); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/analyze.rs: -------------------------------------------------------------------------------- 1 | //! Analysis of Rust MIR to generate a CHC system. 2 | //! 3 | //! The [`Analyzer`] generates subtyping constraints in the form of CHCs ([`chc::System`]). 4 | //! The entry point is [`crate_::Analyzer::run`], followed by [`local_def::Analyzer::run`] 5 | //! and [`basic_block::Analyzer::run`], while accumulating the necessary information in 6 | //! [`Analyzer`]. Once [`chc::System`] is collected for the entire input, it invokes an external 7 | //! CHC solver with the [`Analyzer::solve`] and subsequently reports the result. 8 | 9 | use std::cell::RefCell; 10 | use std::collections::HashMap; 11 | use std::rc::Rc; 12 | 13 | use rustc_hir::lang_items::LangItem; 14 | use rustc_middle::mir::{self, BasicBlock, Local}; 15 | use rustc_middle::ty::{self as mir_ty, TyCtxt}; 16 | use rustc_span::def_id::{DefId, LocalDefId}; 17 | 18 | use crate::chc; 19 | use crate::pretty::PrettyDisplayExt as _; 20 | use crate::refine::{self, BasicBlockType, TypeBuilder}; 21 | use crate::rty; 22 | 23 | mod annot; 24 | mod basic_block; 25 | mod crate_; 26 | mod did_cache; 27 | mod local_def; 28 | 29 | pub fn local_of_function_param(idx: rty::FunctionParamIdx) -> Local { 30 | Local::from(idx.index() + 1) 31 | } 32 | 33 | pub fn resolve_discr(tcx: TyCtxt<'_>, discr: mir_ty::VariantDiscr) -> u32 { 34 | match discr { 35 | mir_ty::VariantDiscr::Relative(i) => i, 36 | mir_ty::VariantDiscr::Explicit(did) => { 37 | let val = tcx.const_eval_poly(did).unwrap(); 38 | val.try_to_scalar_int().unwrap().try_to_u32().unwrap() 39 | } 40 | } 41 | } 42 | 43 | pub struct ReplacePlacesVisitor<'tcx> { 44 | replacements: HashMap<(Local, &'tcx [mir::PlaceElem<'tcx>]), mir::Place<'tcx>>, 45 | tcx: TyCtxt<'tcx>, 46 | } 47 | 48 | impl<'tcx> mir::visit::MutVisitor<'tcx> for ReplacePlacesVisitor<'tcx> { 49 | fn tcx(&self) -> TyCtxt<'tcx> { 50 | self.tcx 51 | } 52 | 53 | fn visit_place( 54 | &mut self, 55 | place: &mut mir::Place<'tcx>, 56 | _: mir::visit::PlaceContext, 57 | _: mir::Location, 58 | ) { 59 | let proj = place.projection.as_slice(); 60 | for i in 0..=proj.len() { 61 | if let Some(to) = self.replacements.get(&(place.local, &proj[0..i])) { 62 | place.local = to.local; 63 | place.projection = self.tcx.mk_place_elems_from_iter( 64 | to.projection.iter().chain(proj.iter().skip(i).cloned()), 65 | ); 66 | return; 67 | } 68 | } 69 | } 70 | } 71 | 72 | impl<'tcx> ReplacePlacesVisitor<'tcx> { 73 | pub fn new(tcx: TyCtxt<'tcx>) -> Self { 74 | Self { 75 | tcx, 76 | replacements: Default::default(), 77 | } 78 | } 79 | 80 | pub fn with_replacement( 81 | tcx: TyCtxt<'tcx>, 82 | from: mir::Place<'tcx>, 83 | to: mir::Place<'tcx>, 84 | ) -> Self { 85 | let mut visitor = Self::new(tcx); 86 | visitor.add_replacement(from, to); 87 | visitor 88 | } 89 | 90 | pub fn add_replacement(&mut self, from: mir::Place<'tcx>, to: mir::Place<'tcx>) { 91 | self.replacements 92 | .insert((from.local, from.projection.as_slice()), to); 93 | } 94 | 95 | pub fn visit_statement(&mut self, stmt: &mut mir::Statement<'tcx>) { 96 | // dummy location 97 | mir::visit::MutVisitor::visit_statement(self, stmt, mir::Location::START); 98 | } 99 | 100 | pub fn visit_terminator(&mut self, term: &mut mir::Terminator<'tcx>) { 101 | // dummy location 102 | mir::visit::MutVisitor::visit_terminator(self, term, mir::Location::START); 103 | } 104 | } 105 | 106 | #[derive(Debug, Clone)] 107 | struct DeferredDefTy<'tcx> { 108 | cache: Rc, rty::RefinedType>>>, 109 | } 110 | 111 | #[derive(Debug, Clone)] 112 | enum DefTy<'tcx> { 113 | Concrete(rty::RefinedType), 114 | Deferred(DeferredDefTy<'tcx>), 115 | } 116 | 117 | #[derive(Clone)] 118 | pub struct Analyzer<'tcx> { 119 | tcx: TyCtxt<'tcx>, 120 | 121 | /// Collection of refined known def types. 122 | /// 123 | /// currently contains only local-def templates, 124 | /// but will be extended to contain externally known def's refinement types 125 | /// (at least for every defs referenced by local def bodies) 126 | defs: HashMap>, 127 | 128 | /// Resulting CHC system. 129 | system: Rc>, 130 | 131 | basic_blocks: HashMap>, 132 | def_ids: did_cache::DefIdCache<'tcx>, 133 | 134 | enum_defs: Rc>>, 135 | } 136 | 137 | impl<'tcx> crate::refine::TemplateRegistry for Analyzer<'tcx> { 138 | fn register_template(&mut self, tmpl: rty::Template) -> rty::RefinedType { 139 | tmpl.into_refined_type(|pred_sig| self.generate_pred_var(pred_sig)) 140 | } 141 | } 142 | 143 | impl<'tcx> Analyzer<'tcx> { 144 | pub fn generate_pred_var(&mut self, sig: chc::PredSig) -> chc::PredVarId { 145 | self.system 146 | .borrow_mut() 147 | .new_pred_var(sig, chc::DebugInfo::from_current_span()) 148 | } 149 | } 150 | 151 | impl<'tcx> Analyzer<'tcx> { 152 | pub fn new(tcx: TyCtxt<'tcx>) -> Self { 153 | let defs = Default::default(); 154 | let system = Default::default(); 155 | let basic_blocks = Default::default(); 156 | let enum_defs = Default::default(); 157 | Self { 158 | tcx, 159 | defs, 160 | system, 161 | basic_blocks, 162 | def_ids: did_cache::DefIdCache::new(tcx), 163 | enum_defs, 164 | } 165 | } 166 | 167 | pub fn add_clause(&mut self, clause: chc::Clause) { 168 | self.system.borrow_mut().push_clause(clause); 169 | } 170 | 171 | pub fn extend_clauses(&mut self, clauses: impl IntoIterator) { 172 | for clause in clauses { 173 | self.add_clause(clause); 174 | } 175 | } 176 | 177 | pub fn register_enum_def(&mut self, def_id: DefId, enum_def: rty::EnumDatatypeDef) { 178 | tracing::debug!(def_id = ?def_id, enum_def = ?enum_def, "register_enum_def"); 179 | let ctors = enum_def 180 | .variants 181 | .iter() 182 | .map(|v| chc::DatatypeCtor { 183 | symbol: v.name.clone(), 184 | selectors: v 185 | .field_tys 186 | .clone() 187 | .into_iter() 188 | .enumerate() 189 | .map(|(idx, ty)| chc::DatatypeSelector { 190 | symbol: chc::DatatypeSymbol::new(format!("_get{}.{}", v.name, idx)), 191 | sort: ty.to_sort(), 192 | }) 193 | .collect(), 194 | discriminant: v.discr, 195 | }) 196 | .collect(); 197 | let datatype = chc::Datatype { 198 | symbol: enum_def.name.clone(), 199 | params: enum_def.ty_params, 200 | ctors, 201 | }; 202 | self.enum_defs.borrow_mut().insert(def_id, enum_def); 203 | self.system.borrow_mut().datatypes.push(datatype); 204 | } 205 | 206 | pub fn find_enum_variant( 207 | &self, 208 | ty_sym: &chc::DatatypeSymbol, 209 | v_sym: &chc::DatatypeSymbol, 210 | ) -> Option { 211 | self.enum_defs 212 | .borrow() 213 | .iter() 214 | .find(|(_, d)| &d.name == ty_sym) 215 | .and_then(|(_, d)| d.variants.iter().find(|v| &v.name == v_sym)) 216 | .cloned() 217 | } 218 | 219 | pub fn register_def(&mut self, def_id: DefId, rty: rty::RefinedType) { 220 | tracing::info!(def_id = ?def_id, rty = %rty.display(), "register_def"); 221 | self.defs.insert(def_id, DefTy::Concrete(rty)); 222 | } 223 | 224 | pub fn register_deferred_def(&mut self, def_id: DefId) { 225 | tracing::info!(def_id = ?def_id, "register_deferred_def"); 226 | self.defs.insert( 227 | def_id, 228 | DefTy::Deferred(DeferredDefTy { 229 | cache: Rc::new(RefCell::new(HashMap::new())), 230 | }), 231 | ); 232 | } 233 | 234 | pub fn concrete_def_ty(&self, def_id: DefId) -> Option<&rty::RefinedType> { 235 | self.defs.get(&def_id).and_then(|def_ty| match def_ty { 236 | DefTy::Concrete(rty) => Some(rty), 237 | DefTy::Deferred(_) => None, 238 | }) 239 | } 240 | 241 | pub fn def_ty_with_args( 242 | &mut self, 243 | def_id: DefId, 244 | generic_args: mir_ty::GenericArgsRef<'tcx>, 245 | ) -> Option { 246 | let deferred_ty = match self.defs.get(&def_id)? { 247 | DefTy::Concrete(rty) => { 248 | let type_builder = TypeBuilder::new(self.tcx, def_id); 249 | 250 | let mut def_ty = rty.clone(); 251 | def_ty.instantiate_ty_params( 252 | generic_args 253 | .types() 254 | .map(|ty| type_builder.build(ty)) 255 | .map(rty::RefinedType::unrefined) 256 | .collect(), 257 | ); 258 | return Some(def_ty); 259 | } 260 | DefTy::Deferred(deferred) => deferred, 261 | }; 262 | 263 | let deferred_ty_cache = Rc::clone(&deferred_ty.cache); // to cut reference to allow &mut self 264 | if let Some(rty) = deferred_ty_cache.borrow().get(&generic_args) { 265 | return Some(rty.clone()); 266 | } 267 | 268 | let mut analyzer = self.local_def_analyzer(def_id.as_local()?); 269 | analyzer.generic_args(generic_args); 270 | 271 | let expected = analyzer.expected_ty(); 272 | deferred_ty_cache 273 | .borrow_mut() 274 | .insert(generic_args, expected.clone()); 275 | 276 | analyzer.run(&expected); 277 | Some(expected) 278 | } 279 | 280 | pub fn register_basic_block_ty( 281 | &mut self, 282 | def_id: LocalDefId, 283 | bb: BasicBlock, 284 | rty: BasicBlockType, 285 | ) { 286 | tracing::debug!(def_id = ?def_id, ?bb, rty = %rty.display(), "register_basic_block_ty"); 287 | self.basic_blocks.entry(def_id).or_default().insert(bb, rty); 288 | } 289 | 290 | pub fn basic_block_ty(&self, def_id: LocalDefId, bb: BasicBlock) -> &BasicBlockType { 291 | &self.basic_blocks[&def_id][&bb] 292 | } 293 | 294 | pub fn register_well_known_defs(&mut self) { 295 | let panic_ty = { 296 | let param = rty::RefinedType::new( 297 | rty::PointerType::immut_to(rty::Type::string()).into(), 298 | rty::Refinement::bottom(), 299 | ); 300 | let ret = rty::RefinedType::new(rty::Type::never(), rty::Refinement::bottom()); 301 | rty::FunctionType::new([param.vacuous()].into_iter().collect(), ret) 302 | }; 303 | let panic_def_id = self.tcx.require_lang_item(LangItem::Panic, None); 304 | self.register_def(panic_def_id, rty::RefinedType::unrefined(panic_ty.into())); 305 | } 306 | 307 | pub fn new_env(&self) -> refine::Env { 308 | let defs = self 309 | .enum_defs 310 | .borrow() 311 | .values() 312 | .map(|def| (def.name.clone(), def.clone())) 313 | .collect(); 314 | refine::Env::new(defs) 315 | } 316 | 317 | pub fn crate_analyzer(&mut self) -> crate_::Analyzer<'tcx, '_> { 318 | crate_::Analyzer::new(self) 319 | } 320 | 321 | pub fn local_def_analyzer( 322 | &mut self, 323 | local_def_id: LocalDefId, 324 | ) -> local_def::Analyzer<'tcx, '_> { 325 | local_def::Analyzer::new(self, local_def_id) 326 | } 327 | 328 | pub fn basic_block_analyzer( 329 | &mut self, 330 | local_def_id: LocalDefId, 331 | bb: BasicBlock, 332 | ) -> basic_block::Analyzer<'tcx, '_> { 333 | basic_block::Analyzer::new(self, local_def_id, bb) 334 | } 335 | 336 | pub fn solve(&mut self) { 337 | if let Err(err) = self.system.borrow().solve() { 338 | self.tcx.dcx().err(format!("verification error: {:?}", err)); 339 | } 340 | } 341 | 342 | /// Computes the signature of the local function. 343 | /// 344 | /// This is a drop-in replacement of `self.tcx.fn_sig(local_def_id).instantiate_identity().skip_binder()`, 345 | /// but extracts parameter and return types directly from the given `body` to obtain a signature that 346 | /// reflects potential type instantiations happened after `optimized_mir`. 347 | pub fn local_fn_sig_with_body( 348 | &self, 349 | local_def_id: LocalDefId, 350 | body: &mir::Body<'tcx>, 351 | ) -> mir_ty::FnSig<'tcx> { 352 | let ty = self.tcx.type_of(local_def_id).instantiate_identity(); 353 | let sig = if let mir_ty::TyKind::Closure(_, substs) = ty.kind() { 354 | substs.as_closure().sig().skip_binder() 355 | } else { 356 | ty.fn_sig(self.tcx).skip_binder() 357 | }; 358 | 359 | self.tcx.mk_fn_sig( 360 | body.args_iter().map(|arg| body.local_decls[arg].ty), 361 | body.return_ty(), 362 | sig.c_variadic, 363 | sig.unsafety, 364 | sig.abi, 365 | ) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/chc/format_context.rs: -------------------------------------------------------------------------------- 1 | //! A context for formatting a CHC system into SMT-LIB2. 2 | //! 3 | //! This module provides [`FormatContext`], which is responsible for translating parts of [`chc::System`] 4 | //! into a representation that is compatible with SMT solvers. It handles tasks like 5 | //! monomorphization of polymorphic datatypes and applying solver-specific workarounds. 6 | //! The [`super::smtlib2`] module uses this context to perform the final rendering to the SMT-LIB2 format. 7 | 8 | use std::collections::BTreeSet; 9 | 10 | use crate::chc::{self, hoice::HoiceDatatypeRenamer}; 11 | 12 | /// A context for formatting a CHC system. 13 | /// 14 | /// This subsumes a representational difference between [`chc::System`] and resulting SMT-LIB2. 15 | /// - Gives a naming convention of symbols to represent built-in datatypes of [`chc::System`] in SMT-LIB2, 16 | /// - Gives a stringified representation of [`chc::Sort`]s, 17 | /// - Monomorphizes polymorphic datatypes of [`chc::System`] to be compatible with several CHC solvers, 18 | /// - Renames datatypes to be compatible with Hoice (see [`HoiceDatatypeRenamer`]), 19 | /// - etc. 20 | #[derive(Debug, Clone)] 21 | pub struct FormatContext { 22 | renamer: HoiceDatatypeRenamer, 23 | datatypes: Vec, 24 | } 25 | 26 | // FIXME: this is obviously ineffective and should be replaced 27 | fn term_sorts(clause: &chc::Clause, t: &chc::Term, sorts: &mut BTreeSet) { 28 | sorts.insert(clause.term_sort(t)); 29 | match t { 30 | chc::Term::Null => {} 31 | chc::Term::Var(_) => {} 32 | chc::Term::Bool(_) => {} 33 | chc::Term::Int(_) => {} 34 | chc::Term::String(_) => {} 35 | chc::Term::Box(t) => term_sorts(clause, t, sorts), 36 | chc::Term::Mut(t1, t2) => { 37 | term_sorts(clause, t1, sorts); 38 | term_sorts(clause, t2, sorts); 39 | } 40 | chc::Term::BoxCurrent(t) => term_sorts(clause, t, sorts), 41 | chc::Term::MutCurrent(t) => term_sorts(clause, t, sorts), 42 | chc::Term::MutFinal(t) => term_sorts(clause, t, sorts), 43 | chc::Term::App(_fun, args) => { 44 | for arg in args { 45 | term_sorts(clause, arg, sorts); 46 | } 47 | } 48 | chc::Term::Tuple(ts) => { 49 | for t in ts { 50 | term_sorts(clause, t, sorts); 51 | } 52 | } 53 | chc::Term::TupleProj(t, _) => term_sorts(clause, t, sorts), 54 | chc::Term::DatatypeCtor(_, _, args) => { 55 | for arg in args { 56 | term_sorts(clause, arg, sorts); 57 | } 58 | } 59 | chc::Term::DatatypeDiscr(_, t) => term_sorts(clause, t, sorts), 60 | chc::Term::FormulaExistentialVar(_, _) => {} 61 | } 62 | } 63 | 64 | fn atom_sorts(clause: &chc::Clause, a: &chc::Atom, sorts: &mut BTreeSet) { 65 | for a in &a.args { 66 | term_sorts(clause, a, sorts); 67 | } 68 | } 69 | 70 | #[derive(Debug, Clone)] 71 | pub(super) struct Sort<'a> { 72 | inner: &'a chc::Sort, 73 | } 74 | 75 | impl<'a> std::fmt::Display for Sort<'a> { 76 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 77 | match self.inner { 78 | chc::Sort::Null => write!(f, "Null"), 79 | chc::Sort::Int => write!(f, "Int"), 80 | chc::Sort::Bool => write!(f, "Bool"), 81 | chc::Sort::String => write!(f, "String"), 82 | chc::Sort::Param(i) => write!(f, "T{}", i), 83 | chc::Sort::Box(s) => write!(f, "Box{}", Sort::new(s).sorts()), 84 | chc::Sort::Mut(s) => write!(f, "Mut{}", Sort::new(s).sorts()), 85 | chc::Sort::Tuple(ss) => write!(f, "Tuple{}", Sorts::new(ss)), 86 | chc::Sort::Datatype(s) => write!(f, "{}{}", s.symbol, Sorts::new(&s.args)), 87 | } 88 | } 89 | } 90 | 91 | impl<'a> Sort<'a> { 92 | pub fn new(inner: &'a chc::Sort) -> Self { 93 | Self { inner } 94 | } 95 | 96 | pub fn sorts(self) -> impl std::fmt::Display { 97 | format!("<{}>", self) 98 | } 99 | 100 | pub fn to_symbol(&self) -> chc::DatatypeSymbol { 101 | chc::DatatypeSymbol::new(self.to_string()) 102 | } 103 | } 104 | 105 | #[derive(Debug, Clone)] 106 | struct Sorts<'a> { 107 | inner: &'a [chc::Sort], 108 | } 109 | 110 | impl<'a> std::fmt::Display for Sorts<'a> { 111 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 112 | if self.inner.is_empty() { 113 | return Ok(()); 114 | } 115 | write!(f, "<")?; 116 | for (i, s) in self.inner.iter().enumerate() { 117 | if i != 0 { 118 | write!(f, "-")?; 119 | } 120 | write!(f, "{}", Sort::new(s))?; 121 | } 122 | write!(f, ">")?; 123 | Ok(()) 124 | } 125 | } 126 | 127 | impl<'a> Sorts<'a> { 128 | pub fn new(inner: &'a [chc::Sort]) -> Self { 129 | Self { inner } 130 | } 131 | } 132 | 133 | fn builtin_sort_datatype(s: chc::Sort) -> Option { 134 | let symbol = Sort::new(&s).to_symbol(); 135 | let d = match s { 136 | chc::Sort::Null => chc::Datatype { 137 | symbol, 138 | params: 0, 139 | ctors: vec![chc::DatatypeCtor { 140 | symbol: chc::DatatypeSymbol::new("null".to_string()), 141 | selectors: vec![], 142 | discriminant: 0, 143 | }], 144 | }, 145 | chc::Sort::Box(inner) => { 146 | let ss = Sort::new(&inner).sorts(); 147 | chc::Datatype { 148 | symbol, 149 | params: 0, 150 | ctors: vec![chc::DatatypeCtor { 151 | symbol: chc::DatatypeSymbol::new(format!("box{ss}")), 152 | selectors: vec![chc::DatatypeSelector { 153 | symbol: chc::DatatypeSymbol::new(format!("box_current{ss}")), 154 | sort: *inner, 155 | }], 156 | discriminant: 0, 157 | }], 158 | } 159 | } 160 | chc::Sort::Mut(inner) => { 161 | let ss = Sort::new(&inner).sorts(); 162 | chc::Datatype { 163 | symbol, 164 | params: 0, 165 | ctors: vec![chc::DatatypeCtor { 166 | symbol: chc::DatatypeSymbol::new(format!("mut{ss}")), 167 | selectors: vec![ 168 | chc::DatatypeSelector { 169 | symbol: chc::DatatypeSymbol::new(format!("mut_current{ss}")), 170 | sort: *inner.clone(), 171 | }, 172 | chc::DatatypeSelector { 173 | symbol: chc::DatatypeSymbol::new(format!("mut_final{ss}")), 174 | sort: *inner, 175 | }, 176 | ], 177 | discriminant: 0, 178 | }], 179 | } 180 | } 181 | chc::Sort::Tuple(elems) => { 182 | let ss = Sorts::new(&elems); 183 | let selectors = elems 184 | .iter() 185 | .enumerate() 186 | .map(|(i, sort)| chc::DatatypeSelector { 187 | symbol: chc::DatatypeSymbol::new(format!("tuple_proj{ss}.{i}")), 188 | sort: sort.clone(), 189 | }) 190 | .collect(); 191 | chc::Datatype { 192 | symbol, 193 | params: 0, 194 | ctors: vec![chc::DatatypeCtor { 195 | symbol: chc::DatatypeSymbol::new(format!("tuple{ss}")), 196 | selectors, 197 | discriminant: 0, 198 | }], 199 | } 200 | } 201 | _ => return None, 202 | }; 203 | Some(d) 204 | } 205 | 206 | fn collect_sorts(system: &chc::System) -> BTreeSet { 207 | let mut sorts = BTreeSet::new(); 208 | 209 | for def in &system.pred_vars { 210 | sorts.extend(def.sig.clone()); 211 | } 212 | 213 | for clause in &system.clauses { 214 | sorts.extend(clause.vars.clone()); 215 | atom_sorts(clause, &clause.head, &mut sorts); 216 | for a in clause.body.iter_atoms() { 217 | atom_sorts(clause, a, &mut sorts); 218 | } 219 | } 220 | 221 | sorts 222 | } 223 | 224 | fn monomorphize_datatype( 225 | sort: &chc::DatatypeSort, 226 | datatypes: &[chc::Datatype], 227 | ) -> Option { 228 | let datatype = datatypes.iter().find(|d| d.symbol == sort.symbol).unwrap(); 229 | if datatype.params == 0 { 230 | return None; 231 | } 232 | let ss = Sorts::new(&sort.args); 233 | let mono_datatype = chc::Datatype { 234 | symbol: chc::DatatypeSymbol::new(format!("{}{}", datatype.symbol, ss)), 235 | params: 0, 236 | ctors: datatype 237 | .ctors 238 | .iter() 239 | .map(|c| chc::DatatypeCtor { 240 | symbol: chc::DatatypeSymbol::new(format!("{}{}", c.symbol, ss)), 241 | selectors: c 242 | .selectors 243 | .iter() 244 | .map(|s| { 245 | let mut sel_sort = s.sort.clone(); 246 | sel_sort.instantiate_params(&sort.args); 247 | chc::DatatypeSelector { 248 | symbol: chc::DatatypeSymbol::new(format!("{}{}", s.symbol, ss)), 249 | sort: sel_sort, 250 | } 251 | }) 252 | .collect(), 253 | discriminant: c.discriminant, 254 | }) 255 | .collect(), 256 | }; 257 | Some(mono_datatype) 258 | } 259 | 260 | impl FormatContext { 261 | pub fn from_system(system: &chc::System) -> Self { 262 | let sorts = collect_sorts(system); 263 | let mut datatypes = system.datatypes.clone(); 264 | for sort in sorts.iter().flat_map(|s| s.as_datatype()) { 265 | if let Some(mono_datatype) = monomorphize_datatype(sort, &datatypes) { 266 | datatypes.push(mono_datatype); 267 | } 268 | } 269 | let datatypes: Vec<_> = sorts 270 | .into_iter() 271 | .flat_map(builtin_sort_datatype) 272 | .chain(datatypes) 273 | .filter(|d| d.params == 0) 274 | .collect(); 275 | let renamer = HoiceDatatypeRenamer::new(&datatypes); 276 | FormatContext { renamer, datatypes } 277 | } 278 | 279 | pub fn datatypes(&self) -> &[chc::Datatype] { 280 | &self.datatypes 281 | } 282 | 283 | pub fn box_ctor(&self, sort: &chc::Sort) -> impl std::fmt::Display { 284 | let ss = Sort::new(sort).sorts(); 285 | format!("box{ss}") 286 | } 287 | 288 | pub fn box_current(&self, sort: &chc::Sort) -> impl std::fmt::Display { 289 | let ss = Sort::new(sort).sorts(); 290 | format!("box_current{ss}") 291 | } 292 | 293 | pub fn mut_ctor(&self, sort: &chc::Sort) -> impl std::fmt::Display { 294 | let ss = Sort::new(sort).sorts(); 295 | format!("mut{ss}") 296 | } 297 | 298 | pub fn mut_current(&self, sort: &chc::Sort) -> impl std::fmt::Display { 299 | let ss = Sort::new(sort).sorts(); 300 | format!("mut_current{ss}") 301 | } 302 | 303 | pub fn mut_final(&self, sort: &chc::Sort) -> impl std::fmt::Display { 304 | let ss = Sort::new(sort).sorts(); 305 | format!("mut_final{ss}") 306 | } 307 | 308 | pub fn tuple_ctor(&self, sorts: &[chc::Sort]) -> impl std::fmt::Display { 309 | let ss = Sorts::new(sorts); 310 | format!("tuple{ss}") 311 | } 312 | 313 | pub fn tuple_proj(&self, sorts: &[chc::Sort], idx: usize) -> impl std::fmt::Display { 314 | let ss = Sorts::new(sorts); 315 | format!("tuple_proj{ss}.{idx}") 316 | } 317 | 318 | pub fn datatype_ctor( 319 | &self, 320 | sort: &chc::DatatypeSort, 321 | ctor_sym: &chc::DatatypeSymbol, 322 | ) -> impl std::fmt::Display { 323 | let ss = Sorts::new(&sort.args); 324 | format!("{}{}", ctor_sym, ss) 325 | } 326 | 327 | pub fn datatype_discr(&self, sort: &chc::DatatypeSort) -> impl std::fmt::Display { 328 | let ss = Sorts::new(&sort.args); 329 | let sym = chc::DatatypeSymbol::new(format!("{}{}", sort.symbol, ss)); 330 | self.datatype_discr_def(&sym) 331 | } 332 | 333 | pub fn datatype_discr_def(&self, sym: &chc::DatatypeSymbol) -> impl std::fmt::Display { 334 | format!("datatype_discr<{}>", self.fmt_datatype_symbol(sym)) 335 | } 336 | 337 | pub fn matcher_pred(&self, p: &chc::MatcherPred) -> impl std::fmt::Display { 338 | let ss = Sorts::new(&p.datatype_args); 339 | let sym = chc::DatatypeSymbol::new(format!("{}{}", p.datatype_symbol, ss)); 340 | self.matcher_pred_def(&sym) 341 | } 342 | 343 | pub fn matcher_pred_def(&self, sym: &chc::DatatypeSymbol) -> impl std::fmt::Display { 344 | format!("matcher_pred<{}>", self.fmt_datatype_symbol(sym)) 345 | } 346 | 347 | pub fn fmt_sort(&self, sort: &chc::Sort) -> impl std::fmt::Display { 348 | let sym = Sort::new(sort).to_symbol(); 349 | self.fmt_datatype_symbol(&sym) 350 | } 351 | 352 | pub fn fmt_datatype_symbol(&self, sym: &chc::DatatypeSymbol) -> impl std::fmt::Display { 353 | self.renamer.rename(sym) 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/refine/template.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use rustc_index::IndexVec; 4 | use rustc_middle::mir::{Local, Mutability}; 5 | use rustc_middle::ty as mir_ty; 6 | use rustc_span::def_id::DefId; 7 | 8 | use super::basic_block::BasicBlockType; 9 | use crate::chc; 10 | use crate::refine; 11 | use crate::rty; 12 | 13 | pub trait TemplateRegistry { 14 | fn register_template(&mut self, tmpl: rty::Template) -> rty::RefinedType; 15 | } 16 | 17 | impl TemplateRegistry for &mut T 18 | where 19 | T: TemplateRegistry + ?Sized, 20 | { 21 | fn register_template(&mut self, tmpl: rty::Template) -> rty::RefinedType { 22 | T::register_template(self, tmpl) 23 | } 24 | } 25 | 26 | /// [`TemplateScope`] with no variables in scope. 27 | #[derive(Clone, Default)] 28 | pub struct EmptyTemplateScope; 29 | 30 | impl TemplateScope for EmptyTemplateScope { 31 | type Var = rty::Closed; 32 | fn build_template(&self) -> rty::TemplateBuilder { 33 | rty::TemplateBuilder::default() 34 | } 35 | } 36 | 37 | pub trait TemplateScope { 38 | type Var: chc::Var; 39 | fn build_template(&self) -> rty::TemplateBuilder; 40 | } 41 | 42 | impl TemplateScope for &T 43 | where 44 | T: TemplateScope, 45 | { 46 | type Var = T::Var; 47 | fn build_template(&self) -> rty::TemplateBuilder { 48 | T::build_template(self) 49 | } 50 | } 51 | 52 | impl TemplateScope for rty::TemplateBuilder 53 | where 54 | T: chc::Var, 55 | { 56 | type Var = T; 57 | fn build_template(&self) -> rty::TemplateBuilder { 58 | self.clone() 59 | } 60 | } 61 | 62 | /// Translates [`mir_ty::Ty`] to [`rty::Type`]. 63 | /// 64 | /// This struct implements a translation from Rust MIR types to Thrust types. 65 | /// Thrust types may contain refinement predicates which do not exist in MIR types, 66 | /// and [`TypeBuilder`] solely builds types with null refinement (true) in 67 | /// [`TypeBuilder::build`]. This also provides [`TypeBuilder::for_template`] to build 68 | /// refinement types by filling unknown predicates with templates with predicate variables. 69 | #[derive(Clone)] 70 | pub struct TypeBuilder<'tcx> { 71 | tcx: mir_ty::TyCtxt<'tcx>, 72 | /// Maps index in [`mir_ty::ParamTy`] to [`rty::TypeParamIdx`]. 73 | /// These indices may differ because we skip lifetime parameters and they always need to be 74 | /// mapped when we translate a [`mir_ty::ParamTy`] to [`rty::ParamType`]. 75 | /// See [`rty::TypeParamIdx`] for more details. 76 | param_idx_mapping: HashMap, 77 | } 78 | 79 | impl<'tcx> TypeBuilder<'tcx> { 80 | pub fn new(tcx: mir_ty::TyCtxt<'tcx>, def_id: DefId) -> Self { 81 | let generics = tcx.generics_of(def_id); 82 | let mut param_idx_mapping: HashMap = Default::default(); 83 | for i in 0..generics.count() { 84 | let generic_param = generics.param_at(i, tcx); 85 | match generic_param.kind { 86 | mir_ty::GenericParamDefKind::Lifetime => {} 87 | mir_ty::GenericParamDefKind::Type { .. } => { 88 | param_idx_mapping.insert(i as u32, param_idx_mapping.len().into()); 89 | } 90 | mir_ty::GenericParamDefKind::Const { .. } => {} 91 | } 92 | } 93 | Self { 94 | tcx, 95 | param_idx_mapping, 96 | } 97 | } 98 | 99 | fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { 100 | let index = *self 101 | .param_idx_mapping 102 | .get(&ty.index) 103 | .expect("unknown type param idx"); 104 | rty::ParamType::new(index).into() 105 | } 106 | 107 | // TODO: consolidate two impls 108 | pub fn build(&self, ty: mir_ty::Ty<'tcx>) -> rty::Type { 109 | match ty.kind() { 110 | mir_ty::TyKind::Bool => rty::Type::bool(), 111 | mir_ty::TyKind::Uint(_) | mir_ty::TyKind::Int(_) => rty::Type::int(), 112 | mir_ty::TyKind::Str => rty::Type::string(), 113 | mir_ty::TyKind::Ref(_, elem_ty, mutbl) => { 114 | let elem_ty = self.build(*elem_ty); 115 | match mutbl { 116 | mir_ty::Mutability::Mut => rty::PointerType::mut_to(elem_ty).into(), 117 | mir_ty::Mutability::Not => rty::PointerType::immut_to(elem_ty).into(), 118 | } 119 | } 120 | mir_ty::TyKind::Tuple(ts) => { 121 | // elaboration: all fields are boxed 122 | let elems = ts 123 | .iter() 124 | .map(|ty| rty::PointerType::own(self.build(ty)).into()) 125 | .collect(); 126 | rty::TupleType::new(elems).into() 127 | } 128 | mir_ty::TyKind::Never => rty::Type::never(), 129 | mir_ty::TyKind::Param(ty) => self.translate_param_type(ty), 130 | mir_ty::TyKind::FnPtr(sig) => { 131 | // TODO: justification for skip_binder 132 | let sig = sig.skip_binder(); 133 | let params = sig 134 | .inputs() 135 | .iter() 136 | .map(|ty| rty::RefinedType::unrefined(self.build(*ty)).vacuous()) 137 | .collect(); 138 | let ret = rty::RefinedType::unrefined(self.build(sig.output())); 139 | rty::FunctionType::new(params, ret.vacuous()).into() 140 | } 141 | mir_ty::TyKind::Adt(def, params) if def.is_box() => { 142 | rty::PointerType::own(self.build(params.type_at(0))).into() 143 | } 144 | mir_ty::TyKind::Adt(def, params) => { 145 | if def.is_enum() { 146 | let sym = refine::datatype_symbol(self.tcx, def.did()); 147 | let args: IndexVec<_, _> = params 148 | .types() 149 | .map(|ty| rty::RefinedType::unrefined(self.build(ty))) 150 | .collect(); 151 | rty::EnumType::new(sym, args).into() 152 | } else if def.is_struct() { 153 | let elem_tys = def 154 | .all_fields() 155 | .map(|field| { 156 | let ty = field.ty(self.tcx, params); 157 | // elaboration: all fields are boxed 158 | rty::PointerType::own(self.build(ty)).into() 159 | }) 160 | .collect(); 161 | rty::TupleType::new(elem_tys).into() 162 | } else { 163 | unimplemented!("unsupported ADT: {:?}", ty); 164 | } 165 | } 166 | kind => unimplemented!("unrefined_ty: {:?}", kind), 167 | } 168 | } 169 | 170 | pub fn for_template<'a, R>( 171 | &self, 172 | registry: &'a mut R, 173 | ) -> TemplateTypeBuilder<'tcx, 'a, R, EmptyTemplateScope> { 174 | TemplateTypeBuilder { 175 | inner: self.clone(), 176 | registry, 177 | scope: Default::default(), 178 | } 179 | } 180 | 181 | pub fn for_function_template<'a, R>( 182 | &self, 183 | registry: &'a mut R, 184 | sig: mir_ty::FnSig<'tcx>, 185 | ) -> FunctionTemplateTypeBuilder<'tcx, 'a, R> { 186 | FunctionTemplateTypeBuilder { 187 | inner: self.clone(), 188 | registry, 189 | param_tys: sig 190 | .inputs() 191 | .iter() 192 | .map(|ty| mir_ty::TypeAndMut { 193 | ty: *ty, 194 | mutbl: Mutability::Not, 195 | }) 196 | .collect(), 197 | ret_ty: sig.output(), 198 | param_rtys: Default::default(), 199 | param_refinement: None, 200 | ret_rty: None, 201 | } 202 | } 203 | } 204 | 205 | /// Translates [`mir_ty::Ty`] to [`rty::Type`] using templates for refinements. 206 | /// 207 | /// [`rty::Template`] is a refinement type in the form of `{ T | P(x1, ..., xn) }` where `P` is a 208 | /// predicate variable. When constructing a template, we need to know which variables can affect the 209 | /// predicate of the template (dependencies, `x1, ..., xn`), and they are provided by the 210 | /// [`TemplateScope`]. No variables are in scope by default and you can provide a scope using 211 | /// [`TemplateTypeBuilder::with_scope`]. 212 | pub struct TemplateTypeBuilder<'tcx, 'a, R, S> { 213 | inner: TypeBuilder<'tcx>, 214 | // XXX: this can't be simply `R` because monomorphization instantiates types recursively 215 | registry: &'a mut R, 216 | scope: S, 217 | } 218 | 219 | impl<'tcx, 'a, R, S> TemplateTypeBuilder<'tcx, 'a, R, S> { 220 | pub fn with_scope(self, scope: T) -> TemplateTypeBuilder<'tcx, 'a, R, T> { 221 | TemplateTypeBuilder { 222 | inner: self.inner, 223 | registry: self.registry, 224 | scope, 225 | } 226 | } 227 | } 228 | 229 | impl<'tcx, 'a, R, S> TemplateTypeBuilder<'tcx, 'a, R, S> 230 | where 231 | R: TemplateRegistry, 232 | S: TemplateScope, 233 | { 234 | pub fn build(&mut self, ty: mir_ty::Ty<'tcx>) -> rty::Type { 235 | match ty.kind() { 236 | mir_ty::TyKind::Bool => rty::Type::bool(), 237 | mir_ty::TyKind::Uint(_) | mir_ty::TyKind::Int(_) => rty::Type::int(), 238 | mir_ty::TyKind::Str => rty::Type::string(), 239 | mir_ty::TyKind::Ref(_, elem_ty, mutbl) => { 240 | let elem_ty = self.build(*elem_ty); 241 | match mutbl { 242 | mir_ty::Mutability::Mut => rty::PointerType::mut_to(elem_ty).into(), 243 | mir_ty::Mutability::Not => rty::PointerType::immut_to(elem_ty).into(), 244 | } 245 | } 246 | mir_ty::TyKind::Tuple(ts) => { 247 | // elaboration: all fields are boxed 248 | let elems = ts 249 | .iter() 250 | .map(|ty| rty::PointerType::own(self.build(ty)).into()) 251 | .collect(); 252 | rty::TupleType::new(elems).into() 253 | } 254 | mir_ty::TyKind::Never => rty::Type::never(), 255 | mir_ty::TyKind::Param(ty) => self.inner.translate_param_type(ty).vacuous(), 256 | mir_ty::TyKind::FnPtr(sig) => { 257 | // TODO: justification for skip_binder 258 | let sig = sig.skip_binder(); 259 | let ty = self.inner.for_function_template(self.registry, sig).build(); 260 | rty::Type::function(ty) 261 | } 262 | mir_ty::TyKind::Adt(def, params) if def.is_box() => { 263 | rty::PointerType::own(self.build(params.type_at(0))).into() 264 | } 265 | mir_ty::TyKind::Adt(def, params) => { 266 | if def.is_enum() { 267 | let sym = refine::datatype_symbol(self.inner.tcx, def.did()); 268 | let args: IndexVec<_, _> = 269 | params.types().map(|ty| self.build_refined(ty)).collect(); 270 | rty::EnumType::new(sym, args).into() 271 | } else if def.is_struct() { 272 | let elem_tys = def 273 | .all_fields() 274 | .map(|field| { 275 | let ty = field.ty(self.inner.tcx, params); 276 | // elaboration: all fields are boxed 277 | rty::PointerType::own(self.build(ty)).into() 278 | }) 279 | .collect(); 280 | rty::TupleType::new(elem_tys).into() 281 | } else { 282 | unimplemented!("unsupported ADT: {:?}", ty); 283 | } 284 | } 285 | kind => unimplemented!("ty: {:?}", kind), 286 | } 287 | } 288 | 289 | pub fn build_refined(&mut self, ty: mir_ty::Ty<'tcx>) -> rty::RefinedType { 290 | // TODO: consider building ty with scope 291 | let ty = self.inner.for_template(self.registry).build(ty).vacuous(); 292 | let tmpl = self.scope.build_template().build(ty); 293 | self.registry.register_template(tmpl) 294 | } 295 | 296 | pub fn build_basic_block( 297 | &mut self, 298 | live_locals: I, 299 | ret_ty: mir_ty::Ty<'tcx>, 300 | ) -> BasicBlockType 301 | where 302 | I: IntoIterator)>, 303 | { 304 | let mut locals = IndexVec::::new(); 305 | let mut tys = Vec::new(); 306 | // TODO: avoid two iteration and assumption of FunctionParamIdx match between locals and ty 307 | for (local, ty) in live_locals { 308 | locals.push((local, ty.mutbl)); 309 | tys.push(ty); 310 | } 311 | let ty = FunctionTemplateTypeBuilder { 312 | inner: self.inner.clone(), 313 | registry: self.registry, 314 | param_tys: tys, 315 | ret_ty, 316 | param_rtys: Default::default(), 317 | param_refinement: None, 318 | ret_rty: None, 319 | } 320 | .build(); 321 | BasicBlockType { ty, locals } 322 | } 323 | } 324 | 325 | /// A builder for function template types. 326 | pub struct FunctionTemplateTypeBuilder<'tcx, 'a, R> { 327 | inner: TypeBuilder<'tcx>, 328 | registry: &'a mut R, 329 | param_tys: Vec>, 330 | ret_ty: mir_ty::Ty<'tcx>, 331 | param_refinement: Option>, 332 | param_rtys: HashMap>, 333 | ret_rty: Option>, 334 | } 335 | 336 | impl<'tcx, 'a, R> FunctionTemplateTypeBuilder<'tcx, 'a, R> { 337 | pub fn param_refinement( 338 | &mut self, 339 | refinement: rty::Refinement, 340 | ) -> &mut Self { 341 | let rty::Refinement { existentials, body } = refinement; 342 | let refinement = rty::Refinement { 343 | existentials, 344 | body: body.map_var(|v| match v { 345 | rty::RefinedTypeVar::Free(idx) if idx.index() == self.param_tys.len() - 1 => { 346 | rty::RefinedTypeVar::Value 347 | } 348 | v => v, 349 | }), 350 | }; 351 | self.param_refinement = Some(refinement); 352 | self 353 | } 354 | 355 | pub fn param_rty( 356 | &mut self, 357 | idx: rty::FunctionParamIdx, 358 | ty: rty::RefinedType, 359 | ) -> &mut Self { 360 | self.param_rtys.insert(idx, ty); 361 | self 362 | } 363 | 364 | pub fn ret_refinement( 365 | &mut self, 366 | refinement: rty::Refinement, 367 | ) -> &mut Self { 368 | let ty = self.inner.build(self.ret_ty); 369 | self.ret_rty = Some(rty::RefinedType::new(ty.vacuous(), refinement)); 370 | self 371 | } 372 | 373 | pub fn ret_rty(&mut self, rty: rty::RefinedType) -> &mut Self { 374 | self.ret_rty = Some(rty); 375 | self 376 | } 377 | } 378 | 379 | impl<'tcx, 'a, R> FunctionTemplateTypeBuilder<'tcx, 'a, R> 380 | where 381 | R: TemplateRegistry, 382 | { 383 | pub fn build(&mut self) -> rty::FunctionType { 384 | let mut builder = rty::TemplateBuilder::default(); 385 | let mut param_rtys = IndexVec::::new(); 386 | for (idx, param_ty) in self.param_tys.iter().enumerate() { 387 | let param_rty = self 388 | .param_rtys 389 | .get(&idx.into()) 390 | .cloned() 391 | .unwrap_or_else(|| { 392 | if idx == self.param_tys.len() - 1 { 393 | if let Some(param_refinement) = &self.param_refinement { 394 | let ty = self.inner.build(param_ty.ty); 395 | rty::RefinedType::new(ty.vacuous(), param_refinement.clone()) 396 | } else { 397 | self.inner 398 | .for_template(self.registry) 399 | .with_scope(&builder) 400 | .build_refined(param_ty.ty) 401 | } 402 | } else { 403 | rty::RefinedType::unrefined( 404 | self.inner 405 | .for_template(self.registry) 406 | .with_scope(&builder) 407 | .build(param_ty.ty), 408 | ) 409 | } 410 | }); 411 | let param_rty = if param_ty.mutbl.is_mut() { 412 | // elaboration: treat mutabully declared variables as own 413 | param_rty.boxed() 414 | } else { 415 | param_rty 416 | }; 417 | let param_sort = param_rty.ty.to_sort(); 418 | let param_idx = param_rtys.push(param_rty); 419 | builder.add_dependency(param_idx, param_sort); 420 | } 421 | 422 | // elaboration: we need at least one predicate variable in parameter 423 | if self.param_tys.is_empty() { 424 | let param_rty = if let Some(param_refinement) = &self.param_refinement { 425 | rty::RefinedType::new(rty::Type::unit(), param_refinement.clone()) 426 | } else { 427 | let unit_ty = mir_ty::Ty::new_unit(self.inner.tcx); 428 | self.inner 429 | .for_template(self.registry) 430 | .with_scope(&builder) 431 | .build_refined(unit_ty) 432 | }; 433 | param_rtys.push(param_rty); 434 | } 435 | 436 | let ret_rty = self.ret_rty.clone().unwrap_or_else(|| { 437 | self.inner 438 | .for_template(self.registry) 439 | .with_scope(&builder) 440 | .build_refined(self.ret_ty) 441 | }); 442 | rty::FunctionType::new(param_rtys, ret_rty) 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /src/chc/smtlib2.rs: -------------------------------------------------------------------------------- 1 | //! Wrappers around CHC structures to display them in SMT-LIB2 format. 2 | //! 3 | //! The main entry point is the [`System`] wrapper, which takes a [`chc::System`] and provides a 4 | //! [`std::fmt::Display`] implementation that produces a complete SMT-LIB2. 5 | //! It uses [`FormatContext`] to handle the complexities of the conversion, 6 | //! such as naming convention and solver-specific workarounds. 7 | //! The output of this module is what gets passed to the external CHC solver. 8 | 9 | use crate::chc::{self, format_context::FormatContext}; 10 | 11 | /// A helper struct to display a list of items. 12 | #[derive(Debug, Clone)] 13 | struct List { 14 | open: Option<&'static str>, 15 | close: Option<&'static str>, 16 | delimiter: &'static str, 17 | items: Vec, 18 | } 19 | 20 | impl std::fmt::Display for List 21 | where 22 | T: std::fmt::Display, 23 | { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | if let Some(c) = self.open { 26 | write!(f, "{}", c)?; 27 | } 28 | for (i, e) in self.items.iter().enumerate() { 29 | if i != 0 { 30 | write!(f, "{}", self.delimiter)?; 31 | } 32 | write!(f, "{}", e)?; 33 | } 34 | if let Some(c) = self.close { 35 | write!(f, "{}", c)?; 36 | } 37 | Ok(()) 38 | } 39 | } 40 | 41 | impl List { 42 | pub fn closed(inner: I) -> Self 43 | where 44 | I: std::iter::IntoIterator, 45 | { 46 | Self { 47 | open: Some("("), 48 | close: Some(")"), 49 | delimiter: " ", 50 | items: inner.into_iter().collect(), 51 | } 52 | } 53 | 54 | pub fn multiline_closed(inner: I) -> Self 55 | where 56 | I: std::iter::IntoIterator, 57 | { 58 | Self { 59 | open: Some("(\n"), 60 | close: Some("\n)"), 61 | delimiter: "\n", 62 | items: inner.into_iter().collect(), 63 | } 64 | } 65 | 66 | pub fn multiline_open(inner: I) -> Self 67 | where 68 | I: std::iter::IntoIterator, 69 | { 70 | Self { 71 | open: None, 72 | close: None, 73 | delimiter: "\n", 74 | items: inner.into_iter().collect(), 75 | } 76 | } 77 | 78 | pub fn open(inner: I) -> Self 79 | where 80 | I: std::iter::IntoIterator, 81 | { 82 | Self { 83 | open: None, 84 | close: None, 85 | delimiter: " ", 86 | items: inner.into_iter().collect(), 87 | } 88 | } 89 | } 90 | 91 | /// A wrapper around a [`chc::Term`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 92 | #[derive(Debug, Clone)] 93 | struct Term<'ctx, 'a> { 94 | ctx: &'ctx FormatContext, 95 | // we need clause to select box/mut selector/constructor based on sort 96 | clause: &'a chc::Clause, 97 | inner: &'a chc::Term, 98 | } 99 | 100 | impl<'ctx, 'a> std::fmt::Display for Term<'ctx, 'a> { 101 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 102 | match self.inner { 103 | chc::Term::Null => write!(f, "null"), 104 | chc::Term::Var(v) => write!(f, "{}", v), 105 | chc::Term::Int(i) => write!(f, "{}", i), 106 | chc::Term::Bool(b) => write!(f, "{}", b), 107 | chc::Term::String(s) => write!(f, "\"{}\"", s.escape_default()), 108 | chc::Term::Box(t) => { 109 | let s = self.clause.term_sort(t); 110 | write!( 111 | f, 112 | "({} {})", 113 | self.ctx.box_ctor(&s), 114 | Term::new(self.ctx, self.clause, t) 115 | ) 116 | } 117 | chc::Term::Mut(t1, t2) => { 118 | let s = self.clause.term_sort(t1); 119 | write!( 120 | f, 121 | "({} {} {})", 122 | self.ctx.mut_ctor(&s), 123 | Term::new(self.ctx, self.clause, t1), 124 | Term::new(self.ctx, self.clause, t2) 125 | ) 126 | } 127 | chc::Term::BoxCurrent(t) => { 128 | let s = self.clause.term_sort(t).deref(); 129 | write!( 130 | f, 131 | "({} {})", 132 | self.ctx.box_current(&s), 133 | Term::new(self.ctx, self.clause, t) 134 | ) 135 | } 136 | chc::Term::MutCurrent(t) => { 137 | let s = self.clause.term_sort(t).deref(); 138 | write!( 139 | f, 140 | "({} {})", 141 | self.ctx.mut_current(&s), 142 | Term::new(self.ctx, self.clause, t) 143 | ) 144 | } 145 | chc::Term::MutFinal(t) => { 146 | let s = self.clause.term_sort(t).deref(); 147 | write!( 148 | f, 149 | "({} {})", 150 | self.ctx.mut_final(&s), 151 | Term::new(self.ctx, self.clause, t) 152 | ) 153 | } 154 | chc::Term::App(fn_, args) => { 155 | write!( 156 | f, 157 | "({} {})", 158 | fn_, 159 | List::open(args.iter().map(|t| Term::new(self.ctx, self.clause, t))) 160 | ) 161 | } 162 | chc::Term::Tuple(ts) => { 163 | let ss: Vec<_> = ts.iter().map(|t| self.clause.term_sort(t)).collect(); 164 | if ss.is_empty() { 165 | write!(f, "{}", self.ctx.tuple_ctor(&ss),) 166 | } else { 167 | write!( 168 | f, 169 | "({} {})", 170 | self.ctx.tuple_ctor(&ss), 171 | List::open(ts.iter().map(|t| Term::new(self.ctx, self.clause, t))) 172 | ) 173 | } 174 | } 175 | chc::Term::TupleProj(t, i) => { 176 | let s = self.clause.term_sort(t); 177 | write!( 178 | f, 179 | "({} {})", 180 | self.ctx.tuple_proj(s.as_tuple().unwrap(), *i), 181 | Term::new(self.ctx, self.clause, t) 182 | ) 183 | } 184 | chc::Term::DatatypeCtor(sort, sym, args) => { 185 | if args.is_empty() { 186 | write!(f, "{}", self.ctx.datatype_ctor(sort, sym)) 187 | } else { 188 | write!( 189 | f, 190 | "({} {})", 191 | self.ctx.datatype_ctor(sort, sym), 192 | List::open(args.iter().map(|t| Term::new(self.ctx, self.clause, t))) 193 | ) 194 | } 195 | } 196 | chc::Term::DatatypeDiscr(_s, t) => { 197 | let s = self.clause.term_sort(t).into_datatype().unwrap(); 198 | write!( 199 | f, 200 | "({} {})", 201 | self.ctx.datatype_discr(&s), 202 | Term::new(self.ctx, self.clause, t) 203 | ) 204 | } 205 | chc::Term::FormulaExistentialVar(_, name) => write!(f, "{}", name), 206 | } 207 | } 208 | } 209 | 210 | impl<'ctx, 'a> Term<'ctx, 'a> { 211 | pub fn new(ctx: &'ctx FormatContext, clause: &'a chc::Clause, inner: &'a chc::Term) -> Self { 212 | Self { ctx, clause, inner } 213 | } 214 | } 215 | 216 | /// A wrapper around a [`chc::Atom`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 217 | #[derive(Debug, Clone)] 218 | pub struct Atom<'ctx, 'a> { 219 | ctx: &'ctx FormatContext, 220 | clause: &'a chc::Clause, 221 | inner: &'a chc::Atom, 222 | } 223 | 224 | impl<'ctx, 'a> std::fmt::Display for Atom<'ctx, 'a> { 225 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 226 | if self.inner.pred.is_negative() { 227 | write!(f, "(not ")?; 228 | } 229 | let pred = match &self.inner.pred { 230 | chc::Pred::Matcher(p) => self.ctx.matcher_pred(p).to_string(), 231 | p => p.name().into_owned(), 232 | }; 233 | if self.inner.args.is_empty() { 234 | write!(f, "{}", pred)?; 235 | } else { 236 | let args = List::open( 237 | self.inner 238 | .args 239 | .iter() 240 | .map(|t| Term::new(self.ctx, self.clause, t)), 241 | ); 242 | write!(f, "({} {})", pred, args)?; 243 | } 244 | if self.inner.pred.is_negative() { 245 | write!(f, ")")?; 246 | } 247 | Ok(()) 248 | } 249 | } 250 | 251 | impl<'ctx, 'a> Atom<'ctx, 'a> { 252 | pub fn new(ctx: &'ctx FormatContext, clause: &'a chc::Clause, inner: &'a chc::Atom) -> Self { 253 | Self { ctx, clause, inner } 254 | } 255 | } 256 | 257 | /// A wrapper around a [`chc::Formula`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 258 | #[derive(Debug, Clone)] 259 | pub struct Formula<'ctx, 'a> { 260 | ctx: &'ctx FormatContext, 261 | clause: &'a chc::Clause, 262 | inner: &'a chc::Formula, 263 | } 264 | 265 | impl<'ctx, 'a> std::fmt::Display for Formula<'ctx, 'a> { 266 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 267 | match self.inner { 268 | chc::Formula::Atom(atom) => { 269 | let atom = Atom::new(self.ctx, self.clause, atom); 270 | write!(f, "{}", atom) 271 | } 272 | chc::Formula::Not(fo) => { 273 | let fo = Formula::new(self.ctx, self.clause, fo); 274 | write!(f, "(not {})", fo) 275 | } 276 | chc::Formula::And(fs) => { 277 | let fs = List::open(fs.iter().map(|fo| Formula::new(self.ctx, self.clause, fo))); 278 | write!(f, "(and {})", fs) 279 | } 280 | chc::Formula::Or(fs) => { 281 | let fs = List::open(fs.iter().map(|fo| Formula::new(self.ctx, self.clause, fo))); 282 | write!(f, "(or {})", fs) 283 | } 284 | chc::Formula::Exists(vars, fo) => { 285 | let vars = 286 | List::closed(vars.iter().map(|(v, s)| { 287 | List::closed([v.to_string(), self.ctx.fmt_sort(s).to_string()]) 288 | })); 289 | let fo = Formula::new(self.ctx, self.clause, fo); 290 | write!(f, "(exists {vars} {fo})") 291 | } 292 | } 293 | } 294 | } 295 | 296 | impl<'ctx, 'a> Formula<'ctx, 'a> { 297 | pub fn new(ctx: &'ctx FormatContext, clause: &'a chc::Clause, inner: &'a chc::Formula) -> Self { 298 | Self { ctx, clause, inner } 299 | } 300 | } 301 | 302 | /// A wrapper around a [`chc::Body`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 303 | #[derive(Debug, Clone)] 304 | pub struct Body<'ctx, 'a> { 305 | ctx: &'ctx FormatContext, 306 | clause: &'a chc::Clause, 307 | inner: &'a chc::Body, 308 | } 309 | 310 | impl<'ctx, 'a> std::fmt::Display for Body<'ctx, 'a> { 311 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 312 | let atoms = List::open( 313 | self.inner 314 | .atoms 315 | .iter() 316 | .map(|a| Atom::new(self.ctx, self.clause, a)), 317 | ); 318 | let formula = Formula::new(self.ctx, self.clause, &self.inner.formula); 319 | write!(f, "(and {atoms} {formula})") 320 | } 321 | } 322 | 323 | impl<'ctx, 'a> Body<'ctx, 'a> { 324 | pub fn new(ctx: &'ctx FormatContext, clause: &'a chc::Clause, inner: &'a chc::Body) -> Self { 325 | Self { ctx, clause, inner } 326 | } 327 | } 328 | 329 | /// A wrapper around a [`chc::Clause`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 330 | #[derive(Debug, Clone)] 331 | pub struct Clause<'ctx, 'a> { 332 | ctx: &'ctx FormatContext, 333 | inner: &'a chc::Clause, 334 | } 335 | 336 | impl<'ctx, 'a> std::fmt::Display for Clause<'ctx, 'a> { 337 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 338 | if !self.inner.debug_info.is_empty() { 339 | writeln!(f, "{}", self.inner.debug_info.display("; "))?; 340 | } 341 | let body = Body::new(self.ctx, self.inner, &self.inner.body); 342 | let head = Atom::new(self.ctx, self.inner, &self.inner.head); 343 | if !self.inner.vars.is_empty() { 344 | let vars = List::closed( 345 | self.inner 346 | .vars 347 | .iter_enumerated() 348 | .map(|(v, s)| List::closed([v.to_string(), self.ctx.fmt_sort(s).to_string()])), 349 | ); 350 | write!(f, "(forall {vars} ")?; 351 | } 352 | write!(f, "(=> {body} {head})")?; 353 | if !self.inner.vars.is_empty() { 354 | write!(f, ")")?; 355 | } 356 | Ok(()) 357 | } 358 | } 359 | 360 | impl<'ctx, 'a> Clause<'ctx, 'a> { 361 | pub fn new(ctx: &'ctx FormatContext, inner: &'a chc::Clause) -> Self { 362 | Self { ctx, inner } 363 | } 364 | } 365 | 366 | /// A wrapper around a [`chc::DatatypeSelector`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 367 | #[derive(Debug, Clone)] 368 | pub struct DatatypeSelector<'ctx, 'a> { 369 | ctx: &'ctx FormatContext, 370 | inner: &'a chc::DatatypeSelector, 371 | } 372 | 373 | impl<'ctx, 'a> std::fmt::Display for DatatypeSelector<'ctx, 'a> { 374 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 375 | write!( 376 | f, 377 | "({} {})", 378 | &self.inner.symbol, 379 | self.ctx.fmt_sort(&self.inner.sort) 380 | ) 381 | } 382 | } 383 | 384 | impl<'ctx, 'a> DatatypeSelector<'ctx, 'a> { 385 | pub fn new(ctx: &'ctx FormatContext, inner: &'a chc::DatatypeSelector) -> Self { 386 | Self { ctx, inner } 387 | } 388 | } 389 | 390 | /// A wrapper around a [`chc::DatatypeCtor`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 391 | #[derive(Debug, Clone)] 392 | pub struct DatatypeCtor<'ctx, 'a> { 393 | ctx: &'ctx FormatContext, 394 | inner: &'a chc::DatatypeCtor, 395 | } 396 | 397 | impl<'ctx, 'a> std::fmt::Display for DatatypeCtor<'ctx, 'a> { 398 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 399 | let selectors = self 400 | .inner 401 | .selectors 402 | .iter() 403 | .map(|s| DatatypeSelector::new(self.ctx, s)); 404 | write!(f, " ({} {})", &self.inner.symbol, List::open(selectors)) 405 | } 406 | } 407 | 408 | impl<'ctx, 'a> DatatypeCtor<'ctx, 'a> { 409 | pub fn new(ctx: &'ctx FormatContext, inner: &'a chc::DatatypeCtor) -> Self { 410 | Self { ctx, inner } 411 | } 412 | } 413 | 414 | /// A wrapper around a slice of [`chc::Datatype`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 415 | #[derive(Debug, Clone)] 416 | pub struct Datatypes<'ctx, 'a> { 417 | ctx: &'ctx FormatContext, 418 | inner: &'a [chc::Datatype], 419 | } 420 | 421 | impl<'ctx, 'a> std::fmt::Display for Datatypes<'ctx, 'a> { 422 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 423 | if self.inner.is_empty() { 424 | return Ok(()); 425 | } 426 | 427 | let datatypes = self 428 | .inner 429 | .iter() 430 | .map(|d| format!("({} 0)", self.ctx.fmt_datatype_symbol(&d.symbol))); 431 | let ctors = self.inner.iter().map(|d| { 432 | format!( 433 | " (par () (\n{}\n ))", 434 | List::multiline_open(d.ctors.iter().map(|c| DatatypeCtor::new(self.ctx, c))) 435 | ) 436 | }); 437 | write!( 438 | f, 439 | "(declare-datatypes {} {})", 440 | List::closed(datatypes), 441 | List::multiline_closed(ctors) 442 | ) 443 | } 444 | } 445 | 446 | impl<'ctx, 'a> Datatypes<'ctx, 'a> { 447 | pub fn new(ctx: &'ctx FormatContext, inner: &'a [chc::Datatype]) -> Self { 448 | Self { ctx, inner } 449 | } 450 | } 451 | 452 | /// A wrapper around a [`chc::Datatype`] that provides a [`std::fmt::Display`] implementation for the 453 | /// discriminant function in SMT-LIB2 format. 454 | #[derive(Debug, Clone)] 455 | pub struct DatatypeDiscrFun<'ctx, 'a> { 456 | ctx: &'ctx FormatContext, 457 | inner: &'a chc::Datatype, 458 | } 459 | 460 | impl<'ctx, 'a> std::fmt::Display for DatatypeDiscrFun<'ctx, 'a> { 461 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 462 | let sym = &self.inner.symbol; 463 | let cases = self 464 | .inner 465 | .ctors 466 | .iter() 467 | .rfold("(- 1)".to_owned(), |acc, ctor| { 468 | format!( 469 | "(ite ((_ is {ctor}) x) {discr} {acc})", 470 | ctor = &ctor.symbol, 471 | discr = ctor.discriminant, 472 | ) 473 | }); 474 | write!( 475 | f, 476 | "(define-fun {discr} ((x {sym})) Int {cases})", 477 | discr = self.ctx.datatype_discr_def(sym), 478 | sym = self.ctx.fmt_datatype_symbol(sym), 479 | ) 480 | } 481 | } 482 | 483 | impl<'ctx, 'a> DatatypeDiscrFun<'ctx, 'a> { 484 | pub fn new(ctx: &'ctx FormatContext, inner: &'a chc::Datatype) -> DatatypeDiscrFun<'ctx, 'a> { 485 | DatatypeDiscrFun { ctx, inner } 486 | } 487 | } 488 | 489 | /// A wrapper around a [`chc::Datatype`] that provides a [`std::fmt::Display`] implementation for the 490 | /// matcher predicate in SMT-LIB2 format. 491 | #[derive(Debug, Clone)] 492 | pub struct MatcherPredFun<'ctx, 'a> { 493 | ctx: &'ctx FormatContext, 494 | inner: &'a chc::Datatype, 495 | } 496 | 497 | impl<'ctx, 'a> std::fmt::Display for MatcherPredFun<'ctx, 'a> { 498 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 499 | let sym = &self.inner.symbol; 500 | let mut offset = 0; 501 | let mut variants = Vec::new(); 502 | for ctor in &self.inner.ctors { 503 | let args = List::open( 504 | (0..ctor.selectors.len()) 505 | .map(|i| i + offset) 506 | .map(|i| format!("x{i}")), 507 | ); 508 | offset += ctor.selectors.len(); 509 | let repr = if ctor.selectors.is_empty() { 510 | ctor.symbol.to_string() 511 | } else { 512 | format!("({} {})", &ctor.symbol, args) 513 | }; 514 | variants.push(format!("(= v {repr})")); 515 | } 516 | let params = List::closed( 517 | self.inner 518 | .ctors 519 | .iter() 520 | .flat_map(|c| &c.selectors) 521 | .enumerate() 522 | .map(|(idx, s)| format!("(x{} {})", idx, self.ctx.fmt_sort(&s.sort))) 523 | .chain([format!("(v {})", self.ctx.fmt_datatype_symbol(sym))]), 524 | ); 525 | write!( 526 | f, 527 | "(define-fun {name} {params} Bool (or {variants}))", 528 | name = self.ctx.matcher_pred_def(sym), 529 | variants = List::open(variants), 530 | ) 531 | } 532 | } 533 | 534 | impl<'ctx, 'a> MatcherPredFun<'ctx, 'a> { 535 | pub fn new(ctx: &'ctx FormatContext, inner: &'a chc::Datatype) -> MatcherPredFun<'ctx, 'a> { 536 | MatcherPredFun { ctx, inner } 537 | } 538 | } 539 | 540 | /// A wrapper around a [`chc::System`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. 541 | #[derive(Debug, Clone)] 542 | pub struct System<'a> { 543 | ctx: FormatContext, 544 | inner: &'a chc::System, 545 | } 546 | 547 | impl<'a> std::fmt::Display for System<'a> { 548 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 549 | writeln!(f, "(set-logic HORN)\n")?; 550 | 551 | writeln!(f, "{}\n", Datatypes::new(&self.ctx, self.ctx.datatypes()))?; 552 | for datatype in self.ctx.datatypes() { 553 | writeln!(f, "{}", DatatypeDiscrFun::new(&self.ctx, datatype))?; 554 | writeln!(f, "{}", MatcherPredFun::new(&self.ctx, datatype))?; 555 | } 556 | writeln!(f)?; 557 | for (p, def) in self.inner.pred_vars.iter_enumerated() { 558 | if !def.debug_info.is_empty() { 559 | writeln!(f, "{}", def.debug_info.display("; "))?; 560 | } 561 | writeln!( 562 | f, 563 | "(declare-fun {} {} Bool)\n", 564 | p, 565 | List::closed(def.sig.iter().map(|s| self.ctx.fmt_sort(s))) 566 | )?; 567 | } 568 | for (id, clause) in self.inner.clauses.iter_enumerated() { 569 | writeln!( 570 | f, 571 | "; {:?}\n(assert {})\n", 572 | id, 573 | Clause::new(&self.ctx, clause) 574 | )?; 575 | } 576 | Ok(()) 577 | } 578 | } 579 | 580 | impl<'a> System<'a> { 581 | pub fn new(inner: &'a chc::System) -> Self { 582 | let ctx = FormatContext::from_system(inner); 583 | Self { ctx, inner } 584 | } 585 | } 586 | --------------------------------------------------------------------------------