Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/analyze/annot_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ pub struct FormulaFn<'tcx> {
formula: chc::Formula<rty::FunctionParamIdx>,
}

/// The source name a parameter of a formula function lifted out of a function body
/// (`invariant!`, `ghost!`) refers to.
///
/// The lifted function is free, where `self` is not a legal parameter name, so a formula
/// naming the receiver gets a synthetic parameter instead. It stands for the value that
/// debug info records as `self`.
pub fn lifted_param_source_name(ident: rustc_span::symbol::Ident) -> rustc_span::Symbol {
if ident.name.as_str() == "__thrust_self" {
rustc_span::Symbol::intern("self")
} else {
ident.name
}
}

impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &FormulaFn<'_>
where
D: pretty::DocAllocator<'a, termcolor::ColorSpec>,
Expand Down
5 changes: 3 additions & 2 deletions src/analyze/basic_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1054,9 +1054,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
.skip(1)
.map(|ident| {
let ident = ident.expect("ghost term parameters must be named");
let operand = self.operand_of_name(ident.name).unwrap_or_else(|| {
let name = analyze::annot_fn::lifted_param_source_name(ident);
let operand = self.operand_of_name(name).unwrap_or_else(|| {
self.tcx.dcx().fatal(format!(
"ghost term refers to `{ident}`, which is not a live variable here"
"ghost term refers to `{name}`, which is not a live variable here"
))
});
self.operand_refined_type(operand)
Expand Down
8 changes: 1 addition & 7 deletions src/analyze/local_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,13 +853,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
.unwrap_or(*input_ty)
};

// The synthetic `__thrust_self` parameter (emitted when an invariant refers to the receiver
// `self`) maps to the loop-carried receiver, which appears as `self` in debug info.
let name = if ident.name.as_str() == "__thrust_self" {
rustc_span::Symbol::intern("self")
} else {
ident.name
};
let name = analyze::annot_fn::lifted_param_source_name(ident);

if input_ty
.ty_adt_def()
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/fail/ghost_generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//@error-in-other-file: Unsat
//@compile-flags: -C debug-assertions=off -A unused-variables

use thrust_models::Ghost;

#[thrust_macros::requires(g == v)]
fn expect_same<T>(g: Ghost<T>, v: T) {
let _ = g;
}

#[thrust_macros::context]
fn record<T: Copy>(a: T, b: T) {
let g = thrust_macros::ghost!(|b: T| -> T { b });
expect_same(g, a);
}

fn main() {
record(3_i64, 5_i64);
}
26 changes: 26 additions & 0 deletions tests/ui/fail/ghost_self.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//@error-in-other-file: Unsat
//@compile-flags: -C debug-assertions=off -A unused-variables

use thrust_models::model::{Int, Seq};
use thrust_models::Ghost;

struct Counter {
count: i64,
seen: Ghost<Seq<Int>>,
}

impl thrust_models::Model for Counter {
type Ty = (Int, Seq<Int>);
}

#[thrust_macros::context]
impl Counter {
#[thrust_macros::requires((*self).1.len() == (*self).0)]
#[thrust_macros::ensures((!self).1.len() == (!self).0)]
fn record(&mut self, x: i64) {
self.count += 1;
self.seen = thrust_macros::ghost!(|self: &mut Self, x: i64| -> Seq<Int> { (*self).1 });
}
}

fn main() {}
19 changes: 19 additions & 0 deletions tests/ui/pass/ghost_generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//@check-pass
//@compile-flags: -C debug-assertions=off -A unused-variables

use thrust_models::Ghost;

#[thrust_macros::requires(g == v)]
fn expect_same<T>(g: Ghost<T>, v: T) {
let _ = g;
}

#[thrust_macros::context]
fn record<T: Copy>(a: T, b: T) {
let g = thrust_macros::ghost!(|a: T| -> T { a });
expect_same(g, a);
}

fn main() {
record(3_i64, 5_i64);
}
27 changes: 27 additions & 0 deletions tests/ui/pass/ghost_self.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//@check-pass
//@compile-flags: -C debug-assertions=off -A unused-variables

use thrust_models::model::{Int, Seq};
use thrust_models::Ghost;

struct Counter {
count: i64,
seen: Ghost<Seq<Int>>,
}

impl thrust_models::Model for Counter {
type Ty = (Int, Seq<Int>);
}

#[thrust_macros::context]
impl Counter {
#[thrust_macros::requires((*self).1.len() == (*self).0)]
#[thrust_macros::ensures((!self).1.len() == (!self).0)]
fn record(&mut self, x: i64) {
self.count += 1;
self.seen =
thrust_macros::ghost!(|self: &mut Self, x: i64| -> Seq<Int> { (*self).1.push(x) });
}
}

fn main() {}
10 changes: 6 additions & 4 deletions thrust-macros/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
//!
//! Makes the enclosing context available to the specifications written inside an item:
//!
//! - On a function, every `thrust_macros::invariant!(...)` in the body is rewritten into
//! its context-carrying counterpart, carrying the host signature (and, in a method, the
//! enclosing `impl`/`trait` header), so an invariant may refer to generic- and
//! `Self`-typed variables that the standalone macro cannot see.
//! - On a function, every `thrust_macros::invariant!(...)` and
//! `thrust_macros::ghost!(...)` in the body is rewritten into its context-carrying
//! counterpart, carrying the host signature (and, in a method, the enclosing
//! `impl`/`trait` header), so a formula may refer to generic- and `Self`-typed
//! variables that the standalone macros cannot see.
//! - On an `impl`/`trait`, each method is stamped with the enclosing header so
//! method-level `requires`/`ensures` can recover the outer generics, and its body is
//! threaded as above.
Expand Down Expand Up @@ -164,6 +165,7 @@ fn context_carrying_form(path: &syn::Path) -> Option<syn::Path> {
// TODO: identify the macro precisely
match path.segments.last()?.ident.to_string().as_str() {
"invariant" => Some(syn::parse_quote!(::thrust_macros::_invariant_with_context)),
"ghost" => Some(syn::parse_quote!(::thrust_macros::_ghost_with_context)),
_ => None,
}
}
Loading