diff --git a/cli/tests/snapshot/inputs/errors/validator_custom_error.ncl b/cli/tests/snapshot/inputs/errors/validator_custom_error.ncl new file mode 100644 index 0000000000..0647ecfa57 --- /dev/null +++ b/cli/tests/snapshot/inputs/errors/validator_custom_error.ncl @@ -0,0 +1,12 @@ +# capture = 'stderr' +# command = ['eval'] +let Is42 = std.contract.from_validator (fun value => + if value == 42 then + 'Ok value + else + 'Error { + message = "Value must be 42", + notes = ["This is a first custom note", "This is a second custom note"] + } +) in +43 | Is42 diff --git a/cli/tests/snapshot/snapshots/snapshot__eval_stderr_validator_custom_error.ncl.snap b/cli/tests/snapshot/snapshots/snapshot__eval_stderr_validator_custom_error.ncl.snap new file mode 100644 index 0000000000..28f4f4360c --- /dev/null +++ b/cli/tests/snapshot/snapshots/snapshot__eval_stderr_validator_custom_error.ncl.snap @@ -0,0 +1,15 @@ +--- +source: cli/tests/snapshot/main.rs +expression: err +--- +error: contract broken by a value + Value must be 42 + ┌─ [INPUTS_PATH]/errors/validator_custom_error.ncl:12:1 + │ +12 │ 43 | Is42 + │ ^^ ---- expected type + │ │ + │ applied to this expression + │ + = This is a first custom note + = This is a second custom note diff --git a/core/src/eval/operation.rs b/core/src/eval/operation.rs index a9e0e88e92..ae787df58a 100644 --- a/core/src/eval/operation.rs +++ b/core/src/eval/operation.rs @@ -1214,6 +1214,25 @@ impl VirtualMachine { )) } } + UnaryOp::ContractFromValidator => { + if matches!(&*t, Term::Fun(..) | Term::Match(_)) { + Ok(Closure { + body: RichTerm::new( + Term::CustomContract(CustomContract::Validator(RichTerm { + term: t, + pos, + })), + pos, + ), + env, + }) + } else { + Err(mk_type_error!( + "contract/from_validator", + "Function or MatchExpression" + )) + } + } UnaryOp::ContractCustom => { if matches!(&*t, Term::Fun(..) | Term::Match(_)) { Ok(Closure { @@ -1588,6 +1607,11 @@ impl VirtualMachine { .with_pos(pos1), env: env1, }), + Term::CustomContract(CustomContract::Validator(validator)) => Ok(Closure { + body: mk_app!(internals::validator_to_ctr(), validator.clone()) + .with_pos(pos1), + env: env1, + }), Term::Record(..) => { let closurized = RichTerm { term: t1, diff --git a/core/src/parser/grammar.lalrpop b/core/src/parser/grammar.lalrpop index 8eb00a4d74..a937d5fd36 100644 --- a/core/src/parser/grammar.lalrpop +++ b/core/src/parser/grammar.lalrpop @@ -1082,6 +1082,7 @@ UOp: UnaryOp = { "label/go_array" => UnaryOp::LabelGoArray, "label/go_dict" => UnaryOp::LabelGoDict, "contract/from_predicate" => UnaryOp::ContractFromPredicate, + "contract/from_validator" => UnaryOp::ContractFromValidator, "contract/custom" => UnaryOp::ContractCustom, "enum/embed" => UnaryOp::EnumEmbed(<>), "array/map" => UnaryOp::ArrayMap, @@ -1515,6 +1516,7 @@ extern { "contract/array_lazy_app" => Token::Normal(NormalToken::ContractArrayLazyApp), "contract/record_lazy_app" => Token::Normal(NormalToken::ContractRecordLazyApp), "contract/from_predicate" => Token::Normal(NormalToken::ContractFromPredicate), + "contract/from_validator" => Token::Normal(NormalToken::ContractFromValidator), "contract/custom" => Token::Normal(NormalToken::ContractCustom), "op force" => Token::Normal(NormalToken::OpForce), "blame" => Token::Normal(NormalToken::Blame), diff --git a/core/src/parser/lexer.rs b/core/src/parser/lexer.rs index 94c0c7daff..b0bbae2e69 100644 --- a/core/src/parser/lexer.rs +++ b/core/src/parser/lexer.rs @@ -202,6 +202,8 @@ pub enum NormalToken<'input> { ContractRecordLazyApp, #[token("%contract/from_predicate%")] ContractFromPredicate, + #[token("%contract/from_validator%")] + ContractFromValidator, #[token("%contract/custom%")] ContractCustom, #[token("%blame%")] diff --git a/core/src/pretty.rs b/core/src/pretty.rs index 9c4cad7a6f..2bb5175e05 100644 --- a/core/src/pretty.rs +++ b/core/src/pretty.rs @@ -142,7 +142,7 @@ fn needs_parens_in_type_pos(typ: &Type) -> bool { term.as_ref(), Term::Fun(..) | Term::FunPattern(..) - | Term::CustomContract(CustomContract::Predicate(..)) + | Term::CustomContract(_) | Term::Let(..) | Term::LetPattern(..) | Term::Op1(UnaryOp::IfThenElse, _) @@ -821,22 +821,28 @@ where Str(v) => allocator.escaped_string(v).double_quotes(), StrChunks(chunks) => allocator.chunks(chunks, StringRenderStyle::Multiline), Fun(id, body) => allocator.function(allocator.as_string(id), body), - CustomContract(ContractNode::PartialIdentity(ctr)) => docs![ - allocator, - "%contract/custom%", - docs![allocator, allocator.line(), ctr.pretty(allocator).parens()] + // Format this as the primop application ` `. + CustomContract(contract_node) => { + let (constructor, contract) = match contract_node { + ContractNode::Predicate(p) => ("%contract/from_predicate%", p), + ContractNode::Validator(v) => ("%contract/from_validator%", v), + ContractNode::PartialIdentity(pid) => ("%contract/custom%", pid), + }; + + docs![ + allocator, + constructor, + docs![ + allocator, + allocator.line(), + contract.pretty(allocator).parens() + ] .nest(2) .group() - ], + ] + } FunPattern(pat, body) => allocator.function(allocator.pat_with_parens(pat), body), // Format this as the application `std.contract.from_predicate `. - CustomContract(ContractNode::Predicate(pred)) => docs![ - allocator, - "%contract/from_predicate%", - docs![allocator, allocator.line(), pred.pretty(allocator).parens()] - .nest(2) - .group() - ], Lbl(_lbl) => allocator.text("%