Skip to content

Commit d49896b

Browse files
wrwgclaude
andauthored
[move-prover] Adding behavioral predicates to the parser (#18428)
* [ai] Basic setup for Cursor/Claude agents.md This adds autogenerated configuration files for Claude code and for Cursor. - `CLAUDE.md` which links to `agents.md` - `agents.md` is generarted via Cursor AND Claude - Moreover, for safety and privacy, an autogenerated `.cursorignore`. The process how `agents.md` has been created was as follows: - Use claude command `/init` to created `CLAUDE.md` - Ask cursor 'generate `agents.md` by extending `CLAUDE.md`' - Remove original CLAUDE.md and symlink to agents This little trick was recommended by Gemini. Moving forward, we can ask Claude or Cursor to further extend this file with specific instructions. * Separating files. * [move-prover] Adding behavioral predicates to the parser This adds parsing support for behavioral predicates for function values in specifications. These predicates allow reasoning about the behavior of function-typed parameters: - `requires_of<f>(args)` - precondition of function `f` - `aborts_of<f>(args)` - aborts condition of function `f` - `ensures_of<f>(args)` - postcondition of function `f` - `modifies_of<f>(args)` - modify clauses of function `f` State labels can be used to bind pre/post states: - `S@ensures_of<f>(args)` - with pre-state label - `ensures_of<f>(args)@R` - with post-state label - `S@ensures_of<f>(args)@R` - with both labels The syntax is parsed and passed through to the expansion AST. The model builder currently produces an error indicating the feature is not yet fully supported. Tests are added for: - Valid syntax with model builder error - Parser error for invalid syntax - Spec-context-only restriction Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8fc3fd0 commit d49896b

12 files changed

Lines changed: 375 additions & 3 deletions

File tree

third_party/move/move-compiler-v2/legacy-move-compiler/src/expansion/ast.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
use crate::{
66
expansion::translate::is_valid_struct_constant_or_schema_name,
77
parser::ast::{
8-
self as P, Ability, Ability_, BinOp, CallKind, ConstantName, Field, FunctionName, Label,
9-
LambdaCaptureKind, ModuleName, QuantKind, SpecApplyPattern, StructName, UnaryOp, UseDecl,
10-
Var, VariantName, ENTRY_MODIFIER,
8+
self as P, Ability, Ability_, BehaviorKind, BinOp, CallKind, ConstantName, Field,
9+
FunctionName, Label, LambdaCaptureKind, ModuleName, QuantKind, SpecApplyPattern,
10+
StructName, UnaryOp, UseDecl, Var, VariantName, ENTRY_MODIFIER,
1111
},
1212
shared::{
1313
ast_debug::*,
@@ -542,6 +542,19 @@ pub enum Exp_ {
542542
Option<Box<Exp>>,
543543
Box<Exp>,
544544
), // spec only
545+
// Behavior predicate for function values in specifications:
546+
// [pre_label@]requires_of<f[<T1, ..., Tn>]>(args)[@post_label]
547+
// [pre_label@]aborts_of<f[<T1, ..., Tn>]>(args)[@post_label]
548+
// [pre_label@]ensures_of<f[<T1, ..., Tn>]>(args)[@post_label]
549+
// [pre_label@]modifies_of<f[<T1, ..., Tn>]>(args)[@post_label]
550+
Behavior(
551+
BehaviorKind,
552+
Option<Label>, // pre-state label
553+
ModuleAccess, // function name
554+
Option<Vec<Type>>, // optional type instantiation
555+
Spanned<Vec<Exp>>, // arguments
556+
Option<Label>, // post-state label
557+
), // spec only
545558

546559
Assign(LValueList, Box<Exp>),
547560
FieldMutate(Box<ExpDotted>, Box<Exp>),
@@ -1863,6 +1876,31 @@ impl AstDebug for Exp_ {
18631876
w.write("]");
18641877
}
18651878
},
1879+
E::Behavior(kind, pre_label, fn_name, type_args, sp!(_, args), post_label) => {
1880+
if let Some(label) = pre_label {
1881+
w.write(format!("{}@", label.value().as_str()));
1882+
}
1883+
let kind_str = match kind {
1884+
BehaviorKind::RequiresOf => "requires_of",
1885+
BehaviorKind::AbortsOf => "aborts_of",
1886+
BehaviorKind::EnsuresOf => "ensures_of",
1887+
BehaviorKind::ModifiesOf => "modifies_of",
1888+
};
1889+
w.write(kind_str);
1890+
w.write("<");
1891+
fn_name.ast_debug(w);
1892+
if let Some(tys) = type_args {
1893+
w.write("<");
1894+
w.comma(tys, |w, ty| ty.ast_debug(w));
1895+
w.write(">");
1896+
}
1897+
w.write(">(");
1898+
w.comma(args, |w, e| e.ast_debug(w));
1899+
w.write(")");
1900+
if let Some(label) = post_label {
1901+
w.write(format!("@{}", label.value().as_str()));
1902+
}
1903+
},
18661904
E::UnresolvedError => w.write("_|_"),
18671905
}
18681906
}

third_party/move/move-compiler-v2/legacy-move-compiler/src/expansion/dependency_ordering.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,11 @@ fn exp(context: &mut Context, sp!(_loc, e_): &E::Exp) {
591591
eopt.iter().for_each(|e| exp(context, e));
592592
exp(context, e)
593593
},
594+
E::Behavior(_, _, fn_name, type_args, sp!(_, args), _) => {
595+
module_access(context, fn_name);
596+
types_opt(context, type_args);
597+
args.iter().for_each(|e| exp(context, e))
598+
},
594599
}
595600
}
596601

third_party/move/move-compiler-v2/legacy-move-compiler/src/expansion/translate.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2806,6 +2806,32 @@ fn exp_(context: &mut Context, sp!(loc, pe_): P::Exp) -> E::Exp {
28062806
} = unbound_names;
28072807
EE::Spec(spec_id, unbound_vars, unbound_func_ptrs)
28082808
},
2809+
PE::Behavior(kind, pre_label, fn_name, type_args, sp!(args_loc, args), post_label) => {
2810+
if !context.in_spec_context {
2811+
context.env.add_diag(diag!(
2812+
Syntax::SpecContextRestricted,
2813+
(
2814+
loc,
2815+
"behavior predicate expression only allowed in specifications"
2816+
)
2817+
));
2818+
EE::UnresolvedError
2819+
} else {
2820+
let e_fn_name = name_access_chain(
2821+
context,
2822+
Access::Term,
2823+
fn_name,
2824+
Some(DeprecatedItem::Function),
2825+
);
2826+
let e_type_args = optional_types(context, type_args);
2827+
let e_args = sp(args_loc, exps(context, args));
2828+
if let Some(fn_access) = e_fn_name {
2829+
EE::Behavior(kind, pre_label, fn_access, e_type_args, e_args, post_label)
2830+
} else {
2831+
EE::UnresolvedError
2832+
}
2833+
}
2834+
},
28092835
PE::UnresolvedError => panic!("ICE error should have been thrown"),
28102836
};
28112837
sp(loc, e_)
@@ -3419,6 +3445,15 @@ fn unbound_names_exp(unbound: &mut UnboundNames, sp!(_, e_): &E::Exp) {
34193445
unbound.vars.extend(unbound_vars);
34203446
unbound.func_ptrs.extend(unbound_func_ptrs);
34213447
},
3448+
EE::Behavior(_, _, fn_name, _type_args, sp!(_, args), _) => {
3449+
match &fn_name.value {
3450+
E::ModuleAccess_::Name(n) => {
3451+
unbound.func_ptrs.insert(*n);
3452+
},
3453+
E::ModuleAccess_::ModuleAccess(..) => (),
3454+
}
3455+
unbound_names_exps(unbound, args);
3456+
},
34223457
}
34233458
}
34243459

third_party/move/move-compiler-v2/legacy-move-compiler/src/parser/ast.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,20 @@ pub enum QuantKind_ {
639639
}
640640
pub type QuantKind = Spanned<QuantKind_>;
641641

642+
/// The kind of behavior predicate for function values in specifications.
643+
/// These predicates allow reasoning about the behavior of function-typed parameters.
644+
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
645+
pub enum BehaviorKind {
646+
/// `requires_of<f>(x)` - the precondition of function `f`
647+
RequiresOf,
648+
/// `aborts_of<f>(x)` - the aborts condition of function `f`
649+
AbortsOf,
650+
/// `ensures_of<f>(x, y)` - the postcondition of function `f`
651+
EnsuresOf,
652+
/// `modifies_of<f>(x)` - the modify clauses of function `f`
653+
ModifiesOf,
654+
}
655+
642656
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
643657
pub enum CallKind {
644658
/// Regular function call.
@@ -729,6 +743,20 @@ pub enum Exp_ {
729743
Vec<Vec<Exp>>,
730744
Option<Box<Exp>>,
731745
Box<Exp>,
746+
),
747+
// spec only
748+
// Behavior predicate for function values in specifications:
749+
// [pre_label@]requires_of<f[<T1, ..., Tn>]>(args)[@post_label]
750+
// [pre_label@]aborts_of<f[<T1, ..., Tn>]>(args)[@post_label]
751+
// [pre_label@]ensures_of<f[<T1, ..., Tn>]>(args)[@post_label]
752+
// [pre_label@]modifies_of<f[<T1, ..., Tn>]>(args)[@post_label]
753+
Behavior(
754+
BehaviorKind,
755+
Option<Label>, // pre-state label
756+
NameAccessChain, // function name
757+
Option<Vec<Type>>, // optional type instantiation
758+
Spanned<Vec<Exp>>, // arguments
759+
Option<Label>, // post-state label
732760
), // spec only
733761
// (e1, ..., en)
734762
ExpList(Vec<Exp>),
@@ -2068,6 +2096,31 @@ impl AstDebug for Exp_ {
20682096
s.ast_debug(w);
20692097
w.write("}");
20702098
},
2099+
E::Behavior(kind, pre_label, fn_name, type_args, sp!(_, args), post_label) => {
2100+
if let Some(label) = pre_label {
2101+
w.write(format!("{}@", label.value().as_str()));
2102+
}
2103+
let kind_str = match kind {
2104+
BehaviorKind::RequiresOf => "requires_of",
2105+
BehaviorKind::AbortsOf => "aborts_of",
2106+
BehaviorKind::EnsuresOf => "ensures_of",
2107+
BehaviorKind::ModifiesOf => "modifies_of",
2108+
};
2109+
w.write(kind_str);
2110+
w.write("<");
2111+
fn_name.ast_debug(w);
2112+
if let Some(tys) = type_args {
2113+
w.write("<");
2114+
w.comma(tys, |w, ty| ty.ast_debug(w));
2115+
w.write(">");
2116+
}
2117+
w.write(">(");
2118+
w.comma(args, |w, e| e.ast_debug(w));
2119+
w.write(")");
2120+
if let Some(label) = post_label {
2121+
w.write(format!("@{}", label.value().as_str()));
2122+
}
2123+
},
20712124
E::UnresolvedError => w.write("_|_"),
20722125
}
20732126
}

third_party/move/move-compiler-v2/legacy-move-compiler/src/parser/syntax.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,6 +2010,7 @@ fn parse_exp(context: &mut Context) -> Result<Exp, Box<Diagnostic>> {
20102010
parse_lambda(context, start_loc, LambdaCaptureKind::default(), token)?
20112011
},
20122012
Tok::Identifier if is_quant(context) => parse_quant(context)?,
2013+
Tok::Identifier if is_behavior_predicate(context) => parse_behavior(context)?,
20132014
_ => {
20142015
// This could be either an assignment, operator assignment (e.g., +=), or a binary operator
20152016
// expression, or a cast or test
@@ -2386,6 +2387,106 @@ fn is_quant(context: &mut Context) -> bool {
23862387
}
23872388
}
23882389

2390+
// Returns the BehaviorKind if the given identifier is a behavior predicate keyword.
2391+
fn behavior_kind_from_str(s: &str) -> Option<BehaviorKind> {
2392+
match s {
2393+
"requires_of" => Some(BehaviorKind::RequiresOf),
2394+
"aborts_of" => Some(BehaviorKind::AbortsOf),
2395+
"ensures_of" => Some(BehaviorKind::EnsuresOf),
2396+
"modifies_of" => Some(BehaviorKind::ModifiesOf),
2397+
_ => None,
2398+
}
2399+
}
2400+
2401+
// Check if we're looking at a behavior predicate expression.
2402+
// This can be either:
2403+
// - `identifier @ behavior_keyword < ...` (with pre-state label)
2404+
// - `behavior_keyword < ...` (without pre-state label)
2405+
fn is_behavior_predicate(context: &mut Context) -> bool {
2406+
// Behavior predicates are only available in V2_4 and later
2407+
if context.env.flags().language_version() < LanguageVersion::V2_4 {
2408+
return false;
2409+
}
2410+
if context.tokens.peek() != Tok::Identifier {
2411+
return false;
2412+
}
2413+
2414+
let content = context.tokens.content();
2415+
2416+
// Check for direct behavior keyword followed by `<`
2417+
if behavior_kind_from_str(content).is_some() {
2418+
return matches!(context.tokens.lookahead(), Ok(Tok::Less));
2419+
}
2420+
2421+
// Check for `label @ behavior_keyword < ...` pattern
2422+
match context.tokens.lookahead2() {
2423+
Ok((Tok::AtSign, Tok::Identifier)) => {
2424+
// Need to check the third token - it should be a behavior keyword
2425+
// We can't easily lookahead 3, so we accept this pattern and validate during parse
2426+
true
2427+
},
2428+
_ => false,
2429+
}
2430+
}
2431+
2432+
// Parses a behavior predicate expression:
2433+
// [pre_label@]behavior_kind<fn_exp>(args)[@post_label]
2434+
fn parse_behavior(context: &mut Context) -> Result<Exp_, Box<Diagnostic>> {
2435+
// Parse optional pre-state label
2436+
let pre_label = if context.tokens.peek() == Tok::Identifier
2437+
&& matches!(context.tokens.lookahead(), Ok(Tok::AtSign))
2438+
{
2439+
let label_name = parse_identifier(context)?;
2440+
let label = Label(label_name);
2441+
consume_token(context.tokens, Tok::AtSign)?;
2442+
Some(label)
2443+
} else {
2444+
None
2445+
};
2446+
2447+
// Parse behavior kind
2448+
let kind_content = context.tokens.content();
2449+
let kind = behavior_kind_from_str(kind_content).ok_or_else(|| {
2450+
Box::new(
2451+
diag!(
2452+
Syntax::UnexpectedToken,
2453+
(
2454+
current_token_loc(context.tokens),
2455+
format!(
2456+
"expected a behavior predicate keyword (requires_of, aborts_of, ensures_of, modifies_of), found '{}'",
2457+
kind_content
2458+
)
2459+
)
2460+
)
2461+
)
2462+
})?;
2463+
context.tokens.advance()?;
2464+
2465+
// Parse `<` fn_name [type_args] `>`
2466+
// We parse a name access chain (not a full expression) to avoid ambiguity with `>`
2467+
consume_token(context.tokens, Tok::Less)?;
2468+
let fn_name = parse_name_access_chain(context, false, || "a function name")?;
2469+
// Parse optional type arguments for the function
2470+
let type_args = parse_optional_type_args(context)?;
2471+
consume_token(context.tokens, Tok::Greater)?;
2472+
2473+
// Parse `(` args `)`
2474+
let args = parse_call_args(context)?;
2475+
2476+
// Parse optional post-state label
2477+
let post_label = if context.tokens.peek() == Tok::AtSign {
2478+
context.tokens.advance()?;
2479+
let label_name = parse_identifier(context)?;
2480+
Some(Label(label_name))
2481+
} else {
2482+
None
2483+
};
2484+
2485+
Ok(Exp_::Behavior(
2486+
kind, pre_label, fn_name, type_args, args, post_label,
2487+
))
2488+
}
2489+
23892490
// Parses a quantifier expressions, assuming is_quant(context) is true.
23902491
//
23912492
// <Quantifier> =
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
Diagnostics:
3+
error: behavior predicates for function values (requires_of, aborts_of, ensures_of, modifies_of) are not yet supported in the model
4+
┌─ tests/checking-lang-v2.4/specs/behavior_predicates_err.move:14:17
5+
6+
14 │ ensures requires_of<f>(x, y);
7+
│ ^^^^^^^^^^^^^^^^^^^^
8+
9+
error: behavior predicates for function values (requires_of, aborts_of, ensures_of, modifies_of) are not yet supported in the model
10+
┌─ tests/checking-lang-v2.4/specs/behavior_predicates_err.move:17:17
11+
12+
17 │ ensures ensures_of<f>(x, y, result);
13+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: behavior predicates for function values (requires_of, aborts_of, ensures_of, modifies_of) are not yet supported in the model
16+
┌─ tests/checking-lang-v2.4/specs/behavior_predicates_err.move:20:19
17+
18+
20 │ aborts_if aborts_of<f>(x, y);
19+
│ ^^^^^^^^^^^^^^^^^^
20+
21+
error: behavior predicates for function values (requires_of, aborts_of, ensures_of, modifies_of) are not yet supported in the model
22+
┌─ tests/checking-lang-v2.4/specs/behavior_predicates_err.move:23:18
23+
24+
23 │ modifies modifies_of<f>(x);
25+
│ ^^^^^^^^^^^^^^^^^
26+
27+
error: global resource access expected
28+
┌─ tests/checking-lang-v2.4/specs/behavior_predicates_err.move:23:18
29+
30+
23 │ modifies modifies_of<f>(x);
31+
│ ^^^^^^^^^^^^^^^^^
32+
33+
error: behavior predicates for function values (requires_of, aborts_of, ensures_of, modifies_of) are not yet supported in the model
34+
┌─ tests/checking-lang-v2.4/specs/behavior_predicates_err.move:33:17
35+
36+
33 │ ensures old@ensures_of<f>(x, result);
37+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38+
39+
error: behavior predicates for function values (requires_of, aborts_of, ensures_of, modifies_of) are not yet supported in the model
40+
┌─ tests/checking-lang-v2.4/specs/behavior_predicates_err.move:36:17
41+
42+
36 │ ensures ensures_of<f>(x, result)@post;
43+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44+
45+
error: behavior predicates for function values (requires_of, aborts_of, ensures_of, modifies_of) are not yet supported in the model
46+
┌─ tests/checking-lang-v2.4/specs/behavior_predicates_err.move:39:17
47+
48+
39 │ ensures pre@ensures_of<f>(x, result)@post;
49+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)