diff --git a/SPEC_FORMAT.md b/SPEC_FORMAT.md index fffb1c22007c4d9bcad37ee1bdf2dc1e1b4893c6..25ac7ca0124f6f23c3be220cd5bbc88c3c26f34f 100644 --- a/SPEC_FORMAT.md +++ b/SPEC_FORMAT.md @@ -37,6 +37,7 @@ Finally, the `returns` clause specifies a refinement (and optionally, a type) fo | `ensures` | specify a postcondition | multiple | `#[rr::ensures("x > y")]` | | `exists` | specify an existential for the postcondition | multiple | `#[rr::exists("x" : "Z")]` | | `observe` | shortcut for specifying observations on ghost variables | single | `#[rr::observe("γ": "x + 2")]` | +| `context` | add an unnamed implicit parameter to the context | single | `#[rr::context("ghost_varG Σ Z")]` | There are further attributes that influence the proof-checking behaviour: | Keyword | Purpose | Properties | Example | @@ -54,6 +55,7 @@ The following attributes are also available on impls and then influence all func | `trust_me` | generate and type-check the specifications and code, but do not generate proofs | none | `#[rr::trust_me]` | | `only_spec` | only generate and type-check the specifications, but do not generate the code | none | `#[rr::only_spec]` | | `skip` | ignore this block completely | none | `#[rr::skip]` | +| `context` | add an unnamed implicit parameter to the context | single | `#[rr::context("ghost_varG Σ Z")]` | ## Struct attributes @@ -65,6 +67,7 @@ The following attributes are also available on impls and then influence all func | `refines` | Optionally specify the refinement of the raw struct type (can only be used when no refinements are specified via `field` attributes on the struct's fields) | single | `#[rr::refines("-[a; b]")]` | | `mode` | Specifies the sharing mode of the defined type: either `plain` (the default, invariants are shared on a best-effort basis for the sharing invariant) or `persistent` (all invariants need to be shareable) | `#[rr::mode("plain")]` | | `export_as` | influence the exported path in the generated RefinedRust library interface for import in other proofs | a Rust path | `#[rr::export_as(std::vec::Vec)]` | +| `context` | add an unnamed implicit parameter to the context in which the invariant is declared | single | `#[rr::context("ghost_varG Σ Z")]` | Invariants on structs can be prefixed by `#own` or `#shr` to only include the invariant in the struct's ownership or sharing predicate, respectively, e.g.: `#[rr::invariant(#own "freeable l len")]`. diff --git a/rr_frontend/radium/src/code.rs b/rr_frontend/radium/src/code.rs index 9ea7051b5672b9c427cece7ea936e7c3753c01fa..8d5d25982667b5253f7bcc882ed8076ad93573fd 100644 --- a/rr_frontend/radium/src/code.rs +++ b/rr_frontend/radium/src/code.rs @@ -986,12 +986,14 @@ impl<'def> Function<'def> { // write coq parameters if !self.spec.coq_params.is_empty() || !self.other_functions.is_empty() { write!(f, "∀ ")?; - for (n, ty, imp) in self.spec.coq_params.iter() { - if !imp { - write!(f, "({n} : {ty}) ")?; + for param in self.spec.coq_params.iter() { + // TODO use CoqParam::format instead + if param.implicit { + write!(f, "`({})", param.ty)?; } else { - write!(f, "`({ty}) ")?; + write!(f, "({} : {})", param.name, param.ty)?; } + write!(f, " ")?; } // assume locations for other functions @@ -1060,9 +1062,9 @@ impl<'def> Function<'def> { write!(f, "] (type_of_{} ", self.name())?; // write type args (passed to the type definition) - for (n, _, imp) in self.spec.coq_params.iter() { - if !imp { - write!(f, "{} ", n)?; + for param in self.spec.coq_params.iter() { + if !param.implicit { + write!(f, "{} ", param.name)?; } } @@ -1085,9 +1087,9 @@ impl<'def> Function<'def> { // intros spec params if !self.spec.coq_params.is_empty() { write!(f, "intros")?; - for (n, _, imp) in self.spec.coq_params.iter() { - if !imp { - write!(f, " {n}")?; + for param in self.spec.coq_params.iter() { + if !param.implicit { + write!(f, " {}", param.name)?; } else { write!(f, " ?")?; } diff --git a/rr_frontend/radium/src/specs.rs b/rr_frontend/radium/src/specs.rs index 3f5eeb9e1659f2eae50081ed0896d748054dcf18..e1f84f5e6e199561e08b5e15472c7f0ac2b5475e 100644 --- a/rr_frontend/radium/src/specs.rs +++ b/rr_frontend/radium/src/specs.rs @@ -345,6 +345,10 @@ pub struct TypeAnnotMeta { } impl TypeAnnotMeta { + pub fn is_empty(&self) -> bool { + self.escaped_lfts.is_empty() && self.escaped_tyvars.is_empty() + } + pub fn empty() -> TypeAnnotMeta { TypeAnnotMeta { escaped_lfts: HashSet::new(), @@ -798,6 +802,8 @@ pub struct InvariantSpec { /// the specification of the abstracted refinement under a context where rfn_pat is bound abstracted_refinement: Option<CoqPattern>, // TODO add stuff for non-atomic/atomic invariants + /// name, type, implicit or not + coq_params: Vec<CoqParam>, } impl InvariantSpec { @@ -811,6 +817,7 @@ impl InvariantSpec { invariants: Vec<(IProp, InvariantMode)>, ty_own_invariants: Vec<TyOwnSpec>, abstracted_refinement: Option<CoqPattern>, + coq_params: Vec<CoqParam>, ) -> Self { match flags { InvariantSpecFlags::Persistent => { @@ -830,6 +837,7 @@ impl InvariantSpec { invariants, ty_own_invariants, abstracted_refinement, + coq_params, } } @@ -1091,7 +1099,17 @@ impl InvariantSpec { } out.push_str(".\n"); } - out.push_str("\n"); + + // write coq parameters + write!(out, "{} (* Additional parameters *)\n", indent).unwrap(); + if !self.coq_params.is_empty() { + write!(out, "{}Context", indent).unwrap(); + for param in self.coq_params.iter() { + write!(out, " {}", param).unwrap(); + } + write!(out, ".\n").unwrap(); + } + write!(out, "\n").unwrap(); // get the applied base_rfn_type let rfn_instantiations: Vec<String> = @@ -2545,13 +2563,45 @@ pub struct LoopSpec { pub func_predicate: IPropPredicate, } +#[derive(Clone, Debug, PartialEq)] +pub struct CoqParam { + pub name: CoqName, + pub ty: CoqType, + pub implicit: bool, +} + +impl CoqParam { + pub fn new(name: CoqName, ty: CoqType, implicit: bool) -> Self { + Self { name, ty, implicit } + } + + pub fn format(&self, f: &mut Formatter, make_implicits: bool) -> fmt::Result { + if self.implicit { + if make_implicits { + write!(f, "`{{{}}}", self.ty) + } + else { + write!(f, "`({})", self.ty) + } + } else { + write!(f, "({} : {})", self.name, self.ty) + } + } +} + +impl Display for CoqParam { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.format(f, true) + } +} + /** * A Caesium function specification. */ #[derive(Debug)] pub struct FunctionSpec<'def> { /// Coq-level parameters the typing statement needs (bool is for implicit or not) - pub coq_params: Vec<(CoqName, CoqType, bool)>, + pub coq_params: Vec<CoqParam>, /// Function name pub function_name: String, /// The name of the spec @@ -2598,15 +2648,11 @@ impl<'def> FunctionSpec<'def> { if self.coq_params.len() == 0 { } else { let mut need_sep = false; - for (name, t, implicit) in self.coq_params.iter() { + for param in self.coq_params.iter() { if need_sep { out.push_str(" "); } - if *implicit { - out.push_str(format!("`{{{}}}", t).as_str()); - } else { - out.push_str(format!("({} : {})", name, t).as_str()); - } + out.push_str(format!("{}", param).as_str()); need_sep = true; } } @@ -2712,7 +2758,8 @@ impl<'def> Display for FunctionSpec<'def> { #[derive(Debug)] pub struct FunctionSpecBuilder<'def> { /// Coq-level parameters the typing statement needs, bool is true if implicit - coq_params: Vec<(CoqName, CoqType, bool)>, + coq_params: Vec<CoqParam>, + late_coq_params: Vec<CoqParam>, lifetimes: Vec<Lft>, params: Vec<(CoqName, CoqType)>, @@ -2734,6 +2781,7 @@ impl<'def> FunctionSpecBuilder<'def> { pub fn new() -> Self { Self { coq_params: Vec::new(), + late_coq_params: Vec::new(), lifetimes: Vec::new(), params: Vec::new(), ty_params: Vec::new(), @@ -2810,16 +2858,28 @@ impl<'def> FunctionSpecBuilder<'def> { Ok(()) } - /// Add a Coq-level param. + /// Add a Coq-level param that comes before the type parameters. pub fn add_coq_param(&mut self, name: CoqName, t: CoqType, implicit: bool) -> Result<(), String> { self.ensure_coq_not_bound(&name)?; - self.coq_params.push((name, t, implicit)); + self.coq_params.push(CoqParam::new(name, t, implicit)); + Ok(()) + } + + /// Add a Coq-level param that comes after the type parameters. + pub fn add_late_coq_param(&mut self, name: CoqName, t: CoqType, implicit: bool) -> Result<(), String> { + self.ensure_coq_not_bound(&name)?; + self.late_coq_params.push(CoqParam::new(name, t, implicit)); Ok(()) } /// Variant of [add_coq_param] that can never fail and makes the parameter anonymous. pub fn add_unnamed_coq_param(&mut self, t: CoqType, implicit: bool) { - self.coq_params.push((CoqName::Unnamed, t, implicit)); + self.coq_params.push(CoqParam::new(CoqName::Unnamed, t, implicit)); + } + + /// Variant of [add_late_coq_param] that can never fail and makes the parameter anonymous. + pub fn add_unnamed_late_coq_param(&mut self, t: CoqType, implicit: bool) { + self.late_coq_params.push(CoqParam::new(CoqName::Unnamed, t, implicit)); } /// Add a new universal lifetime constraint. @@ -2901,7 +2961,8 @@ impl<'def> FunctionSpecBuilder<'def> { /// `name` is the designated name of the function. /// `code_params` are the parameters the code body needs to be provided (e.g., locations of /// other functions). - pub fn into_function_spec(self, name: &str, spec_name: &str) -> FunctionSpec<'def> { + pub fn into_function_spec(mut self, name: &str, spec_name: &str) -> FunctionSpec<'def> { + self.coq_params.extend(self.late_coq_params.into_iter()); let ret = self.ret.unwrap_or(TypeWithRef::make_unit()); FunctionSpec { function_name: name.to_string(), diff --git a/rr_frontend/translation/src/function_body.rs b/rr_frontend/translation/src/function_body.rs index a6e0eac34311cd76e02ec404c1a3c822ee652917..901de3686c154757c7ec255280d4561f527be156 100644 --- a/rr_frontend/translation/src/function_body.rs +++ b/rr_frontend/translation/src/function_body.rs @@ -185,7 +185,7 @@ pub struct FunctionTranslator<'a, 'def, 'tcx> { /// registry of other procedures procedure_registry: &'a ProcedureScope<'def>, /// attributes on this function - attrs: &'a [Attribute], + attrs: &'a [&'a rustc_ast::ast::AttrItem], /// polonius info for this function info: &'a PoloniusInfo<'a, 'tcx>, /// translator for types @@ -204,7 +204,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> { env: &'def Environment<'tcx>, meta: ProcedureMeta, proc: Procedure<'tcx>, - attrs: &'a [Attribute], + attrs: &'a [&'a rustc_ast::ast::AttrItem], ty_translator: &'def TypeTranslator<'def, 'tcx>, proc_registry: &'a ProcedureScope<'def>, ) -> Result<Self, TranslationError> { @@ -410,8 +410,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> { /// Parse and process attributes of this function. fn process_attrs(&mut self) -> Result<(), TranslationError> { - let a = self.attrs; - let v = crate::utils::filter_tool_attrs(a); + let v = self.attrs; let inputs = &self.inputs; let output = &self.output; @@ -436,7 +435,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> { ty_translator.intern_literal(lit) }); parser - .parse_function_spec(v.as_slice(), &mut self.translated_fn) + .parse_function_spec(v, &mut self.translated_fn) .map_err(|e| TranslationError::AttributeError(e))?; Ok(()) }, @@ -642,7 +641,7 @@ struct BodyTranslator<'a, 'def, 'tcx> { /// registry of other procedures procedure_registry: &'a ProcedureScope<'def>, /// attributes on this function - attrs: &'a [Attribute], + attrs: &'a [&'a rustc_ast::ast::AttrItem], /// polonius info for this function info: &'a PoloniusInfo<'a, 'tcx>, /// local lifetimes: the LHS is the lifetime name, the RHS are the super lifetimes diff --git a/rr_frontend/translation/src/lib.rs b/rr_frontend/translation/src/lib.rs index d66d99dd513d721310c1e9a587ee28e1359ade26..c67475de4740062a91ca155632c2a7b87bfea9ab 100644 --- a/rr_frontend/translation/src/lib.rs +++ b/rr_frontend/translation/src/lib.rs @@ -829,6 +829,17 @@ fn register_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) { } } +fn propagate_attr_from_impl(it: &rustc_ast::ast::AttrItem) -> bool { + if let Some(ident) = it.path.segments.get(1) { + match ident.ident.as_str() { + "context" => true, + _ => false, + } + } else { + false + } +} + /// Translate functions of the crate, assuming they were previously registered. fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) { for &f in vcx.functions { @@ -837,6 +848,13 @@ fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) { let meta = vcx.procedure_registry.lookup_function(&f.to_def_id()).unwrap(); let attrs = vcx.env.get_attributes(f.to_def_id()); + let mut filtered_attrs = crate::utils::filter_tool_attrs(attrs); + // also add selected attributes from the surrounding impl + if let Some(impl_did) = vcx.env.tcx().impl_of_method(f.to_def_id()) { + let impl_attrs = vcx.env.get_attributes(impl_did); + let filtered_impl_attrs = crate::utils::filter_tool_attrs(impl_attrs); + filtered_attrs.extend(filtered_impl_attrs.into_iter().filter(|x| propagate_attr_from_impl(x))); + } let mode = meta.get_mode(); if mode.is_shim() { @@ -852,7 +870,7 @@ fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) { &vcx.env, meta, proc, - attrs, + &filtered_attrs, &vcx.type_translator, &vcx.procedure_registry, ); diff --git a/rr_frontend/translation/src/spec_parsers/parse_utils.rs b/rr_frontend/translation/src/spec_parsers/parse_utils.rs index 9897cdff92ff6e1088eb018aacc7950076eabf60..f6cb72c64edd06884d1d7ac14d0f178f85ba8b90 100644 --- a/rr_frontend/translation/src/spec_parsers/parse_utils.rs +++ b/rr_frontend/translation/src/spec_parsers/parse_utils.rs @@ -204,6 +204,33 @@ impl<'a, U> Parse<U> for CoqPath { } } +/// Parse an assumed Coq context item, e.g. +/// `"`{ghost_mapG Σ Z Z}"`. +#[derive(Debug)] +pub struct RRCoqContextItem { + pub item: String, + /// true if this should be put at the end of the dependency list, e.g. if it depends on type + /// parameters + pub at_end: bool, +} +impl<'a> parse::Parse<ParseMeta<'a>> for RRCoqContextItem { + fn parse(input: parse::ParseStream, meta: &ParseMeta<'a>) -> parse::ParseResult<Self> { + let item: parse::LitStr = input.parse(meta)?; + let (item_str, annot_meta) = process_coq_literal(&item.value(), *meta); + + let mut at_end = false; + if !annot_meta.is_empty() { + at_end = true; + } + + //annot_meta. + Ok(RRCoqContextItem { + item: item_str, + at_end, + }) + } +} + pub type ParseMeta<'a> = (&'a [specs::LiteralTyParam], &'a [(Option<String>, specs::Lft)]); /// Processes a literal Coq term annotated via an attribute. diff --git a/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs b/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs index a2973426b1496f9ea7063b6349fcc16e25d7c222..add95b4a8a624307ce2397d7fd591424144574fe 100644 --- a/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs +++ b/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs @@ -23,6 +23,7 @@ pub trait InvariantSpecParser { /// - rr::exists /// - rr::invariant /// - rr::refines for the inner refinement + /// - rr::context for context items that are required to be available in the definition /// /// Returns whether a Boolean stating whether a rr::refines attribute was included. fn parse_invariant_spec<'a>( @@ -203,6 +204,8 @@ impl InvariantSpecParser for VerboseInvariantSpecParser { // use Plain as the default let mut inv_flags = specs::InvariantSpecFlags::Plain; + let mut params: Vec<specs::CoqParam> = Vec::new(); + for &it in attrs.iter() { let ref path_segs = it.path.segments; let ref args = it.args; @@ -256,6 +259,14 @@ impl InvariantSpecParser for VerboseInvariantSpecParser { abstracted_refinement = Some(term.to_string()); } }, + "context" => { + let param = RRCoqContextItem::parse(&buffer, &meta).map_err(str_err)?; + params.push(specs::CoqParam::new( + specs::CoqName::Unnamed, + specs::CoqType::Literal(param.item), + true, + )); + }, _ => { //skip, this may be part of an enum spec () @@ -276,6 +287,7 @@ impl InvariantSpecParser for VerboseInvariantSpecParser { invariants, type_invariants, abstracted_refinement, + params, ); Ok((spec, refinement_included)) diff --git a/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs b/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs index df4900be8813c8d95419daeb545eaec1c602a443..9aaba8831581e9ec0287d9738c8707df4fec8c38 100644 --- a/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs +++ b/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs @@ -23,22 +23,6 @@ pub trait FunctionSpecParser<'def> { ) -> Result<(), String>; } -/// Parse an assumed Coq context item, e.g. -/// `"`{ghost_mapG Σ Z Z}"`. -#[derive(Debug)] -struct RRCoqContextItem { - item: String, -} -impl<U> parse::Parse<U> for RRCoqContextItem -where - U: ?Sized, -{ - fn parse(input: parse::ParseStream, meta: &U) -> parse::ParseResult<Self> { - let item: parse::LitStr = input.parse(meta)?; - Ok(RRCoqContextItem { item: item.value() }) - } -} - /// A sequence of refinements with optional types, e.g. /// `"42" @ "int i32", "1337", "(a, b)"` #[derive(Debug)] @@ -316,12 +300,19 @@ where }, "context" => { let context_item = RRCoqContextItem::parse(&buffer, &meta).map_err(str_err)?; - builder.spec.add_coq_param( - specs::CoqName::Unnamed, - specs::CoqType::Literal(context_item.item), - true, - )?; - //spec.add_coq_context_item(context_item.item); + if context_item.at_end { + builder.spec.add_late_coq_param( + specs::CoqName::Unnamed, + specs::CoqType::Literal(context_item.item), + true, + )?; + } else { + builder.spec.add_coq_param( + specs::CoqName::Unnamed, + specs::CoqType::Literal(context_item.item), + true, + )?; + } }, _ => { info!("ignoring function attribute: {:?}", args);