From 6bf4df6f1a340ec0a2957ffe1d8c22156150c373 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 7 Jul 2025 11:18:11 +0300 Subject: [PATCH 01/86] feat(annotations): initial parser crate --- Cargo.lock | 31 +++ Cargo.toml | 1 + compiler/formal_verification/Cargo.toml | 33 +++ compiler/formal_verification/src/lib.rs | 1 + compiler/formal_verification/src/parse/mod.rs | 192 ++++++++++++++++++ 5 files changed, 258 insertions(+) create mode 100644 compiler/formal_verification/Cargo.toml create mode 100644 compiler/formal_verification/src/lib.rs create mode 100644 compiler/formal_verification/src/parse/mod.rs diff --git a/Cargo.lock b/Cargo.lock index fd609f8758a..b7d61de8feb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1942,6 +1942,28 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "formal_verification" +version = "1.0.0-beta.7" +dependencies = [ + "base64", + "bn254_blackbox_solver", + "cfg-if", + "function_name", + "insta", + "nom", + "num-bigint 0.4.6", + "num-traits", + "proptest", + "proptest-derive", + "serde", + "serde_json", + "strum 0.24.1", + "strum_macros 0.24.3", + "tracing", + "vir", +] + [[package]] name = "fs2" version = "0.4.3" @@ -3930,6 +3952,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index f5744673690..972b2e785ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "compiler/noirc_errors", "compiler/noirc_driver", "compiler/noirc_printable_type", + "compiler/formal_verification", "compiler/fm", "compiler/wasm", # Crates related to tooling built on top of the Noir compiler diff --git a/compiler/formal_verification/Cargo.toml b/compiler/formal_verification/Cargo.toml new file mode 100644 index 00000000000..a81a13d674d --- /dev/null +++ b/compiler/formal_verification/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "formal_verification" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[dependencies] +bn254_blackbox_solver.workspace = true +serde_json.workspace = true +serde.workspace = true +num-bigint.workspace = true +num-traits.workspace = true +vir.workspace = true +cfg-if.workspace = true +tracing.workspace = true +strum.workspace = true +strum_macros.workspace = true +nom = "8.0" + +[dev-dependencies] +base64.workspace = true +function_name = "0.3.0" +proptest.workspace = true +proptest-derive.workspace = true +insta.workspace = true + +[features] +bn254 = [] +bls12_381 = [] +test_utils = [] +nextest = [] diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs new file mode 100644 index 00000000000..06f1a3c69d4 --- /dev/null +++ b/compiler/formal_verification/src/lib.rs @@ -0,0 +1 @@ +mod parse; diff --git a/compiler/formal_verification/src/parse/mod.rs b/compiler/formal_verification/src/parse/mod.rs new file mode 100644 index 00000000000..ef7a2e5254e --- /dev/null +++ b/compiler/formal_verification/src/parse/mod.rs @@ -0,0 +1,192 @@ +use nom::{ + IResult, Parser, + branch::alt, + bytes::complete::{tag, take_while, take_while1}, + character::complete::{alpha1, alphanumeric0, char, digit1, multispace0}, + combinator::{cut, map, opt, recognize, value}, + error::{ContextError, ParseError, context}, + multi::{fold_many0, separated_list1}, + sequence::{delimited, pair, preceded, separated_pair, terminated}, +}; +use num_bigint::{BigInt, BigUint, Sign}; +use num_traits::ConstZero; +use std::{ + cell::LazyCell, + collections::HashMap, + fmt::{self, Debug}, + sync::Arc, +}; +use vir::{ + ast::{Constant, Expr, ExprX, Ident, IntRange, SpannedTyped, Typ, TypX, VarIdent}, + messages::{RawSpan, Span}, +}; + +const SPAN: LazyCell = LazyCell::new(|| Span { + raw_span: Arc::new(()), + id: 0, + data: vec![], + as_string: "".to_string(), +}); + +enum Attribute { + Ghost, + Ensures(Expr), + Requires(Expr), +} + +type Signature<'a> = HashMap<&'a str, Typ>; + +// TODO: variable/function rustc ids +// TODO: function signature + +pub fn parse_attribute<'a>( + annotation: &'a str, + signature: &Signature, + // initial_span: &SerializeTupleVariant +) -> IResult<&'a str, Attribute> { + todo!() +} + +fn parse_bool(input: &str) -> IResult<&str, bool> { + alt((map(tag("true"), |_| true), map(tag("false"), |_| false))).parse(input) +} + +fn sign(input: &str) -> IResult<&str, bool> { + let (i, opt_sign) = opt(alt(( + // + value(false, tag(&b"-"[..])), + value(true, tag(&b"+"[..])), + ))) + .parse(input)?; + let sign = opt_sign.unwrap_or(true); + + Ok((i, sign)) +} + +fn parse_int(input: &str) -> IResult<&str, BigInt> { + let (input, sign) = sign(input)?; + let (input, digits) = digit1(input)?; + + let biguint = digits + .chars() + .map(|c| c.to_digit(10).expect("`digit1` should return digits")) + .fold(BigUint::ZERO, |acc, d| acc * 10u8 + d); + + let bigint = BigInt::from_biguint( + match sign { + true => Sign::Plus, + false => Sign::Minus, + }, + biguint, + ); + + Ok((input, bigint)) +} + +fn parse_constant(input: &str) -> IResult<&str, Expr> { + alt(( + map(parse_bool, |b| { + SpannedTyped::new(&SPAN, &Arc::new(TypX::Bool), ExprX::Const(Constant::Bool(b))) + }), + map(parse_int, |bi| { + // TODO: Better type than `TypX::Int` + SpannedTyped::new( + &SPAN, + &Arc::new(TypX::Int(IntRange::Int)), + ExprX::Const(Constant::Int(bi)), + ) + }), + )) + .parse(input) +} + +fn parse_identifier(input: &str) -> IResult<&str, &str> { + fn is_valid_start(c: char) -> bool { + c.is_ascii_alphabetic() || c == '_' + } + + fn is_valid_char(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '_' + } + + let mut parser = recognize(pair( + // + take_while1(is_valid_start), + take_while(is_valid_char), + )); + + parser.parse(input) +} + +fn parse_var(input: &str) -> IResult<&str, Expr> { + let (input, ident) = parse_identifier(input)?; + + // TODO: Actual types, fetched from signatures + Ok(( + input, + SpannedTyped::new( + &SPAN, + &Arc::new(TypX::Bool), + ExprX::Var(VarIdent( + Arc::new(ident.to_string()), + vir::ast::VarIdentDisambiguate::RustcId(0), + )), + ), + )) +} + +fn parse_fn_call(input: &str) -> IResult<&str, Expr> { + todo!() +} + +fn parse_expression(input: &str) -> IResult<&str, Expr> { + alt(( + // + parse_constant, + parse_var, + )) + .parse(input) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bool_true() { + let expr = parse_expression("true").unwrap(); + assert_eq!(expr.0, ""); + assert!(matches!(*expr.1.typ, TypX::Bool)); + assert!(matches!(expr.1.x, ExprX::Const(Constant::Bool(true)))); + } + + #[test] + fn test_int() { + let chislo = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; + let expr = parse_expression(chislo).unwrap(); + assert_eq!(expr.0, ""); + assert!(matches!(*expr.1.typ, TypX::Int(IntRange::Int))); + let ExprX::Const(Constant::Int(ref bi)) = expr.1.x else { panic!() }; + assert_eq!(bi.to_str_radix(10), chislo); + } + + #[test] + fn test_ident() { + let identche = "Banica_123_"; + let expr = parse_expression(identche).unwrap(); + assert_eq!(expr.0, ""); + // TODO: same as definition + assert!(matches!(*expr.1.typ, TypX::Bool)); + let ExprX::Var(VarIdent(ref i, _)) = expr.1.x else { panic!() }; + assert_eq!(**i, identche.to_string()); + } + + #[test] + #[should_panic] + fn test_ident_starts_with_digit() { + let identche = "1Banica_123_"; + let expr = parse_var(identche).unwrap(); + assert_eq!(expr.0, ""); + dbg!(expr); + } +} From 10a72df84f6d624bbd7c8652ce512c5e32dbeb69 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 7 Jul 2025 11:18:11 +0300 Subject: [PATCH 02/86] feat(annotations): WIP function calls --- Cargo.lock | 2 + compiler/formal_verification/Cargo.toml | 2 + compiler/formal_verification/src/lib.rs | 2 +- compiler/formal_verification/src/parse/mod.rs | 68 +++++++++++++++---- 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7d61de8feb..037975385ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1951,6 +1951,8 @@ dependencies = [ "cfg-if", "function_name", "insta", + "noirc_errors", + "noirc_frontend", "nom", "num-bigint 0.4.6", "num-traits", diff --git a/compiler/formal_verification/Cargo.toml b/compiler/formal_verification/Cargo.toml index a81a13d674d..ee677e99f2b 100644 --- a/compiler/formal_verification/Cargo.toml +++ b/compiler/formal_verification/Cargo.toml @@ -12,6 +12,8 @@ serde_json.workspace = true serde.workspace = true num-bigint.workspace = true num-traits.workspace = true +noirc_frontend.workspace = true +noirc_errors.workspace = true vir.workspace = true cfg-if.workspace = true tracing.workspace = true diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 06f1a3c69d4..ea868482d66 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -1 +1 @@ -mod parse; +pub mod parse; diff --git a/compiler/formal_verification/src/parse/mod.rs b/compiler/formal_verification/src/parse/mod.rs index ef7a2e5254e..0c1eaad5be3 100644 --- a/compiler/formal_verification/src/parse/mod.rs +++ b/compiler/formal_verification/src/parse/mod.rs @@ -1,3 +1,6 @@ +use noirc_errors::Location; +use noirc_frontend::monomorphization::ast::{Expression, Function, GlobalId, Type}; +#[allow(unused)] use nom::{ IResult, Parser, branch::alt, @@ -5,19 +8,22 @@ use nom::{ character::complete::{alpha1, alphanumeric0, char, digit1, multispace0}, combinator::{cut, map, opt, recognize, value}, error::{ContextError, ParseError, context}, - multi::{fold_many0, separated_list1}, + multi::{fold_many0, separated_list0, separated_list1}, sequence::{delimited, pair, preceded, separated_pair, terminated}, }; use num_bigint::{BigInt, BigUint, Sign}; use num_traits::ConstZero; use std::{ cell::LazyCell, - collections::HashMap, + collections::{BTreeMap, HashMap}, fmt::{self, Debug}, sync::Arc, }; use vir::{ - ast::{Constant, Expr, ExprX, Ident, IntRange, SpannedTyped, Typ, TypX, VarIdent}, + ast::{ + AutospecUsage, CallTarget, CallTargetKind, Constant, Expr, ExprX, FunX, Ident, IntRange, + Path, PathX, SpannedTyped, Typ, TypX, VarIdent, + }, messages::{RawSpan, Span}, }; @@ -28,22 +34,21 @@ const SPAN: LazyCell = LazyCell::new(|| Span { as_string: "".to_string(), }); -enum Attribute { +pub enum Attribute { Ghost, Ensures(Expr), Requires(Expr), } -type Signature<'a> = HashMap<&'a str, Typ>; - // TODO: variable/function rustc ids // TODO: function signature pub fn parse_attribute<'a>( annotation: &'a str, - signature: &Signature, - // initial_span: &SerializeTupleVariant -) -> IResult<&'a str, Attribute> { + location: Location, + function: &Function, + gamma: &BTreeMap, +) -> Result { todo!() } @@ -136,15 +141,46 @@ fn parse_var(input: &str) -> IResult<&str, Expr> { } fn parse_fn_call(input: &str) -> IResult<&str, Expr> { - todo!() + let (input, name) = parse_identifier(input)?; + + let (input, params) = delimited( + tag("("), + separated_list0(pair(tag(","), opt(tag(" "))), parse_expression), + tag(")"), + ) + .parse(input)?; + + dbg!(&name, ¶ms); + + // TODO: Actual types, fetched from signatures + Ok(( + input, + SpannedTyped::new( + &SPAN, + &Arc::new(TypX::Bool), + ExprX::Call( + CallTarget::Fun( + CallTargetKind::Static, + Arc::new(FunX { + path: Path::new(PathX { krate: None, segments: Arc::new(vec![]) }), + }), + Arc::new(vec![]), + Arc::new(vec![]), + AutospecUsage::Final, + ), + Arc::new(vec![]), + ), + ), + )) } fn parse_expression(input: &str) -> IResult<&str, Expr> { - alt(( + alt([ // + parse_fn_call, parse_constant, parse_var, - )) + ]) .parse(input) } @@ -189,4 +225,12 @@ mod tests { assert_eq!(expr.0, ""); dbg!(expr); } + + #[test] + fn test_function_call() { + let identche = "banica(1, f(), g(1, kek))"; + let expr = parse_expression(identche).unwrap(); + assert_eq!(expr.0, ""); + dbg!(expr); + } } From c910d03d772542aa3371b9491987da468dfbcaa8 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 7 Jul 2025 11:18:11 +0300 Subject: [PATCH 03/86] feat(annotations): WIP more function calls - `State` passing around parsers, carrying important type information - `pub`-licize some functions in the `noirc_evaluator` module --- Cargo.lock | 1 + compiler/formal_verification/Cargo.toml | 3 +- compiler/formal_verification/src/parse/mod.rs | 281 ++++++++++++++---- .../src/vir/vir_gen/expr_to_vir/expr.rs | 2 +- .../noirc_evaluator/src/vir/vir_gen/mod.rs | 6 +- 5 files changed, 222 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 037975385ae..9a223a57834 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1952,6 +1952,7 @@ dependencies = [ "function_name", "insta", "noirc_errors", + "noirc_evaluator", "noirc_frontend", "nom", "num-bigint 0.4.6", diff --git a/compiler/formal_verification/Cargo.toml b/compiler/formal_verification/Cargo.toml index ee677e99f2b..2ab6908abf5 100644 --- a/compiler/formal_verification/Cargo.toml +++ b/compiler/formal_verification/Cargo.toml @@ -12,8 +12,9 @@ serde_json.workspace = true serde.workspace = true num-bigint.workspace = true num-traits.workspace = true -noirc_frontend.workspace = true noirc_errors.workspace = true +noirc_evaluator.workspace = true +noirc_frontend.workspace = true vir.workspace = true cfg-if.workspace = true tracing.workspace = true diff --git a/compiler/formal_verification/src/parse/mod.rs b/compiler/formal_verification/src/parse/mod.rs index 0c1eaad5be3..6835e902979 100644 --- a/compiler/formal_verification/src/parse/mod.rs +++ b/compiler/formal_verification/src/parse/mod.rs @@ -1,5 +1,13 @@ -use noirc_errors::Location; -use noirc_frontend::monomorphization::ast::{Expression, Function, GlobalId, Type}; +use noirc_errors::{Location, Span}; +use noirc_evaluator::vir::vir_gen::{ + build_span_no_id, + expr_to_vir::{expr::function_name_to_vir_fun, types::ast_type_to_vir_type}, +}; +use noirc_frontend::{ + monomorphization::ast::{Expression, FuncId, Function, GlobalId, Type}, + parser::ParserError as NoirParserError, +}; +use nom::Err; #[allow(unused)] use nom::{ IResult, Parser, @@ -19,20 +27,83 @@ use std::{ fmt::{self, Debug}, sync::Arc, }; -use vir::{ - ast::{ - AutospecUsage, CallTarget, CallTargetKind, Constant, Expr, ExprX, FunX, Ident, IntRange, - Path, PathX, SpannedTyped, Typ, TypX, VarIdent, - }, - messages::{RawSpan, Span}, +use vir::ast::{ + AutospecUsage, CallTarget, CallTargetKind, Constant, Expr, ExprX, FunX, Ident, IntRange, Path, + PathX, SpannedTyped, Typ, TypX, VarIdent, VarIdentDisambiguate, }; -const SPAN: LazyCell = LazyCell::new(|| Span { - raw_span: Arc::new(()), - id: 0, - data: vec![], - as_string: "".to_string(), -}); +struct State<'a> { + // span: &'a Span, + full_length: u32, + location: Location, + function: &'a Function, + global_constants: &'a BTreeMap, + functions: &'a BTreeMap, +} + +type Input<'a> = &'a str; + +#[derive(Debug)] +struct ParserError(NoirParserError); + +type PResult<'a, T> = IResult, T, ParserError>; + +impl<'a> ParseError> for ParserError { + fn from_error_kind(input: Input<'a>, kind: nom::error::ErrorKind) -> Self { + Self(NoirParserError::empty( + noirc_frontend::token::Token::DoubleDotEqual, + Location::dummy(), + )) + } + + fn append(input: Input<'a>, kind: nom::error::ErrorKind, other: Self) -> Self { + other + } +} + +// https://github.com/rust-bakery/nom/blob/main/doc/error_management.md + +fn abuild_expr( + state: &State, + prev_offset: usize, + after_offset: usize, + atypx: Arc, + exprx: ExprX, +) -> Expr { + let span = build_span_no_id( + "".to_string(), + Some(Location { + span: Span::inclusive( + state.location.span.start() + state.full_length - prev_offset as u32, + state.location.span.start() + state.full_length - after_offset as u32, + ), + file: state.location.file, + }), + ); + + SpannedTyped::new(&span, &atypx, exprx) +} + +fn build_expr( + state: &State, + prev_offset: usize, + after_offset: usize, + typx: TypX, + exprx: ExprX, +) -> Expr { + let span = build_span_no_id( + "".to_string(), + Some(Location { + span: Span::inclusive( + state.location.span.start() + state.full_length - prev_offset as u32, + state.location.span.start() + state.full_length - after_offset as u32, + ), + file: state.location.file, + }), + ); + + SpannedTyped::new(&span, &Arc::new(typx), exprx) +} pub enum Attribute { Ghost, @@ -46,18 +117,27 @@ pub enum Attribute { pub fn parse_attribute<'a>( annotation: &'a str, location: Location, - function: &Function, - gamma: &BTreeMap, + function: &'a Function, + global_constants: &'a BTreeMap, + functions: &'a BTreeMap, ) -> Result { + let state = State { + full_length: annotation.len() as u32, + location, + function, + global_constants, + functions, + }; + build_span_no_id("".to_string(), todo!()); todo!() } -fn parse_bool(input: &str) -> IResult<&str, bool> { +fn parse_bool(input: &str) -> PResult { alt((map(tag("true"), |_| true), map(tag("false"), |_| false))).parse(input) } -fn sign(input: &str) -> IResult<&str, bool> { - let (i, opt_sign) = opt(alt(( +fn sign(input: &str) -> PResult { + let (input, opt_sign) = opt(alt(( // value(false, tag(&b"-"[..])), value(true, tag(&b"+"[..])), @@ -65,10 +145,10 @@ fn sign(input: &str) -> IResult<&str, bool> { .parse(input)?; let sign = opt_sign.unwrap_or(true); - Ok((i, sign)) + Ok((input, sign)) } -fn parse_int(input: &str) -> IResult<&str, BigInt> { +fn parse_int(input: &str) -> PResult { let (input, sign) = sign(input)?; let (input, digits) = digit1(input)?; @@ -88,24 +168,25 @@ fn parse_int(input: &str) -> IResult<&str, BigInt> { Ok((input, bigint)) } -fn parse_constant(input: &str) -> IResult<&str, Expr> { - alt(( - map(parse_bool, |b| { - SpannedTyped::new(&SPAN, &Arc::new(TypX::Bool), ExprX::Const(Constant::Bool(b))) - }), +fn parse_constant<'a>(state: &State, input: &'a str) -> PResult<'a, Expr> { + let prev_offset = input.len(); + let (input, (typx, exprx)) = alt(( + map(parse_bool, |b| (TypX::Bool, ExprX::Const(Constant::Bool(b)))), map(parse_int, |bi| { // TODO: Better type than `TypX::Int` - SpannedTyped::new( - &SPAN, - &Arc::new(TypX::Int(IntRange::Int)), - ExprX::Const(Constant::Int(bi)), - ) + (TypX::Int(IntRange::Int), ExprX::Const(Constant::Int(bi))) }), )) - .parse(input) + .parse(input)?; + + let after_offset = input.len(); + + let res = build_expr(state, prev_offset, after_offset, typx, exprx); + + Ok((input, res)) } -fn parse_identifier(input: &str) -> IResult<&str, &str> { +fn parse_identifier(input: &str) -> PResult<&str> { fn is_valid_start(c: char) -> bool { c.is_ascii_alphabetic() || c == '_' } @@ -123,74 +204,142 @@ fn parse_identifier(input: &str) -> IResult<&str, &str> { parser.parse(input) } -fn parse_var(input: &str) -> IResult<&str, Expr> { +fn parse_var<'a>(state: &State, input: &'a str) -> PResult<'a, Expr> { + let prev_offset = input.len(); let (input, ident) = parse_identifier(input)?; + let after_offset = input.len(); - // TODO: Actual types, fetched from signatures Ok(( input, - SpannedTyped::new( - &SPAN, - &Arc::new(TypX::Bool), - ExprX::Var(VarIdent( - Arc::new(ident.to_string()), - vir::ast::VarIdentDisambiguate::RustcId(0), - )), + abuild_expr( + state, + prev_offset, + after_offset, + // NOTE: type of variable is either found in the function's parameters + // or is the magic `result`, which inherits the function's return type + state + .function + .parameters + .iter() + .find_map(|k| (k.2 == ident).then(|| &k.3)) + .or_else(|| (ident == "result").then(|| &state.function.return_type)) + .map(ast_type_to_vir_type) + .ok_or(Err::Error(ParserError(NoirParserError::empty( + noirc_frontend::token::Token::Arrow, + Location::dummy(), + ))))?, + ExprX::Var(VarIdent(Arc::new(ident.to_string()), VarIdentDisambiguate::RustcId(0))), ), )) } -fn parse_fn_call(input: &str) -> IResult<&str, Expr> { +fn parse_fn_call<'a>(state: &State, input: &'a str) -> PResult<'a, Expr> { + let prev_offset = input.len(); let (input, name) = parse_identifier(input)?; let (input, params) = delimited( tag("("), - separated_list0(pair(tag(","), opt(tag(" "))), parse_expression), + separated_list0(pair(tag(","), opt(tag(" "))), |input| parse_expression(state, input)), tag(")"), ) .parse(input)?; + let after_offset = input.len(); - dbg!(&name, ¶ms); - - // TODO: Actual types, fetched from signatures Ok(( input, - SpannedTyped::new( - &SPAN, - &Arc::new(TypX::Bool), + abuild_expr( + state, + prev_offset, + after_offset, + state + .functions + .iter() + .find_map(|(_, func)| (func.name == name).then_some(&func.return_type)) + .map(ast_type_to_vir_type) + .ok_or(Err::Error(ParserError(NoirParserError::empty( + noirc_frontend::token::Token::Arrow, + Location::dummy(), + ))))?, ExprX::Call( CallTarget::Fun( CallTargetKind::Static, - Arc::new(FunX { - path: Path::new(PathX { krate: None, segments: Arc::new(vec![]) }), - }), + function_name_to_vir_fun(name.to_string()), Arc::new(vec![]), Arc::new(vec![]), AutospecUsage::Final, ), - Arc::new(vec![]), + Arc::new(params), ), ), )) } -fn parse_expression(input: &str) -> IResult<&str, Expr> { - alt([ +fn parse_expression<'a>(state: &State, input: &'a str) -> PResult<'a, Expr> { + alt(( // - parse_fn_call, - parse_constant, - parse_var, - ]) + |input| parse_fn_call(state, input), + |input| parse_constant(state, input), + |input| parse_var(state, input), + )) .parse(input) } #[cfg(test)] mod tests { + use noirc_frontend::{ + monomorphization::ast::{FuncId, InlineType, LocalId}, + shared::Visibility, + }; + use super::*; + fn empty_state(full_length: u32) -> State<'static> { + State { + full_length, + location: Location { span: Span::inclusive(1234, 5678), file: Default::default() }, + function: Box::leak(Box::new(Function { + id: FuncId(4321), + name: "tutmanik".to_string(), + parameters: vec![ + (LocalId(0), false, "a".to_string(), Type::Bool, Visibility::Public), + (LocalId(1), false, "kek".to_string(), Type::Unit, Visibility::Public), + ], + body: Expression::Block(vec![]), + return_type: Type::Unit, + return_visibility: Visibility::Public, + unconstrained: false, + inline_type: InlineType::Inline, + func_sig: (vec![], None), + formal_verification_attributes: vec![], + })), + global_constants: Box::leak(Box::new(vec![].into_iter().collect())), + functions: Box::leak(Box::new( + vec![( + FuncId(0), + Function { + id: FuncId(0), + name: "banica".to_string(), + // TODO: not type-checking parameters, yet + // might need to do some manual dispatching + parameters: vec![], + body: Expression::Block(vec![]), + return_type: Type::Field, + return_visibility: Visibility::Public, + unconstrained: false, + inline_type: InlineType::Inline, + func_sig: (vec![], None), + formal_verification_attributes: vec![], + }, + )] + .into_iter() + .collect(), + )), + } + } + #[test] fn test_bool_true() { - let expr = parse_expression("true").unwrap(); + let expr = parse_expression(&empty_state(4), "true").unwrap(); assert_eq!(expr.0, ""); assert!(matches!(*expr.1.typ, TypX::Bool)); assert!(matches!(expr.1.x, ExprX::Const(Constant::Bool(true)))); @@ -199,7 +348,7 @@ mod tests { #[test] fn test_int() { let chislo = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; - let expr = parse_expression(chislo).unwrap(); + let expr = parse_expression(&empty_state(100), chislo).unwrap(); assert_eq!(expr.0, ""); assert!(matches!(*expr.1.typ, TypX::Int(IntRange::Int))); let ExprX::Const(Constant::Int(ref bi)) = expr.1.x else { panic!() }; @@ -209,7 +358,7 @@ mod tests { #[test] fn test_ident() { let identche = "Banica_123_"; - let expr = parse_expression(identche).unwrap(); + let expr = parse_expression(&empty_state(11), identche).unwrap(); assert_eq!(expr.0, ""); // TODO: same as definition assert!(matches!(*expr.1.typ, TypX::Bool)); @@ -221,15 +370,15 @@ mod tests { #[should_panic] fn test_ident_starts_with_digit() { let identche = "1Banica_123_"; - let expr = parse_var(identche).unwrap(); + let expr = parse_var(&empty_state(12), identche).unwrap(); assert_eq!(expr.0, ""); dbg!(expr); } #[test] fn test_function_call() { - let identche = "banica(1, f(), g(1, kek))"; - let expr = parse_expression(identche).unwrap(); + let identche = "banica(1, banica(a, kek))"; + let expr = parse_expression(&empty_state(25), identche).unwrap(); assert_eq!(expr.0, ""); dbg!(expr); } diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/expr.rs b/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/expr.rs index ddccf78197e..88174086603 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/expr.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/expr.rs @@ -997,7 +997,7 @@ fn build_var_ident(name: String, id: u32) -> VarIdent { ) } -fn function_name_to_vir_fun(func_name: String) -> Fun { +pub fn function_name_to_vir_fun(func_name: String) -> Fun { Arc::new(FunX { path: Arc::new(PathX { krate: None, segments: Arc::new(vec![Arc::new(func_name)]) }), }) diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs b/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs index 7731df511d9..e42a9fd792b 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs @@ -16,18 +16,18 @@ use vir::{ use crate::vir::vir_gen::{expr_to_vir::expression_location, globals::build_global_const_x}; -fn encode_span_to_string(location: Location) -> String { +pub fn encode_span_to_string(location: Location) -> String { let stringified_span: String = format!("{}, {}", location.span.start(), location.span.end()); let stringified_file_id: String = format!("{}", location.file.as_usize()); format!("({}, {})", stringified_span, stringified_file_id) } -fn build_span_no_id(debug_string: String, span: Option) -> Span { +pub fn build_span_no_id(debug_string: String, span: Option) -> Span { build_span(0, debug_string, span) } -fn build_span(id: u32, debug_string: String, span: Option) -> Span { +pub fn build_span(id: u32, debug_string: String, span: Option) -> Span { let encoded_span = span.map(encode_span_to_string).unwrap_or_default(); Span { raw_span: Arc::new(()), // Currently unusable because of hard to resolve bug From 5c52271bfa55624a52a68c70345de44ad2ff5eb7 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Tue, 8 Jul 2025 18:48:06 +0300 Subject: [PATCH 04/86] chore(monomorphizer): Private to pub --- .../src/monomorphization/mod.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index f00eba151a9..a7ad69753ad 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -71,7 +71,7 @@ struct LambdaContext { /// /// This struct holds the FIFO queue of functions to monomorphize, which is added to /// whenever a new (function, type) combination is encountered. -pub(super) struct Monomorphizer<'interner> { +pub struct Monomorphizer<'interner> { /// Functions are keyed by their unique ID, whether they're unconstrained, their expected type, /// and any generics they have so that we can monomorphize a new version of the function for each type. /// @@ -83,16 +83,16 @@ pub(super) struct Monomorphizer<'interner> { /// duplicated during monomorphization. Doing so would allow them to be used polymorphically /// but would also cause them to be re-evaluated which is a performance trap that would /// confuse users. - locals: HashMap, + pub locals: HashMap, /// Globals are keyed by their unique ID because they are never duplicated during monomorphization. globals: HashMap, - finished_globals: HashMap, + pub finished_globals: HashMap, /// Queue of functions to monomorphize next each item in the queue is a tuple of: /// (old_id, new_monomorphized_id, any type bindings to apply, the trait method if old_id is from a trait impl, is_unconstrained, location) - queue: VecDeque<( + pub queue: VecDeque<( node_interner::FuncId, FuncId, TypeBindings, @@ -103,10 +103,10 @@ pub(super) struct Monomorphizer<'interner> { /// When a function finishes being monomorphized, the monomorphized ast::Function is /// stored here along with its FuncId. - finished_functions: BTreeMap, + pub finished_functions: BTreeMap, /// Used to reference existing definitions in the HIR - interner: &'interner mut NodeInterner, + pub interner: &'interner mut NodeInterner, lambda_envs_stack: Vec, @@ -117,11 +117,11 @@ pub(super) struct Monomorphizer<'interner> { is_range_loop: bool, - return_location: Option, + pub return_location: Option, - debug_type_tracker: DebugTypeTracker, + pub debug_type_tracker: DebugTypeTracker, - in_unconstrained_function: bool, + pub in_unconstrained_function: bool, } /// Using nested HashMaps here lets us avoid cloning HirTypes when calling .get() @@ -215,7 +215,7 @@ pub fn monomorphize_debug( } impl<'interner> Monomorphizer<'interner> { - pub(crate) fn new( + pub fn new( interner: &'interner mut NodeInterner, debug_type_tracker: DebugTypeTracker, ) -> Self { @@ -343,7 +343,7 @@ impl<'interner> Monomorphizer<'interner> { .insert(turbofish_generics, new_id); } - fn compile_main( + pub fn compile_main( &mut self, main_id: node_interner::FuncId, ) -> Result { @@ -365,7 +365,7 @@ impl<'interner> Monomorphizer<'interner> { Ok(main_meta.function_signature()) } - fn function( + pub fn function( &mut self, f: node_interner::FuncId, id: FuncId, From 1f8f5eaa00c234520ecaf7694b53134154a8d02a Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Tue, 8 Jul 2025 18:49:50 +0300 Subject: [PATCH 05/86] feat(fv-bridge): First steps towards new architecture Start working towards using Noir as a library without modifying internal structures (like extending the AST). Defined a new module `fv-bridge` which contains the functions for compiling a program and converting it to a VIR Krate. --- Cargo.lock | 15 +++ Cargo.toml | 3 + compiler/fv_bridge/Cargo.toml | 25 +++++ compiler/fv_bridge/src/lib.rs | 205 ++++++++++++++++++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 compiler/fv_bridge/Cargo.toml create mode 100644 compiler/fv_bridge/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9a223a57834..db3ec9a5772 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2099,6 +2099,21 @@ dependencies = [ "thread_local", ] +[[package]] +name = "fv_bridge" +version = "1.0.0-beta.7" +dependencies = [ + "fm", + "formal_verification", + "iter-extended", + "noirc_driver", + "noirc_errors", + "noirc_evaluator", + "noirc_frontend", + "serde", + "vir", +] + [[package]] name = "fxhash" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 972b2e785ec..424024c4a78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [workspace] members = [ + # FV crates + "compiler/fv_bridge", # Compiler crates "compiler/noirc_arena", "compiler/noirc_evaluator", @@ -155,6 +157,7 @@ codespan-reporting = "0.11.1" # Formal verification vir = {git = "https://github.com/Aristotelis2002/verus-lib", branch = "synced_main"} +formal_verification = { path = "compiler/formal_verification"} # Benchmarking criterion = "^0.5.0" diff --git a/compiler/fv_bridge/Cargo.toml b/compiler/fv_bridge/Cargo.toml new file mode 100644 index 00000000000..2a4d00b8be7 --- /dev/null +++ b/compiler/fv_bridge/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "fv_bridge" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[lints] +workspace = true + + +[dependencies] +vir.workspace = true +serde.workspace = true +noirc_errors.workspace = true +noirc_frontend.workspace = true +noirc_evaluator.workspace = true +noirc_driver.workspace = true +fm.workspace = true +iter-extended.workspace = true +formal_verification.workspace = true + +[dev-dependencies] +iter-extended.workspace = true diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs new file mode 100644 index 00000000000..e629a99779e --- /dev/null +++ b/compiler/fv_bridge/src/lib.rs @@ -0,0 +1,205 @@ +use std::collections::{BTreeMap, HashMap}; + +use fm::FileId; +use formal_verification::parse::{Attribute, parse_attribute}; +use iter_extended::vecmap; +use noirc_driver::{CompilationResult, CompileError, CompileOptions, check_crate}; +use noirc_errors::{CustomDiagnostic, Location}; +use noirc_evaluator::{ + errors::{RuntimeError, SsaReport}, + vir::{create_verus_vir, vir_gen::BuildingKrateError}, +}; +use noirc_frontend::{ + debug::DebugInstrumenter, + graph::CrateId, + hir::Context, + monomorphization::{ + Monomorphizer, + ast::{FuncId, Program}, + debug_types::DebugTypeTracker, + errors::MonomorphizationError, + perform_impl_bindings, perform_instantiation_bindings, undo_instantiation_bindings, + }, + node_interner::{self, NodeInterner}, + parser::ParserError, + token::SecondaryAttributeKind, +}; +use vir::ast::Krate; + +pub fn compile_and_build_vir_krate( + context: &mut Context, + crate_id: CrateId, + options: &CompileOptions, +) -> CompilationResult { + modified_compile_main(context, crate_id, options) +} + +fn modified_compile_main( + context: &mut Context, + crate_id: CrateId, + options: &CompileOptions, +) -> CompilationResult { + let (_, mut warnings) = check_crate(context, crate_id, options)?; + + let main = context.get_main_function(&crate_id).ok_or_else(|| { + let err = CustomDiagnostic::from_message( + "cannot compile crate into a program as it does not contain a `main` function", + FileId::default(), + ); + vec![err] + })?; + + let compiled_program = modified_compile_no_check(context, options, main) + .map_err(|error| vec![CustomDiagnostic::from(error)])?; + + let compilation_warnings = vecmap(compiled_program.warnings.clone(), CustomDiagnostic::from); + if options.deny_warnings && !compilation_warnings.is_empty() { + return Err(compilation_warnings); + } + if !options.silence_warnings { + warnings.extend(compilation_warnings); + } + + Ok((compiled_program.krate, warnings)) +} + +// Something like the method `compile_no_check()` +fn modified_compile_no_check( + context: &mut Context, + options: &CompileOptions, + main_function: node_interner::FuncId, +) -> Result { + let force_unconstrained = options.force_brillig || options.minimal_ssa; + + let (program, fv_annotations) = modified_monomorphize( + main_function, + &mut context.def_interner, + &DebugInstrumenter::default(), + force_unconstrained, + )?; + + if options.show_monomorphized { + println!("{program}"); + } + + Ok(KrateAndWarnings { + krate: create_verus_vir(program).map_err(|BuildingKrateError::Error(msg)| { + RuntimeError::InternalError(noirc_evaluator::errors::InternalError::General { + message: msg, + call_stack: vec![], + }) + })?, + warnings: vec![], + parse_annotations_errors: vec![], // TODO(totel): Get the errors from `modified_monomorphize()` + }) +} + +enum MonomorphOrParserError { + MonomorphizationError(MonomorphizationError), + ParserErrors(Vec), +} + +fn modified_monomorphize( + main: node_interner::FuncId, + interner: &mut NodeInterner, + debug_instrumenter: &DebugInstrumenter, + force_unconstrained: bool, +) -> Result<(Program, Vec<(FuncId, Vec)>), MonomorphizationError> { + let debug_type_tracker = DebugTypeTracker::build_from_debug_instrumenter(debug_instrumenter); + // TODO(totel): Monomorphizer is a `pub(crate)` struct + let mut monomorphizer = Monomorphizer::new(interner, debug_type_tracker); + monomorphizer.in_unconstrained_function = force_unconstrained; + let function_sig = monomorphizer.compile_main(main)?; + let mut new_ids_to_old_ids: HashMap = HashMap::new(); + + while !monomorphizer.queue.is_empty() { + let (next_fn_id, new_id, bindings, trait_method, is_unconstrained, location) = + monomorphizer.queue.pop_front().unwrap(); + monomorphizer.locals.clear(); + + monomorphizer.in_unconstrained_function = is_unconstrained; + + perform_instantiation_bindings(&bindings); + let interner = &monomorphizer.interner; + let impl_bindings = perform_impl_bindings(interner, trait_method, next_fn_id, location) + .map_err(MonomorphizationError::InterpreterError)?; + + monomorphizer.function(next_fn_id, new_id, location)?; + new_ids_to_old_ids.insert(new_id, next_fn_id); + undo_instantiation_bindings(impl_bindings); + undo_instantiation_bindings(bindings); + } + + let func_sigs = monomorphizer + .finished_functions + .iter() + .flat_map(|(_, f)| { + if (!force_unconstrained && f.inline_type.is_entry_point()) + || f.id == Program::main_id() + { + Some(f.func_sig.clone()) + } else { + None + } + }) + .collect(); + + let globals = monomorphizer.finished_globals.into_iter().collect::>(); + + let fv_annotations: Vec<(FuncId, Vec)> = monomorphizer + .finished_functions + .iter() + .filter_map(|(new_func_id, function)| { + new_ids_to_old_ids.get(new_func_id).map(|old_id| (new_func_id, old_id, function)) + }) + .map(|(new_func_id, old_id, function)| { + let tag_attributes: Vec<(&str, Location)> = monomorphizer + .interner + .function_attributes(old_id) + .secondary + .iter() + .filter_map(|attribute| match &attribute.kind { + SecondaryAttributeKind::Tag(annotation) => { + Some((annotation.as_str(), attribute.location)) + } + _ => None, + }) + .collect(); + + ( + new_func_id.clone(), + tag_attributes + .into_iter() + .map(|(annotation_body, location)| { + //TODO(totel): After error types are introduced remove the `unwrap` and switch to `?` + parse_attribute(annotation_body, location, function, &globals).unwrap() + }) + .collect(), + ) + }) + .collect(); + + let functions = vecmap(monomorphizer.finished_functions, |(_, f)| f); + + let (debug_variables, debug_functions, debug_types) = + monomorphizer.debug_type_tracker.extract_vars_and_types(); + + let program = Program::new( + functions, + func_sigs, + function_sig, + monomorphizer.return_location, + globals, + debug_variables, + debug_functions, + debug_types, + ); + + Ok((program.handle_ownership(), fv_annotations)) +} + +pub struct KrateAndWarnings { + pub krate: Krate, + pub warnings: Vec, + pub parse_annotations_errors: Vec, +} From d589dc6e8607ede272f41b53683579b1be0594ec Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Thu, 10 Jul 2025 17:00:58 +0300 Subject: [PATCH 06/86] feat(fv-bridge): Attach VIR FV annotations to Mon. AST Attach the FV annotations to the monomorphized AST which was converted to VIR form. The functions were duplicated to allow verification of programs written using either the old or the new syntax. This feature will be available only temporary. --- Cargo.lock | 1 + compiler/fv_bridge/src/lib.rs | 16 ++-- compiler/noirc_evaluator/Cargo.toml | 1 + compiler/noirc_evaluator/src/vir/mod.rs | 19 +++- .../src/vir/vir_gen/function.rs | 94 ++++++++++++++++++- .../noirc_evaluator/src/vir/vir_gen/mod.rs | 82 +++++++++++++++- 6 files changed, 196 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db3ec9a5772..71c0b7a8f67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3898,6 +3898,7 @@ dependencies = [ "cfg-if", "chrono", "fm", + "formal_verification", "function_name", "fxhash", "im", diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index e629a99779e..990f7b57b0f 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -7,7 +7,7 @@ use noirc_driver::{CompilationResult, CompileError, CompileOptions, check_crate} use noirc_errors::{CustomDiagnostic, Location}; use noirc_evaluator::{ errors::{RuntimeError, SsaReport}, - vir::{create_verus_vir, vir_gen::BuildingKrateError}, + vir::{create_verus_vir_with_ready_annotations, vir_gen::BuildingKrateError}, }; use noirc_frontend::{ debug::DebugInstrumenter, @@ -83,12 +83,14 @@ fn modified_compile_no_check( } Ok(KrateAndWarnings { - krate: create_verus_vir(program).map_err(|BuildingKrateError::Error(msg)| { - RuntimeError::InternalError(noirc_evaluator::errors::InternalError::General { - message: msg, - call_stack: vec![], - }) - })?, + krate: create_verus_vir_with_ready_annotations(program, fv_annotations).map_err( + |BuildingKrateError::Error(msg)| { + RuntimeError::InternalError(noirc_evaluator::errors::InternalError::General { + message: msg, + call_stack: vec![], + }) + }, + )?, warnings: vec![], parse_annotations_errors: vec![], // TODO(totel): Get the errors from `modified_monomorphize()` }) diff --git a/compiler/noirc_evaluator/Cargo.toml b/compiler/noirc_evaluator/Cargo.toml index d3cfe324574..7dbb2eb5b4f 100644 --- a/compiler/noirc_evaluator/Cargo.toml +++ b/compiler/noirc_evaluator/Cargo.toml @@ -36,6 +36,7 @@ vec-collections = "0.4.3" petgraph.workspace = true fm.workspace = true vir.workspace = true +formal_verification.workspace = true [dev-dependencies] proptest.workspace = true diff --git a/compiler/noirc_evaluator/src/vir/mod.rs b/compiler/noirc_evaluator/src/vir/mod.rs index 648a317dfc1..9429cc8063c 100644 --- a/compiler/noirc_evaluator/src/vir/mod.rs +++ b/compiler/noirc_evaluator/src/vir/mod.rs @@ -1,12 +1,25 @@ -use noirc_frontend::monomorphization::ast::Program; +use formal_verification::parse::Attribute; +use noirc_frontend::monomorphization::ast::{FuncId, Program}; use vir::ast::Krate; use vir_gen::{BuildingKrateError, build_krate}; -use crate::vir::opt_passes::monomorph_ast_optimization_passes; -pub mod vir_gen; +use crate::vir::{ + opt_passes::monomorph_ast_optimization_passes, vir_gen::build_krate_with_ready_annotations, +}; pub mod opt_passes; +pub mod vir_gen; pub fn create_verus_vir(program: Program) -> Result { let program = monomorph_ast_optimization_passes(program); build_krate(program) } + +/// Same as `create_verus_vir` but expects the FV attributes +/// to be already transformed into VIR form. +pub fn create_verus_vir_with_ready_annotations( + program: Program, + fv_annotations: Vec<(FuncId, Vec)>, +) -> Result { + let program = monomorph_ast_optimization_passes(program); + build_krate_with_ready_annotations(program, fv_annotations) +} diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/function.rs b/compiler/noirc_evaluator/src/vir/vir_gen/function.rs index 60d3b634cef..d7fc82af072 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/function.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/function.rs @@ -7,14 +7,17 @@ use super::{ BuildingKrateError, attribute::{func_ensures_to_vir_expr, func_requires_to_vir_expr}, }; +use formal_verification::parse::Attribute; use noirc_errors::Location; -use noirc_frontend::monomorphization::ast::{Expression, Function, GlobalId, MonomorphizedFvAttribute, Type}; +use noirc_frontend::monomorphization::ast::{ + Expression, Function, GlobalId, MonomorphizedFvAttribute, Type, +}; use std::collections::BTreeMap; use std::sync::Arc; use vir::ast::{ - BodyVisibility, Fun, FunX, FunctionAttrs, FunctionAttrsX, FunctionKind, FunctionX, ItemKind, - Mode, Module, Opaqueness, Param, ParamX, Params, PathX, VarIdent, VarIdentDisambiguate, - Visibility, + BodyVisibility, Exprs, Fun, FunX, FunctionAttrs, FunctionAttrsX, FunctionKind, FunctionX, + ItemKind, Mode, Module, Opaqueness, Param, ParamX, Params, PathX, VarIdent, + VarIdentDisambiguate, Visibility, }; use vir::def::Spanned; @@ -154,3 +157,86 @@ pub fn build_funx( Ok(funx) } + +// Converts the given Monomorphized AST function into a VIR function. +/// Same as `build_funx` but expects the FV attributes +/// to be already transformed into VIR form. +pub fn build_funx_with_ready_annotations( + function: &Function, + current_module: &Module, + globals: &BTreeMap, + annotations: Vec, +) -> Result { + let is_ghost = annotations.iter().any(|x| matches!(x, Attribute::Ghost)); + let mode = get_function_mode(is_ghost); + + let function_params = get_function_params(function, mode)?; + let function_return_param = get_function_return_param(function, mode)?; + + let (requires_annotations, ensures_annotations): (Vec, Vec) = annotations + .into_iter() + .filter(|attribute| { + matches!(attribute, Attribute::Requires(_)) + || matches!(attribute, Attribute::Ensures(_)) + }) + .partition(|attribute| matches!(attribute, Attribute::Requires(_))); + + let requires_annotations_inner: Exprs = Arc::new( + requires_annotations + .into_iter() + .filter_map(|x| match x { + Attribute::Ghost => None, + Attribute::Ensures(_) => None, + Attribute::Requires(expr) => Some(expr), + }) + .collect(), + ); + + let ensures_annotations_inner: Exprs = Arc::new( + ensures_annotations + .into_iter() + .filter_map(|x| match x { + Attribute::Ghost => None, + Attribute::Requires(_) => None, + Attribute::Ensures(expr) => Some(expr), + }) + .collect(), + ); + + let funx = FunctionX { + name: function_into_funx_name(function), + proxy: None, // Only needed for external fn specifications which we currently don't support + kind: FunctionKind::Static, // Monomorphized AST has only static functions + visibility: Visibility { + restricted_to: None, // `None` is for functions with public visibility + }, // Categorization for public/private visibility doesn't exist in the Mon. AST + body_visibility: BodyVisibility::Visibility(Visibility { + restricted_to: None, // We currently support only fully visible ghost functions + }), + opaqueness: Opaqueness::Revealed { + visibility: Visibility { restricted_to: None }, // We currently don't support opaqueness control + }, + owning_module: Some(current_module.x.path.clone()), // The module in which this function is located. + mode, + typ_params: Arc::new(Vec::new()), // There are no generics in Monomorphized AST + typ_bounds: Arc::new(Vec::new()), // There are no generics in Monomorphized AST + params: function_params, + ret: function_return_param, + ens_has_return: !is_function_return_void(function), + require: requires_annotations_inner, + ensure: ensures_annotations_inner, + returns: None, // We don't support the special clause called `return` + decrease: Arc::new(vec![]), // Annotation for recursive functions. We currently don't support it + decrease_when: None, // Annotation for recursive functions. We currently don't support it + decrease_by: None, // Annotation for recursive functions. We currently don't support it + fndef_axioms: None, // We currently don't support this feature + mask_spec: None, // We currently don't support this feature + unwind_spec: None, // To be able to use functions from Verus std we need None on unwinding + item_kind: ItemKind::Function, + attrs: build_default_funx_attrs(function.parameters.is_empty(), !function.unconstrained), + body: Some(func_body_to_vir_expr(function, mode, globals)), + extra_dependencies: Vec::new(), + }; + + Ok(funx) +} diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs b/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs index e42a9fd792b..bd93b7a0a07 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs @@ -3,18 +3,22 @@ pub mod expr_to_vir; pub mod function; pub mod globals; +use formal_verification::parse::Attribute; use function::build_funx; use noirc_errors::Location; -use noirc_frontend::monomorphization::ast::Program; +use noirc_frontend::monomorphization::ast::{FuncId, Program}; use serde::{Deserialize, Serialize}; -use std::{fmt::Display, sync::Arc}; +use std::{collections::HashMap, fmt::Display, sync::Arc}; use vir::{ ast::{Krate, KrateX, ModuleX, PathX}, def::Spanned, messages::Span, }; -use crate::vir::vir_gen::{expr_to_vir::expression_location, globals::build_global_const_x}; +use crate::vir::vir_gen::{ + expr_to_vir::expression_location, function::build_funx_with_ready_annotations, + globals::build_global_const_x, +}; pub fn encode_span_to_string(location: Location) -> String { let stringified_span: String = format!("{}, {}", location.span.start(), location.span.end()); @@ -113,3 +117,75 @@ pub fn build_krate(program: Program) -> Result { Ok(Arc::new(vir)) } + +/// Same as `build_krate` but expects the FV attributes +/// to be already transformed into VIR form. +pub fn build_krate_with_ready_annotations( + program: Program, + fv_annotations: Vec<(FuncId, Vec)>, +) -> Result { + let mut vir = KrateX { + functions: Vec::new(), + reveal_groups: Vec::new(), + datatypes: Vec::new(), + traits: Vec::new(), + trait_impls: Vec::new(), + assoc_type_impls: Vec::new(), + modules: Vec::new(), + external_fns: Vec::new(), + external_types: Vec::new(), + path_as_rust_names: vir::ast_util::get_path_as_rust_names_for_krate(&Arc::new( + vir::def::VERUSLIB.to_string(), + )), + arch: vir::ast::Arch { word_bits: vir::ast::ArchWordBits::Either32Or64 }, + }; + + // There are no modules in the Noir's monomorphized AST. + // Therefore we are creating a default module and all functions will be a part of it. + let module = Spanned::new( + build_span_no_id(String::from("module"), None), + ModuleX { + path: Arc::new(PathX { + krate: None, + segments: Arc::new(vec![Arc::new(String::from("Mon. AST"))]), + }), + reveals: None, + }, + ); + + // Insert global constants as functions. This is how Verus processes constants + vir.functions.extend(program.globals.iter().map( + |(global_id, (name, ast_type, expression))| { + let global_const_x = + build_global_const_x(name, ast_type, expression, &module, &program.globals); + Spanned::new( + build_span( + global_id.0, + format!("Global const {} = {}", name, expression), + expression_location(expression), + ), + global_const_x, + ) + }, + )); + + let mut annotations_map: HashMap> = fv_annotations.into_iter().collect(); + + for function in &program.functions { + let attrs = annotations_map.remove(&function.id).unwrap_or_else(Vec::new); + let func_x = build_funx_with_ready_annotations(function, &module, &program.globals, attrs)?; + let function = Spanned::new( + build_span( + function.id.0, + format!("Function({}) with name {}", function.id.0, function.name), + None, + ), + func_x, + ); + vir.functions.push(function); + } + + vir.modules.push(module); + + Ok(Arc::new(vir)) +} From 8175d962325bd218e642f19b3d6d36b1d2041815 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Thu, 10 Jul 2025 19:57:52 +0300 Subject: [PATCH 07/86] feat(new-syntax): Add new-syntax support to `nargo fv` You can now run `nargo fv --new-syntax` to verify programs which use the new-syntax for the FV annotations. If the flag `--new-syntax` is not provided you will be able to verify programs which use the old syntax. The new syntax is the following one `#[requires(expr)]` -> `#['requires(expr)]` `#[ensures(expr)]` -> `#['ensures(expr)]` `#[ghost]` -> `#['ghost]` --- Cargo.lock | 3 +- Cargo.toml | 1 + compiler/formal_verification/src/parse/mod.rs | 8 +-- compiler/fv_bridge/src/lib.rs | 12 +++- compiler/noirc_evaluator/Cargo.toml | 1 - compiler/noirc_evaluator/src/vir/mod.rs | 2 +- .../src/vir/vir_gen/function.rs | 2 +- .../noirc_evaluator/src/vir/vir_gen/mod.rs | 9 ++- tooling/nargo_cli/Cargo.toml | 4 ++ tooling/nargo_cli/src/cli/fv_cmd.rs | 60 ++++++++++++------- 10 files changed, 64 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71c0b7a8f67..5c7a9ac1fb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3374,6 +3374,7 @@ dependencies = [ "dirs", "fm", "fs2", + "fv_bridge", "fxhash", "iai", "insta", @@ -3422,6 +3423,7 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", + "vir", ] [[package]] @@ -3898,7 +3900,6 @@ dependencies = [ "cfg-if", "chrono", "fm", - "formal_verification", "function_name", "fxhash", "im", diff --git a/Cargo.toml b/Cargo.toml index 424024c4a78..d27e9dfd288 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,6 +158,7 @@ codespan-reporting = "0.11.1" # Formal verification vir = {git = "https://github.com/Aristotelis2002/verus-lib", branch = "synced_main"} formal_verification = { path = "compiler/formal_verification"} +fv_bridge = { path = "compiler/fv_bridge" } # Benchmarking criterion = "^0.5.0" diff --git a/compiler/formal_verification/src/parse/mod.rs b/compiler/formal_verification/src/parse/mod.rs index 6835e902979..78b444209c1 100644 --- a/compiler/formal_verification/src/parse/mod.rs +++ b/compiler/formal_verification/src/parse/mod.rs @@ -1,6 +1,6 @@ use noirc_errors::{Location, Span}; use noirc_evaluator::vir::vir_gen::{ - build_span_no_id, + Attribute, build_span_no_id, expr_to_vir::{expr::function_name_to_vir_fun, types::ast_type_to_vir_type}, }; use noirc_frontend::{ @@ -105,12 +105,6 @@ fn build_expr( SpannedTyped::new(&span, &Arc::new(typx), exprx) } -pub enum Attribute { - Ghost, - Ensures(Expr), - Requires(Expr), -} - // TODO: variable/function rustc ids // TODO: function signature diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 990f7b57b0f..424b30d3724 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -1,10 +1,11 @@ use std::collections::{BTreeMap, HashMap}; use fm::FileId; -use formal_verification::parse::{Attribute, parse_attribute}; +use formal_verification::parse::parse_attribute; use iter_extended::vecmap; use noirc_driver::{CompilationResult, CompileError, CompileOptions, check_crate}; use noirc_errors::{CustomDiagnostic, Location}; +use noirc_evaluator::vir::vir_gen::Attribute; use noirc_evaluator::{ errors::{RuntimeError, SsaReport}, vir::{create_verus_vir_with_ready_annotations, vir_gen::BuildingKrateError}, @@ -174,7 +175,14 @@ fn modified_monomorphize( .into_iter() .map(|(annotation_body, location)| { //TODO(totel): After error types are introduced remove the `unwrap` and switch to `?` - parse_attribute(annotation_body, location, function, &globals).unwrap() + parse_attribute( + annotation_body, + location, + function, + &globals, + &monomorphizer.finished_functions, + ) + .unwrap() }) .collect(), ) diff --git a/compiler/noirc_evaluator/Cargo.toml b/compiler/noirc_evaluator/Cargo.toml index 7dbb2eb5b4f..d3cfe324574 100644 --- a/compiler/noirc_evaluator/Cargo.toml +++ b/compiler/noirc_evaluator/Cargo.toml @@ -36,7 +36,6 @@ vec-collections = "0.4.3" petgraph.workspace = true fm.workspace = true vir.workspace = true -formal_verification.workspace = true [dev-dependencies] proptest.workspace = true diff --git a/compiler/noirc_evaluator/src/vir/mod.rs b/compiler/noirc_evaluator/src/vir/mod.rs index 9429cc8063c..1b70916c195 100644 --- a/compiler/noirc_evaluator/src/vir/mod.rs +++ b/compiler/noirc_evaluator/src/vir/mod.rs @@ -1,6 +1,6 @@ -use formal_verification::parse::Attribute; use noirc_frontend::monomorphization::ast::{FuncId, Program}; use vir::ast::Krate; +use vir_gen::Attribute; use vir_gen::{BuildingKrateError, build_krate}; use crate::vir::{ diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/function.rs b/compiler/noirc_evaluator/src/vir/vir_gen/function.rs index d7fc82af072..31225d7214e 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/function.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/function.rs @@ -7,7 +7,7 @@ use super::{ BuildingKrateError, attribute::{func_ensures_to_vir_expr, func_requires_to_vir_expr}, }; -use formal_verification::parse::Attribute; +use crate::vir::vir_gen::Attribute; use noirc_errors::Location; use noirc_frontend::monomorphization::ast::{ Expression, Function, GlobalId, MonomorphizedFvAttribute, Type, diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs b/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs index bd93b7a0a07..69836bd8117 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/mod.rs @@ -3,14 +3,13 @@ pub mod expr_to_vir; pub mod function; pub mod globals; -use formal_verification::parse::Attribute; use function::build_funx; use noirc_errors::Location; use noirc_frontend::monomorphization::ast::{FuncId, Program}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fmt::Display, sync::Arc}; use vir::{ - ast::{Krate, KrateX, ModuleX, PathX}, + ast::{Expr, Krate, KrateX, ModuleX, PathX}, def::Spanned, messages::Span, }; @@ -20,6 +19,12 @@ use crate::vir::vir_gen::{ globals::build_global_const_x, }; +pub enum Attribute { + Ghost, + Ensures(Expr), + Requires(Expr), +} + pub fn encode_span_to_string(location: Location) -> String { let stringified_span: String = format!("{}, {}", location.span.start(), location.span.end()); let stringified_file_id: String = format!("{}", location.file.as_usize()); diff --git a/tooling/nargo_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index ee6da9fe1fe..b9539288d10 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -87,6 +87,10 @@ tracing-appender = "0.2.3" clap_complete = "4.5.36" fs2 = "0.4.3" +# Formal Verification +fv_bridge.workspace = true +vir.workspace = true + [target.'cfg(not(unix))'.dependencies] tokio-util = { version = "0.7.8", features = ["compat"] } diff --git a/tooling/nargo_cli/src/cli/fv_cmd.rs b/tooling/nargo_cli/src/cli/fv_cmd.rs index df95527b485..b7914e4ff2a 100644 --- a/tooling/nargo_cli/src/cli/fv_cmd.rs +++ b/tooling/nargo_cli/src/cli/fv_cmd.rs @@ -5,12 +5,14 @@ use std::{ use clap::Args; use fm::{FileId, FileManager, FileMap}; +use fv_bridge::compile_and_build_vir_krate; use nargo::{ops::report_errors, prepare_package, workspace::Workspace}; use nargo_toml::PackageSelection; -use noirc_driver::{CompileOptions, CompiledProgram, link_to_debug_crate}; +use noirc_driver::{CompileOptions, link_to_debug_crate}; use noirc_errors::{CustomDiagnostic, DiagnosticKind, Location, Span, reporter::ReportedErrors}; use noirc_frontend::debug::DebugInstrumenter; use serde::Deserialize; +use vir::ast::Krate; use crate::{cli::compile_cmd::parse_workspace, errors::CliError}; @@ -31,6 +33,10 @@ pub(crate) struct FormalVerifyCommand { #[arg(long, hide = true)] pub show_vir: bool, + /// Use the new syntax for FV annotations + #[arg(long)] + pub new_syntax: bool, + // Flags which will be propagated to the Venir binary #[clap(last = true)] venir_flags: Vec, @@ -58,24 +64,39 @@ pub(crate) fn run(args: FormalVerifyCommand, workspace: Workspace) -> Result<(), context.package_build_path = workspace.package_build_path(package); context.perform_formal_verification = true; - let compiled_program = - noirc_driver::compile_main(&mut context, crate_id, &args.compile_options, None); - - // We want to formally verify only compilable programs - let compiled_program = report_errors( - compiled_program, - &workspace_file_manager, - args.compile_options.deny_warnings, - true, // We don't want to report compile related warnings - )?; - + let krate: Krate = if args.new_syntax { + // Compile with new syntax and report errors + report_errors( + compile_and_build_vir_krate(&mut context, crate_id, &args.compile_options), + &workspace_file_manager, + args.compile_options.deny_warnings, + true, + )? + } else { + // Compile with old syntax, report errors, then extract and unwrap VIR + let compiled = report_errors( + noirc_driver::compile_main(&mut context, crate_id, &args.compile_options, None), + &workspace_file_manager, + args.compile_options.deny_warnings, + true, + )?; + + let vir_result = compiled.verus_vir + .ok_or_else(|| CliError::Generic("Failed to generate VIR with no specific error".into()))?; + + let krate = vir_result.map_err(|e| CliError::Generic(e.to_string()))?; + + krate + }; + + // Shared post-processing if args.show_vir { println!("Generated VIR:"); - println!("{:#?}", compiled_program.verus_vir); + println!("{:#?}", krate); } - + z3_verify( - compiled_program, + krate, &workspace_file_manager, args.compile_options.deny_warnings, &args.venir_flags, @@ -88,18 +109,11 @@ pub(crate) fn run(args: FormalVerifyCommand, workspace: Workspace) -> Result<(), /// Runs the Venir binary and passes the compiled program in VIR format to it /// Reports all errors produced during Venir (SMT solver) verification fn z3_verify( - compiled_program: CompiledProgram, + krate: Krate, workspace_file_manager: &FileManager, deny_warnings: bool, venir_args: &Vec, ) -> Result<(), CliError> { - let krate = compiled_program - .verus_vir - .ok_or_else(|| { - CliError::Generic(String::from("Failed to generate VIR with no specific error")) - })? - .map_err(|e| CliError::Generic(e.to_string()))?; - let serialized_vir_krate = serde_json::to_string(&krate).expect("Failed to serialize"); // Run the Venir binary which is used for verifying the vir_krate input. From 93bcb3be9da323559f54871e05c23c54a01639e9 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 11 Jul 2025 15:51:57 +0300 Subject: [PATCH 08/86] chore(tests): Change the syntax of the tests Move to the new FV annotation syntax for the formal verification tests. --- .../src/main.nr | 2 +- .../src/main.nr | 2 +- .../src/main.nr | 2 +- .../fv_attribute_type_check_1/src/main.nr | 2 +- .../fv_attribute_type_check_2/src/main.nr | 2 +- .../fv_attribute_type_check_3/src/main.nr | 2 +- .../array_as_attribute/src/main.nr | 8 ++--- .../array_as_result_attr/src/main.nr | 2 +- .../array_as_result_attr_copy/src/main.nr | 2 +- .../array_as_result_attrs_copies/src/main.nr | 2 +- .../array_as_result_const/src/main.nr | 2 +- .../array_as_result_global/src/main.nr | 2 +- .../array_copy/src/main.nr | 2 +- .../array_set_1/src/main.nr | 4 +-- .../src/main.nr | 4 +-- .../array_set_composite_types_1/src/main.nr | 4 +-- .../array_set_composite_types_3/src/main.nr | 4 +-- .../array_set_simple/src/main.nr | 2 +- .../bitshifts/src/main.nr | 10 +++--- .../formal_verify_success/boolean/src/main.nr | 8 ++--- .../call_struct_method/src/main.nr | 2 +- .../formal_verify_success/cast/src/main.nr | 2 +- .../cast_from_to_field/src/main.nr | 2 +- .../comparison/src/main.nr | 12 +++---- .../exists_big_element_sum/src/main.nr | 4 +-- .../exists_zero_in_array/src/main.nr | 4 +-- .../field_arithmetic/src/main.nr | 8 ++--- .../field_minus_one_is_max/src/main.nr | 2 +- .../field_wrapped_overflow_1/src/main.nr | 2 +- .../field_wrapped_overflow_2/src/main.nr | 2 +- .../field_wrapped_overflow_3/src/main.nr | 4 +-- .../field_wrapped_overflow_4/src/main.nr | 2 +- .../field_wrapped_overflow_5/src/main.nr | 2 +- .../for_loop_array_const/src/main.nr | 2 +- .../for_loop_blocks/src/main.nr | 2 +- .../for_loop_nested_complex/src/main.nr | 2 +- .../for_loop_nested_simple/src/main.nr | 2 +- .../for_loop_simple/src/main.nr | 2 +- .../for_loop_structure_const/src/main.nr | 2 +- .../for_loop_tuple_const/src/main.nr | 2 +- .../forall_max_is_max/src/main.nr | 4 +-- .../forall_structure/src/main.nr | 2 +- .../forall_sum_of_evens/src/main.nr | 4 +-- .../func_call_in_if/src/main.nr | 2 +- .../func_call_in_if_2/src/main.nr | 2 +- .../func_call_in_if_3/src/main.nr | 2 +- .../func_call_in_if_4/src/main.nr | 2 +- .../src/main.nr | 2 +- .../src/main.nr | 2 +- .../src/main.nr | 2 +- .../fv_attribute_type_check_1/src/main.nr | 2 +- .../fv_attribute_type_check_2/src/main.nr | 2 +- .../fv_attribute_type_check_3/src/main.nr | 2 +- .../fv_std_old/src/main.nr | 8 ++--- .../identity_function/src/main.nr | 2 +- .../if_overflow_2/src/main.nr | 2 +- .../implication_operator_1/src/main.nr | 2 +- .../implication_operator_2/src/main.nr | 2 +- .../index_array/src/main.nr | 2 +- .../src/main.nr | 2 +- .../src/main.nr | 4 +-- .../src/main.nr | 4 +-- .../src/main.nr | 4 +-- .../src/main.nr | 4 +-- .../src/main.nr | 4 +-- .../index_var_array_with_constant/src/main.nr | 2 +- .../index_var_array_with_var/src/main.nr | 4 +-- .../integer_and/src/main.nr | 16 ++++----- .../integer_consts/src/main.nr | 12 +++---- .../integer_div/src/main.nr | 34 +++++++++---------- .../integer_mod/src/main.nr | 34 +++++++++---------- .../integer_not/src/main.nr | 34 +++++++++---------- .../integer_or/src/main.nr | 16 ++++----- .../integer_sub/src/main.nr | 34 +++++++++---------- .../integer_sum/src/main.nr | 34 +++++++++---------- .../integer_xor/src/main.nr | 16 ++++----- .../formal_verify_success/is_even/src/main.nr | 4 +-- .../is_power_of_2/src/main.nr | 6 ++-- .../lhs_tuple_access/src/main.nr | 2 +- .../mutate_array_of_tuples/src/main.nr | 6 ++-- .../operation_comutativity/src/main.nr | 10 +++--- .../formal_verify_success/struct/src/main.nr | 4 +-- .../struct_return/src/main.nr | 10 +++--- .../struct_return_basic/src/main.nr | 2 +- .../tuple_deconstruct/src/main.nr | 2 +- .../tuple_deconstruct_2/src/main.nr | 2 +- .../tuple_return/src/main.nr | 10 +++--- .../tuple_return_basic/src/main.nr | 6 ++-- .../formal_verify_success/tuples/src/main.nr | 4 +-- 89 files changed, 252 insertions(+), 252 deletions(-) diff --git a/test_programs/formal_verify_failure/fv_attribute_name_resolution_1/src/main.nr b/test_programs/formal_verify_failure/fv_attribute_name_resolution_1/src/main.nr index bca9f6af325..446cc24727e 100644 --- a/test_programs/formal_verify_failure/fv_attribute_name_resolution_1/src/main.nr +++ b/test_programs/formal_verify_failure/fv_attribute_name_resolution_1/src/main.nr @@ -2,7 +2,7 @@ fn main(x: Field, y: pub Field) { assert(x != y); } -#[requires(y > 5)] +#['requires(y > 5)] fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_failure/fv_attribute_name_resolution_2/src/main.nr b/test_programs/formal_verify_failure/fv_attribute_name_resolution_2/src/main.nr index 3bf470570f6..1751d2004d9 100644 --- a/test_programs/formal_verify_failure/fv_attribute_name_resolution_2/src/main.nr +++ b/test_programs/formal_verify_failure/fv_attribute_name_resolution_2/src/main.nr @@ -2,7 +2,7 @@ fn main(x: Field, y: pub Field) { assert(x != y); } -#[requires(result > 5)] // result can be only used in the ensures attribute +#['requires(result > 5)] // result can be only used in the ensures attribute fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_failure/fv_attribute_name_resolution_3/src/main.nr b/test_programs/formal_verify_failure/fv_attribute_name_resolution_3/src/main.nr index c73980635bf..e2e18936cfd 100644 --- a/test_programs/formal_verify_failure/fv_attribute_name_resolution_3/src/main.nr +++ b/test_programs/formal_verify_failure/fv_attribute_name_resolution_3/src/main.nr @@ -2,7 +2,7 @@ fn main(x: Field, y: pub Field) { assert(x != y); } -#[ensures(result > y)] +#['ensures(result > y)] fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_failure/fv_attribute_type_check_1/src/main.nr b/test_programs/formal_verify_failure/fv_attribute_type_check_1/src/main.nr index 1782692eff6..e45f6de79f2 100644 --- a/test_programs/formal_verify_failure/fv_attribute_type_check_1/src/main.nr +++ b/test_programs/formal_verify_failure/fv_attribute_type_check_1/src/main.nr @@ -2,7 +2,7 @@ fn main(x: Field, y: pub Field) { assert(x != y); } -#[requires(x)] +#['requires(x)] fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_failure/fv_attribute_type_check_2/src/main.nr b/test_programs/formal_verify_failure/fv_attribute_type_check_2/src/main.nr index 8885c56169f..2b600bc322b 100644 --- a/test_programs/formal_verify_failure/fv_attribute_type_check_2/src/main.nr +++ b/test_programs/formal_verify_failure/fv_attribute_type_check_2/src/main.nr @@ -2,7 +2,7 @@ fn main(x: Field, y: pub Field) { assert(x != y); } -#[ensures(result)] +#['ensures(result)] fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_failure/fv_attribute_type_check_3/src/main.nr b/test_programs/formal_verify_failure/fv_attribute_type_check_3/src/main.nr index 8a12d54ebc5..e70c2270fca 100644 --- a/test_programs/formal_verify_failure/fv_attribute_type_check_3/src/main.nr +++ b/test_programs/formal_verify_failure/fv_attribute_type_check_3/src/main.nr @@ -2,7 +2,7 @@ fn main(x: Field, y: pub Field) { assert(x != y); } -#[requires(5)] +#['requires(5)] fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_success/array_as_attribute/src/main.nr b/test_programs/formal_verify_success/array_as_attribute/src/main.nr index 3779b9c26b4..93a21246367 100644 --- a/test_programs/formal_verify_success/array_as_attribute/src/main.nr +++ b/test_programs/formal_verify_success/array_as_attribute/src/main.nr @@ -5,22 +5,22 @@ fn main() { let _ = foo4([1, 0, 8, 0, 0]); } -#[requires((x[1] == 1) & (x[0] == 1) & (x[2] == 8))] +#['requires((x[1] == 1) & (x[0] == 1) & (x[2] == 8))] fn foo1(x: [u32; 5]) { assert(x[2] == 8); } -#[requires((x[1] == 1) & (x[0] == 8) & (x[2] == 8))] +#['requires((x[1] == 1) & (x[0] == 8) & (x[2] == 8))] fn foo2(x: [u32; 5]) { assert(x[2] == 8); } -#[requires((x[1] == 8) & (x[0] == 1) & (x[2] == 8))] +#['requires((x[1] == 8) & (x[0] == 1) & (x[2] == 8))] fn foo3(x: [u32; 5]) { assert(x[2] == 8); } -#[requires((x[0] == 1) & (x[2] == 8))] +#['requires((x[0] == 1) & (x[2] == 8))] fn foo4(x: [u32; 5]) { assert(x[2] == 8); } diff --git a/test_programs/formal_verify_success/array_as_result_attr/src/main.nr b/test_programs/formal_verify_success/array_as_result_attr/src/main.nr index 730a6c8abc3..a12faeb2388 100644 --- a/test_programs/formal_verify_success/array_as_result_attr/src/main.nr +++ b/test_programs/formal_verify_success/array_as_result_attr/src/main.nr @@ -1,4 +1,4 @@ -#[ensures((result[0] == x[0]) & (result[1] == x[1]))] +#['ensures((result[0] == x[0]) & (result[1] == x[1]))] fn main(x: [u32; 2]) -> pub [u32; 2] { x } diff --git a/test_programs/formal_verify_success/array_as_result_attr_copy/src/main.nr b/test_programs/formal_verify_success/array_as_result_attr_copy/src/main.nr index fc153a5938d..fce93e63ea4 100644 --- a/test_programs/formal_verify_success/array_as_result_attr_copy/src/main.nr +++ b/test_programs/formal_verify_success/array_as_result_attr_copy/src/main.nr @@ -1,4 +1,4 @@ -#[ensures((result[0] == x[0]) & (result[1] == x[1]))] +#['ensures((result[0] == x[0]) & (result[1] == x[1]))] fn main(x: [u32; 2]) -> pub [u32; 2] { [x[0], x[1]] } diff --git a/test_programs/formal_verify_success/array_as_result_attrs_copies/src/main.nr b/test_programs/formal_verify_success/array_as_result_attrs_copies/src/main.nr index 1fee3a9f756..db8b7c84229 100644 --- a/test_programs/formal_verify_success/array_as_result_attrs_copies/src/main.nr +++ b/test_programs/formal_verify_success/array_as_result_attrs_copies/src/main.nr @@ -1,4 +1,4 @@ -#[ensures((result[0] == x) & (result[1] == y))] +#['ensures((result[0] == x) & (result[1] == y))] fn main(x: u32, y: u32) -> pub [u32; 2] { [x, y] } diff --git a/test_programs/formal_verify_success/array_as_result_const/src/main.nr b/test_programs/formal_verify_success/array_as_result_const/src/main.nr index 4787154b56e..bb61756a445 100644 --- a/test_programs/formal_verify_success/array_as_result_const/src/main.nr +++ b/test_programs/formal_verify_success/array_as_result_const/src/main.nr @@ -1,4 +1,4 @@ -#[ensures((result[0] == 64) & (result[1] == 820))] +#['ensures((result[0] == 64) & (result[1] == 820))] fn main() -> pub [u32; 2] { [64, 820] } diff --git a/test_programs/formal_verify_success/array_as_result_global/src/main.nr b/test_programs/formal_verify_success/array_as_result_global/src/main.nr index 08591ecb092..3acf373328b 100644 --- a/test_programs/formal_verify_success/array_as_result_global/src/main.nr +++ b/test_programs/formal_verify_success/array_as_result_global/src/main.nr @@ -1,4 +1,4 @@ -#[ensures((result[0] == 64))] +#['ensures((result[0] == 64))] fn main() -> pub [u32; 2] { out } diff --git a/test_programs/formal_verify_success/array_copy/src/main.nr b/test_programs/formal_verify_success/array_copy/src/main.nr index 9ddf32280f9..396f84fc364 100644 --- a/test_programs/formal_verify_success/array_copy/src/main.nr +++ b/test_programs/formal_verify_success/array_copy/src/main.nr @@ -1,5 +1,5 @@ // ArrayGet is not yet implemented -#[ensures((result[0] == x[0]) & (result[1] == 1) & (result[2] == x[2]))] +#['ensures((result[0] == x[0]) & (result[1] == 1) & (result[2] == x[2]))] fn main(x: [u8; 3]) -> pub [u8; 3] { [x[0], 1, x[2]] } diff --git a/test_programs/formal_verify_success/array_set_1/src/main.nr b/test_programs/formal_verify_success/array_set_1/src/main.nr index 61101c3b38d..1b0efa7957a 100644 --- a/test_programs/formal_verify_success/array_set_1/src/main.nr +++ b/test_programs/formal_verify_success/array_set_1/src/main.nr @@ -1,5 +1,5 @@ -#[requires(i < 5)] -#[ensures(result[i] == y)] +#['requires(i < 5)] +#['ensures(result[i] == y)] fn main(mut x: [u32; 5], i: u32, y: u32) -> pub [u32; 5] { x[i] = y; x diff --git a/test_programs/formal_verify_success/array_set_complex_composite_type/src/main.nr b/test_programs/formal_verify_success/array_set_complex_composite_type/src/main.nr index 9a9675b1ce2..d67073724d9 100644 --- a/test_programs/formal_verify_success/array_set_complex_composite_type/src/main.nr +++ b/test_programs/formal_verify_success/array_set_complex_composite_type/src/main.nr @@ -12,8 +12,8 @@ struct B { y: ([A;19],(i16, bool, (i8, [(C, u32);5], u32), u16)) } -#[requires(x < 2)] -#[ensures(result[x].y.1.2.1[x].0.b[x].third.0 == 5)] +#['requires(x < 2)] +#['ensures(result[x].y.1.2.1[x].0.b[x].third.0 == 5)] fn main(mut arr: [B;2], x: u32) -> pub [B;2] { arr[x].y.1.2.1[x].0.b[x].third.0 = 5; arr diff --git a/test_programs/formal_verify_success/array_set_composite_types_1/src/main.nr b/test_programs/formal_verify_success/array_set_composite_types_1/src/main.nr index 0cdce4a1ff2..31222425e1f 100644 --- a/test_programs/formal_verify_success/array_set_composite_types_1/src/main.nr +++ b/test_programs/formal_verify_success/array_set_composite_types_1/src/main.nr @@ -1,5 +1,5 @@ -#[requires(index < 5)] -#[ensures(result[index].1 == y)] +#['requires(index < 5)] +#['ensures(result[index].1 == y)] fn main(mut x: [(u32, i32); 5], y: i32, index: u32) -> pub [(u32, i32); 5] { x[index].1 = y; x diff --git a/test_programs/formal_verify_success/array_set_composite_types_3/src/main.nr b/test_programs/formal_verify_success/array_set_composite_types_3/src/main.nr index e33cf20ef81..7e7213356d3 100644 --- a/test_programs/formal_verify_success/array_set_composite_types_3/src/main.nr +++ b/test_programs/formal_verify_success/array_set_composite_types_3/src/main.nr @@ -3,8 +3,8 @@ struct A { second: (u32, u32, u32), } -#[requires(x < 1)] -#[ensures(result[x + 1].second.2 == 1)] +#['requires(x < 1)] +#['ensures(result[x + 1].second.2 == 1)] fn main(mut arr: [A; 2], x: u32) -> pub [A; 2] { arr[x + 1].second.2 = 1; arr diff --git a/test_programs/formal_verify_success/array_set_simple/src/main.nr b/test_programs/formal_verify_success/array_set_simple/src/main.nr index 8f2b70e471d..03fb5bedfcd 100644 --- a/test_programs/formal_verify_success/array_set_simple/src/main.nr +++ b/test_programs/formal_verify_success/array_set_simple/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 15)] +#['ensures(result == 15)] fn main(x: [u32; 5], i: u32, y: u32) -> pub u32 { let mut arr = x; arr[3] = 15; diff --git a/test_programs/formal_verify_success/bitshifts/src/main.nr b/test_programs/formal_verify_success/bitshifts/src/main.nr index 32ce9154aa0..ce10ab556ac 100644 --- a/test_programs/formal_verify_success/bitshifts/src/main.nr +++ b/test_programs/formal_verify_success/bitshifts/src/main.nr @@ -1,17 +1,17 @@ -#[requires((x < 10) & (y < 3))] +#['requires((x < 10) & (y < 3))] fn main(x: u32, y: u8) { let _ = lbs(x, y); let _ = rbs(x, y); } -#[requires((x < 10) & (y < 3))] -#[ensures(result == x << y)] +#['requires((x < 10) & (y < 3))] +#['ensures(result == x << y)] fn lbs(x: u32, y: u8) -> u32 { x << y } -#[requires((x < 10) & (y < 3))] -#[ensures(result == x >> y)] +#['requires((x < 10) & (y < 3))] +#['ensures(result == x >> y)] fn rbs(x: u32, y: u8) -> u32 { x >> y } diff --git a/test_programs/formal_verify_success/boolean/src/main.nr b/test_programs/formal_verify_success/boolean/src/main.nr index 7fe5b037a76..b210ccefb2a 100644 --- a/test_programs/formal_verify_success/boolean/src/main.nr +++ b/test_programs/formal_verify_success/boolean/src/main.nr @@ -5,22 +5,22 @@ fn main(x: bool, y: bool) { let _ = not(x); } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn xor(x: bool, y: bool) -> bool { x ^ y } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn and(x: bool, y: bool) -> bool { x & y } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn or(x: bool, y: bool) -> bool { x | y } -#[ensures(result == ! x)] +#['ensures(result == ! x)] fn not(x: bool) -> bool { ! x } diff --git a/test_programs/formal_verify_success/call_struct_method/src/main.nr b/test_programs/formal_verify_success/call_struct_method/src/main.nr index 22c011eb973..f4bd6ebdd77 100644 --- a/test_programs/formal_verify_success/call_struct_method/src/main.nr +++ b/test_programs/formal_verify_success/call_struct_method/src/main.nr @@ -4,7 +4,7 @@ struct A { } impl A { - #[ensures(result.balance == self.balance / 2)] + #['ensures(result.balance == self.balance / 2)] fn foo(mut self) -> Self { self.balance = self.balance / 2; self diff --git a/test_programs/formal_verify_success/cast/src/main.nr b/test_programs/formal_verify_success/cast/src/main.nr index 4fd90704d50..d1034305bed 100644 --- a/test_programs/formal_verify_success/cast/src/main.nr +++ b/test_programs/formal_verify_success/cast/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == (x as u32) as u8)] +#['ensures(result == (x as u32) as u8)] fn main(x: u16) -> pub u8 { (x as u32) as u8 } diff --git a/test_programs/formal_verify_success/cast_from_to_field/src/main.nr b/test_programs/formal_verify_success/cast_from_to_field/src/main.nr index 0cc81a946e3..9edd569c453 100644 --- a/test_programs/formal_verify_success/cast_from_to_field/src/main.nr +++ b/test_programs/formal_verify_success/cast_from_to_field/src/main.nr @@ -1,4 +1,4 @@ -#[requires((x == 0) | (x == 1))] +#['requires((x == 0) | (x == 1))] fn main(x: Field) -> pub Field { ((x as u32) + 1) as Field } diff --git a/test_programs/formal_verify_success/comparison/src/main.nr b/test_programs/formal_verify_success/comparison/src/main.nr index 01153fe0c05..d1119041641 100644 --- a/test_programs/formal_verify_success/comparison/src/main.nr +++ b/test_programs/formal_verify_success/comparison/src/main.nr @@ -7,32 +7,32 @@ fn main(x: u8, y: u8) { let _ = neq(x, y); } -#[ensures(result == (x < y))] +#['ensures(result == (x < y))] fn lt(x: u8, y: u8) -> bool { x < y } -#[ensures(result == (x <= y))] +#['ensures(result == (x <= y))] fn lte(x: u8, y: u8) -> bool { x <= y } -#[ensures(result == (x > y))] +#['ensures(result == (x > y))] fn gt(x: u8, y: u8) -> bool { x > y } -#[ensures(result == (x >= y))] +#['ensures(result == (x >= y))] fn gte(x: u8, y: u8) -> bool { x >= y } -#[ensures(result == (x == y))] +#['ensures(result == (x == y))] fn eq(x: u8, y: u8) -> bool { x == y } -#[ensures(result == (x != y))] +#['ensures(result == (x != y))] fn neq(x: u8, y: u8) -> bool { x != y } diff --git a/test_programs/formal_verify_success/exists_big_element_sum/src/main.nr b/test_programs/formal_verify_success/exists_big_element_sum/src/main.nr index 9e1df918091..5ac49a1ef7d 100644 --- a/test_programs/formal_verify_success/exists_big_element_sum/src/main.nr +++ b/test_programs/formal_verify_success/exists_big_element_sum/src/main.nr @@ -1,5 +1,5 @@ -#[requires(exists(|i| (0 <= i) & (i < 20) & arr[i] > 100))] -#[ensures(result > 100)] +#['requires(exists(|i| (0 <= i) & (i < 20) & arr[i] > 100))] +#['ensures(result > 100)] // We require that an element bigger than 100 exists in the array. // Therefore we expect the sum to be bigger than 100. fn main(arr: [u16; 20]) -> pub u64 { diff --git a/test_programs/formal_verify_success/exists_zero_in_array/src/main.nr b/test_programs/formal_verify_success/exists_zero_in_array/src/main.nr index 7f56c7c52aa..9ccef827f47 100644 --- a/test_programs/formal_verify_success/exists_zero_in_array/src/main.nr +++ b/test_programs/formal_verify_success/exists_zero_in_array/src/main.nr @@ -1,5 +1,5 @@ -#[requires(exists(|i| (0 <= i) & (i < 7) & (arr[i] == 0)))] -#[ensures(result == 0)] +#['requires(exists(|i| (0 <= i) & (i < 7) & (arr[i] == 0)))] +#['ensures(result == 0)] fn main(arr: [u8; 7]) -> pub u64 { let mut mul: u64 = 1; for i in 0..7 { diff --git a/test_programs/formal_verify_success/field_arithmetic/src/main.nr b/test_programs/formal_verify_success/field_arithmetic/src/main.nr index c003aab277c..54d0a9cafee 100644 --- a/test_programs/formal_verify_success/field_arithmetic/src/main.nr +++ b/test_programs/formal_verify_success/field_arithmetic/src/main.nr @@ -7,22 +7,22 @@ fn main(x: Field, y: Field) { let _ = div(x, y); } -#[ensures(result == x + y)] +#['ensures(result == x + y)] fn plus(x: Field, y: Field) -> Field { x + y } -#[ensures(result == x - y)] +#['ensures(result == x - y)] fn minus(x: Field, y: Field) -> Field { x - y } -#[ensures(result == x * y)] +#['ensures(result == x * y)] fn mult(x: Field, y: Field) -> Field { x * y } -#[ensures(result == x / y)] +#['ensures(result == x / y)] fn div(x: Field, y: Field) -> Field { x / y } diff --git a/test_programs/formal_verify_success/field_minus_one_is_max/src/main.nr b/test_programs/formal_verify_success/field_minus_one_is_max/src/main.nr index 7c24ba5783e..246dcd8d6df 100644 --- a/test_programs/formal_verify_success/field_minus_one_is_max/src/main.nr +++ b/test_programs/formal_verify_success/field_minus_one_is_max/src/main.nr @@ -1,4 +1,4 @@ -#[requires(x == -1)] +#['requires(x == -1)] fn main(x: Field) { let z: Field = 21888242871839275222246405745257275088548364400416034343698204186575808495616; assert(x == z); diff --git a/test_programs/formal_verify_success/field_wrapped_overflow_1/src/main.nr b/test_programs/formal_verify_success/field_wrapped_overflow_1/src/main.nr index 4dab13353c8..e30110dc8b5 100644 --- a/test_programs/formal_verify_success/field_wrapped_overflow_1/src/main.nr +++ b/test_programs/formal_verify_success/field_wrapped_overflow_1/src/main.nr @@ -1,7 +1,7 @@ // The prime that defines the integer field is // p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 // Adding 1 to p-1 should result in 0. -#[requires((x == 0) & (y == 1))] +#['requires((x == 0) & (y == 1))] fn main(x: Field, y: Field) { let z: Field = 21888242871839275222246405745257275088548364400416034343698204186575808495616; assert(z + y == x); diff --git a/test_programs/formal_verify_success/field_wrapped_overflow_2/src/main.nr b/test_programs/formal_verify_success/field_wrapped_overflow_2/src/main.nr index f3930c6ef04..03917fa10c5 100644 --- a/test_programs/formal_verify_success/field_wrapped_overflow_2/src/main.nr +++ b/test_programs/formal_verify_success/field_wrapped_overflow_2/src/main.nr @@ -1,7 +1,7 @@ // The prime that defines the integer field is // p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 // Adding 1 to p-1 should result in 0. y is 1 and x is 0 in Prover.toml. -#[requires((x == 9) & (y == 10))] +#['requires((x == 9) & (y == 10))] fn main(x: Field, y: Field) { let z: Field = 21888242871839275222246405745257275088548364400416034343698204186575808495616; diff --git a/test_programs/formal_verify_success/field_wrapped_overflow_3/src/main.nr b/test_programs/formal_verify_success/field_wrapped_overflow_3/src/main.nr index 7b4c2b00098..47627d1f961 100644 --- a/test_programs/formal_verify_success/field_wrapped_overflow_3/src/main.nr +++ b/test_programs/formal_verify_success/field_wrapped_overflow_3/src/main.nr @@ -1,8 +1,8 @@ // The prime that defines the integer field is // p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 // Adding 1 to p-1 should result in 0. -#[requires((x == 21888242871839275222246405745257275088548364400416034343698204186575808495616) & (y == 1))] -#[ensures(result == x + y)] // Tests if Field sum in annotation is also correct. +#['requires((x == 21888242871839275222246405745257275088548364400416034343698204186575808495616) & (y == 1))] +#['ensures(result == x + y)] // Tests if Field sum in annotation is also correct. fn main(x: Field, y: Field) -> pub Field { x + y // Wraps around and the result should equal to zero. } diff --git a/test_programs/formal_verify_success/field_wrapped_overflow_4/src/main.nr b/test_programs/formal_verify_success/field_wrapped_overflow_4/src/main.nr index 68ed3330a16..7d5fca5c678 100644 --- a/test_programs/formal_verify_success/field_wrapped_overflow_4/src/main.nr +++ b/test_programs/formal_verify_success/field_wrapped_overflow_4/src/main.nr @@ -2,7 +2,7 @@ // p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 // Adding p-1 + p-1 + p-1 should result in p-3. // Goal is to prove (p - 3 + 3) == 0 -#[requires((x == 0) & (y == 21888242871839275222246405745257275088548364400416034343698204186575808495616))] +#['requires((x == 0) & (y == 21888242871839275222246405745257275088548364400416034343698204186575808495616))] fn main(x: Field, y: Field) { let z = y + y + y; assert(z + 3 == x); diff --git a/test_programs/formal_verify_success/field_wrapped_overflow_5/src/main.nr b/test_programs/formal_verify_success/field_wrapped_overflow_5/src/main.nr index bcc099f553d..00717437592 100644 --- a/test_programs/formal_verify_success/field_wrapped_overflow_5/src/main.nr +++ b/test_programs/formal_verify_success/field_wrapped_overflow_5/src/main.nr @@ -2,7 +2,7 @@ // p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 // If we have the value (p+1)/2 and we multiply it by 2 we will get p+1 // but if we divide p+1 by 2 we will not get the original value but we will get 0. -#[requires((x == 2) & (y == 0))] +#['requires((x == 2) & (y == 0))] fn main(x: Field, y: Field) { let z: Field = 10944121435919637611123202872628637544274182200208017171849102093287904247809; // ((p + 1) / 2) let w = z * x; // ((p + 1)/2) * 2 ==> w == p + 1 == 1 diff --git a/test_programs/formal_verify_success/for_loop_array_const/src/main.nr b/test_programs/formal_verify_success/for_loop_array_const/src/main.nr index 09def704f27..4ad7507f47c 100644 --- a/test_programs/formal_verify_success/for_loop_array_const/src/main.nr +++ b/test_programs/formal_verify_success/for_loop_array_const/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 10)] +#['ensures(result == 10)] fn main() -> pub u32 { let x = [2, 5]; let y = x[1]; diff --git a/test_programs/formal_verify_success/for_loop_blocks/src/main.nr b/test_programs/formal_verify_success/for_loop_blocks/src/main.nr index 676e04799d8..5a969ff7b35 100644 --- a/test_programs/formal_verify_success/for_loop_blocks/src/main.nr +++ b/test_programs/formal_verify_success/for_loop_blocks/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 10)] +#['ensures(result == 10)] fn main() -> pub u32 { let x = { let a = 3; diff --git a/test_programs/formal_verify_success/for_loop_nested_complex/src/main.nr b/test_programs/formal_verify_success/for_loop_nested_complex/src/main.nr index cb70ade3ef4..b662dbb515c 100644 --- a/test_programs/formal_verify_success/for_loop_nested_complex/src/main.nr +++ b/test_programs/formal_verify_success/for_loop_nested_complex/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 2)] +#['ensures(result == 2)] fn main() -> pub u32 { let x = 2; let y = 3; diff --git a/test_programs/formal_verify_success/for_loop_nested_simple/src/main.nr b/test_programs/formal_verify_success/for_loop_nested_simple/src/main.nr index 0a9f9c3583a..d9965abe3bc 100644 --- a/test_programs/formal_verify_success/for_loop_nested_simple/src/main.nr +++ b/test_programs/formal_verify_success/for_loop_nested_simple/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 10)] +#['ensures(result == 10)] fn main() -> pub u32 { let x = 3; let mut sum = 0; diff --git a/test_programs/formal_verify_success/for_loop_simple/src/main.nr b/test_programs/formal_verify_success/for_loop_simple/src/main.nr index 12414d91bda..ea2bf2f09ed 100644 --- a/test_programs/formal_verify_success/for_loop_simple/src/main.nr +++ b/test_programs/formal_verify_success/for_loop_simple/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 10)] +#['ensures(result == 10)] fn main() -> pub u32 { let x = 2 + 3; let mut sum = 0; diff --git a/test_programs/formal_verify_success/for_loop_structure_const/src/main.nr b/test_programs/formal_verify_success/for_loop_structure_const/src/main.nr index cab4df3f070..2d5e4c0dcb9 100644 --- a/test_programs/formal_verify_success/for_loop_structure_const/src/main.nr +++ b/test_programs/formal_verify_success/for_loop_structure_const/src/main.nr @@ -3,7 +3,7 @@ struct A { b: u32 } -#[ensures(result == 10)] +#['ensures(result == 10)] fn main() -> pub u32 { let x = A{a: 2, b: 5}; let y = x.b; diff --git a/test_programs/formal_verify_success/for_loop_tuple_const/src/main.nr b/test_programs/formal_verify_success/for_loop_tuple_const/src/main.nr index 578474233e9..c827b5e2624 100644 --- a/test_programs/formal_verify_success/for_loop_tuple_const/src/main.nr +++ b/test_programs/formal_verify_success/for_loop_tuple_const/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 10)] +#['ensures(result == 10)] fn main() -> pub u32 { let x = (2, 5); let y = x.1; diff --git a/test_programs/formal_verify_success/forall_max_is_max/src/main.nr b/test_programs/formal_verify_success/forall_max_is_max/src/main.nr index 48213a1ae50..9f129e104f1 100644 --- a/test_programs/formal_verify_success/forall_max_is_max/src/main.nr +++ b/test_programs/formal_verify_success/forall_max_is_max/src/main.nr @@ -1,5 +1,5 @@ -#[requires(forall(|i, j| (0 <= i) & (i < j) & (j < 15) ==> v[i] <= v[j]))] -#[ensures(forall(|i| (0 <= i) & (i < 15) ==> v[i] <= result))] +#['requires(forall(|i, j| (0 <= i) & (i < j) & (j < 15) ==> v[i] <= v[j]))] +#['ensures(forall(|i| (0 <= i) & (i < 15) ==> v[i] <= result))] fn main(v: [u64; 15], k: u64) -> pub u64 { v[14] // The array is sorted and this is the last element, therefore it's the maximum element. } diff --git a/test_programs/formal_verify_success/forall_structure/src/main.nr b/test_programs/formal_verify_success/forall_structure/src/main.nr index 5a4eefb352b..94a6ddf1510 100644 --- a/test_programs/formal_verify_success/forall_structure/src/main.nr +++ b/test_programs/formal_verify_success/forall_structure/src/main.nr @@ -2,7 +2,7 @@ struct A { id: Field, balance: u32, } -#[requires(forall(|i, j| (0 <= i) & (i < j) & (j < 2) +#['requires(forall(|i, j| (0 <= i) & (i < j) & (j < 2) ==> x[i].id != x[j].id))] fn main(x: [A; 2]) {} diff --git a/test_programs/formal_verify_success/forall_sum_of_evens/src/main.nr b/test_programs/formal_verify_success/forall_sum_of_evens/src/main.nr index a612e27502c..cdc9fdabc9b 100644 --- a/test_programs/formal_verify_success/forall_sum_of_evens/src/main.nr +++ b/test_programs/formal_verify_success/forall_sum_of_evens/src/main.nr @@ -1,6 +1,6 @@ -#[requires(forall(|i| (0 <= i) & (i < 10) ==> arr[i] % 2 == 0) +#['requires(forall(|i| (0 <= i) & (i < 10) ==> arr[i] % 2 == 0) & forall(|i| (0 <= i) & (i < 10) ==> arr[i] < 100) )] -#[ensures((result % 2 == 0) & (result < 1000))] +#['ensures((result % 2 == 0) & (result < 1000))] // The sum of an array which constist of even elements upper bounded by 100 // will be even and upper bounded by 1000 fn main(arr: [u32; 10]) -> pub u32 { diff --git a/test_programs/formal_verify_success/func_call_in_if/src/main.nr b/test_programs/formal_verify_success/func_call_in_if/src/main.nr index e683dbe04ab..7f69e0cfc17 100644 --- a/test_programs/formal_verify_success/func_call_in_if/src/main.nr +++ b/test_programs/formal_verify_success/func_call_in_if/src/main.nr @@ -2,7 +2,7 @@ fn main(x: u32) -> pub u32 { if x > 5 { foo(x) } else { x } } -#[requires(x > 5)] +#['requires(x > 5)] fn foo(x: u32) -> u32 { 0 } diff --git a/test_programs/formal_verify_success/func_call_in_if_2/src/main.nr b/test_programs/formal_verify_success/func_call_in_if_2/src/main.nr index fbd5c20f0af..6f65e35eab8 100644 --- a/test_programs/formal_verify_success/func_call_in_if_2/src/main.nr +++ b/test_programs/formal_verify_success/func_call_in_if_2/src/main.nr @@ -2,7 +2,7 @@ fn main(x: u32) -> pub (u32, u32) { if x > 5 { foo(x) } else { (x, x) } } -#[requires(x > 5)] +#['requires(x > 5)] fn foo(x: u32) -> (u32, u32) { (0, 0) } diff --git a/test_programs/formal_verify_success/func_call_in_if_3/src/main.nr b/test_programs/formal_verify_success/func_call_in_if_3/src/main.nr index 861503a10a9..36a9a593bf5 100644 --- a/test_programs/formal_verify_success/func_call_in_if_3/src/main.nr +++ b/test_programs/formal_verify_success/func_call_in_if_3/src/main.nr @@ -2,7 +2,7 @@ fn main(x: u32) -> pub [u32; 3] { if x > 5 { foo(x) } else { [x, x, x] } } -#[requires(x > 5)] +#['requires(x > 5)] fn foo(x: u32) -> [u32; 3] { [0, 0, 0] } diff --git a/test_programs/formal_verify_success/func_call_in_if_4/src/main.nr b/test_programs/formal_verify_success/func_call_in_if_4/src/main.nr index 9e131148e1c..2f02b75bb3b 100644 --- a/test_programs/formal_verify_success/func_call_in_if_4/src/main.nr +++ b/test_programs/formal_verify_success/func_call_in_if_4/src/main.nr @@ -7,5 +7,5 @@ fn main(x: u32) -> pub u32 { } } -#[requires(x > 5)] +#['requires(x > 5)] fn foo(x: u32) {} diff --git a/test_programs/formal_verify_success/fv_attribute_name_resolution_1/src/main.nr b/test_programs/formal_verify_success/fv_attribute_name_resolution_1/src/main.nr index 6b5fff5807b..b694e0d9c5e 100644 --- a/test_programs/formal_verify_success/fv_attribute_name_resolution_1/src/main.nr +++ b/test_programs/formal_verify_success/fv_attribute_name_resolution_1/src/main.nr @@ -1,6 +1,6 @@ fn main() {} -#[requires(x as u32 > 5)] // as u32 because fields can not be compared +#['requires(x as u32 > 5)] // as u32 because fields can not be compared fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_success/fv_attribute_name_resolution_2/src/main.nr b/test_programs/formal_verify_success/fv_attribute_name_resolution_2/src/main.nr index 711edd0209d..cc0b378f6b2 100644 --- a/test_programs/formal_verify_success/fv_attribute_name_resolution_2/src/main.nr +++ b/test_programs/formal_verify_success/fv_attribute_name_resolution_2/src/main.nr @@ -1,6 +1,6 @@ fn main() {} -#[ensures(result as u32 > 5)] // result can be only used in the ensures attribute +#['ensures(result as u32 > 5)] // result can be only used in the ensures attribute fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_success/fv_attribute_name_resolution_3/src/main.nr b/test_programs/formal_verify_success/fv_attribute_name_resolution_3/src/main.nr index 2f4d33d43b3..9eaabfdb421 100644 --- a/test_programs/formal_verify_success/fv_attribute_name_resolution_3/src/main.nr +++ b/test_programs/formal_verify_success/fv_attribute_name_resolution_3/src/main.nr @@ -1,6 +1,6 @@ fn main() {} -#[ensures(result == x + x)] +#['ensures(result == x + x)] fn foo(x: Field) -> Field { x + x } diff --git a/test_programs/formal_verify_success/fv_attribute_type_check_1/src/main.nr b/test_programs/formal_verify_success/fv_attribute_type_check_1/src/main.nr index 91da7fd3889..99abb903ed4 100644 --- a/test_programs/formal_verify_success/fv_attribute_type_check_1/src/main.nr +++ b/test_programs/formal_verify_success/fv_attribute_type_check_1/src/main.nr @@ -1,6 +1,6 @@ fn main() {} -#[requires(x)] +#['requires(x)] fn foo(x: bool) -> Field { 5 } diff --git a/test_programs/formal_verify_success/fv_attribute_type_check_2/src/main.nr b/test_programs/formal_verify_success/fv_attribute_type_check_2/src/main.nr index e6bfe101217..57897c1464d 100644 --- a/test_programs/formal_verify_success/fv_attribute_type_check_2/src/main.nr +++ b/test_programs/formal_verify_success/fv_attribute_type_check_2/src/main.nr @@ -1,6 +1,6 @@ fn main() {} -#[ensures(result)] +#['ensures(result)] fn foo() -> bool { true } diff --git a/test_programs/formal_verify_success/fv_attribute_type_check_3/src/main.nr b/test_programs/formal_verify_success/fv_attribute_type_check_3/src/main.nr index 51bb51d539f..ab571a88cf9 100644 --- a/test_programs/formal_verify_success/fv_attribute_type_check_3/src/main.nr +++ b/test_programs/formal_verify_success/fv_attribute_type_check_3/src/main.nr @@ -1,5 +1,5 @@ fn main() {} -#[requires(5 > 5)] +#['requires(5 > 5)] fn foo() {} diff --git a/test_programs/formal_verify_success/fv_std_old/src/main.nr b/test_programs/formal_verify_success/fv_std_old/src/main.nr index 8c75f8a5b3b..ff28d8ec3ef 100644 --- a/test_programs/formal_verify_success/fv_std_old/src/main.nr +++ b/test_programs/formal_verify_success/fv_std_old/src/main.nr @@ -1,14 +1,14 @@ use fv_std::old; -#[requires(x == 4)] -#[ensures(result == 5)] +#['requires(x == 4)] +#['ensures(result == 5)] fn main(mut x: u32) -> pub u32 { foo(&mut x); x } -#[requires(*old(x) == 4)] -#[ensures(*x == *old(x) + 1)] +#['requires(*old(x) == 4)] +#['ensures(*x == *old(x) + 1)] fn foo(x: &mut u32) { *x = *x + 1; } diff --git a/test_programs/formal_verify_success/identity_function/src/main.nr b/test_programs/formal_verify_success/identity_function/src/main.nr index bdf50e52ef8..2e2ca39ade3 100644 --- a/test_programs/formal_verify_success/identity_function/src/main.nr +++ b/test_programs/formal_verify_success/identity_function/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == x)] +#['ensures(result == x)] fn main(x: u32) -> pub u32 { x } diff --git a/test_programs/formal_verify_success/if_overflow_2/src/main.nr b/test_programs/formal_verify_success/if_overflow_2/src/main.nr index b755e27dd71..432e3f22c6a 100644 --- a/test_programs/formal_verify_success/if_overflow_2/src/main.nr +++ b/test_programs/formal_verify_success/if_overflow_2/src/main.nr @@ -3,7 +3,7 @@ // We also expect the requires clause // to work correctly and make the test // equivalent to if_test_overflow_1 -#[requires(y == 255)] +#['requires(y == 255)] fn main(x: u8, y: u8) -> pub u8 { if x < y { x + 1 diff --git a/test_programs/formal_verify_success/implication_operator_1/src/main.nr b/test_programs/formal_verify_success/implication_operator_1/src/main.nr index ccf9781967a..01073a6dacd 100644 --- a/test_programs/formal_verify_success/implication_operator_1/src/main.nr +++ b/test_programs/formal_verify_success/implication_operator_1/src/main.nr @@ -1,4 +1,4 @@ -#[ensures((y ==> result == x) & (!y ==> result == 0))] +#['ensures((y ==> result == x) & (!y ==> result == 0))] fn main(x: u32, y: bool) -> pub u32 { if y { x diff --git a/test_programs/formal_verify_success/implication_operator_2/src/main.nr b/test_programs/formal_verify_success/implication_operator_2/src/main.nr index 680f6c50ce1..6aecc384942 100644 --- a/test_programs/formal_verify_success/implication_operator_2/src/main.nr +++ b/test_programs/formal_verify_success/implication_operator_2/src/main.nr @@ -1,6 +1,6 @@ // This test shows that you can use the implication operator not // only in attributes but also in the function's body. -#[requires(y ==> x > 5)] +#['requires(y ==> x > 5)] fn main(x: u32, y: bool) { assert(y ==> x > 5); } diff --git a/test_programs/formal_verify_success/index_array/src/main.nr b/test_programs/formal_verify_success/index_array/src/main.nr index ae629485911..46cb602aeaf 100644 --- a/test_programs/formal_verify_success/index_array/src/main.nr +++ b/test_programs/formal_verify_success/index_array/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == arr[3])] +#['ensures(result == arr[3])] fn main(arr: [u32; 5]) -> pub u32 { arr[3] } diff --git a/test_programs/formal_verify_success/index_composite_array_with_const/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_const/src/main.nr index 1f4c982c3c8..4e384c10e6a 100644 --- a/test_programs/formal_verify_success/index_composite_array_with_const/src/main.nr +++ b/test_programs/formal_verify_success/index_composite_array_with_const/src/main.nr @@ -1,5 +1,5 @@ struct A { first: i32, second: u32, } -#[ensures(result == arr[1].second)] +#['ensures(result == arr[1].second)] fn main(arr: [A;2]) -> pub u32 { arr[1].second } diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_1/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_1/src/main.nr index e5e0ffd462b..8008f9b2b48 100644 --- a/test_programs/formal_verify_success/index_composite_array_with_var_1/src/main.nr +++ b/test_programs/formal_verify_success/index_composite_array_with_var_1/src/main.nr @@ -1,6 +1,6 @@ struct A { first: i32, second: u32, } -#[requires(x < 2)] -#[ensures(result == arr[x].first)] +#['requires(x < 2)] +#['ensures(result == arr[x].first)] fn main(arr: [A;2], x: u32) -> pub i32 { arr[x].first } diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_2/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_2/src/main.nr index 478bd44e30d..07333d54993 100644 --- a/test_programs/formal_verify_success/index_composite_array_with_var_2/src/main.nr +++ b/test_programs/formal_verify_success/index_composite_array_with_var_2/src/main.nr @@ -1,6 +1,6 @@ struct A { first: i32, second: u32, } -#[requires(x < 2)] -#[ensures(result == arr[x].second)] +#['requires(x < 2)] +#['ensures(result == arr[x].second)] fn main(arr: [A;2], x: u32) -> pub u32 { arr[x].second } diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_3/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_3/src/main.nr index da285cb5a65..a52b61ebcfc 100644 --- a/test_programs/formal_verify_success/index_composite_array_with_var_3/src/main.nr +++ b/test_programs/formal_verify_success/index_composite_array_with_var_3/src/main.nr @@ -12,8 +12,8 @@ struct B { y: ([A;19],(i16, bool, (i8, [(C, u32);5], u32), u16)) } -#[requires((x < 2) & (arr[x].y.1.2.1[x].0.b[x].third.0 == 1))] -#[ensures(result == arr[x].y.1.2.1[x].0.b[x].third.0 / 2)] +#['requires((x < 2) & (arr[x].y.1.2.1[x].0.b[x].third.0 == 1))] +#['ensures(result == arr[x].y.1.2.1[x].0.b[x].third.0 / 2)] fn main(arr: [B;2], x: u32) -> pub u32 { arr[x].y.1.2.1[x].0.b[x].third.0 / 2 } diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_4/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_4/src/main.nr index 91c3ac0eddb..b57322b693d 100644 --- a/test_programs/formal_verify_success/index_composite_array_with_var_4/src/main.nr +++ b/test_programs/formal_verify_success/index_composite_array_with_var_4/src/main.nr @@ -3,8 +3,8 @@ struct A { second: u32, } -#[requires((x < 2) & (arr[x].second == 1))] -#[ensures(result == 1)] +#['requires((x < 2) & (arr[x].second == 1))] +#['ensures(result == 1)] fn main(arr: [A; 2], x: u32) -> pub u32 { arr[x].second } diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_5/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_5/src/main.nr index 3e8b7bba9b5..f02285a8559 100644 --- a/test_programs/formal_verify_success/index_composite_array_with_var_5/src/main.nr +++ b/test_programs/formal_verify_success/index_composite_array_with_var_5/src/main.nr @@ -1,8 +1,8 @@ // Here we are testing for a structure which has fields // of the same type. struct A { first: u32, second: u32, } -#[requires((x < 2) & (arr[x].first == 4) )] -#[ensures(result == 4)] +#['requires((x < 2) & (arr[x].first == 4) )] +#['ensures(result == 4)] fn main(arr: [A;2], x: u32) -> pub u32 { arr[x].first } diff --git a/test_programs/formal_verify_success/index_var_array_with_constant/src/main.nr b/test_programs/formal_verify_success/index_var_array_with_constant/src/main.nr index cb4568a9294..6edede7d58f 100644 --- a/test_programs/formal_verify_success/index_var_array_with_constant/src/main.nr +++ b/test_programs/formal_verify_success/index_var_array_with_constant/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 65)] +#['ensures(result == 65)] fn main() -> pub u32 { let arr = [857, 1, 65]; arr[2] diff --git a/test_programs/formal_verify_success/index_var_array_with_var/src/main.nr b/test_programs/formal_verify_success/index_var_array_with_var/src/main.nr index 1438a356e68..acbf131b406 100644 --- a/test_programs/formal_verify_success/index_var_array_with_var/src/main.nr +++ b/test_programs/formal_verify_success/index_var_array_with_var/src/main.nr @@ -1,5 +1,5 @@ -#[requires(x == 2)] -#[ensures(result == 65)] +#['requires(x == 2)] +#['ensures(result == 65)] fn main(x: u32) -> pub u32 { let arr = [857, 1, 65]; arr[x] diff --git a/test_programs/formal_verify_success/integer_and/src/main.nr b/test_programs/formal_verify_success/integer_and/src/main.nr index 2c3eba17af5..08b32b19693 100644 --- a/test_programs/formal_verify_success/integer_and/src/main.nr +++ b/test_programs/formal_verify_success/integer_and/src/main.nr @@ -9,42 +9,42 @@ fn main(x: i64, y: i64) { let _ = integer_u64(x as u64, y as u64); } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn integer_i8(x: i8, y: i8) -> i8 { x & y } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn integer_i16(x: i16, y: i16) -> i16 { x & y } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn integer_i32(x: i32, y: i32) -> i32 { x & y } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn integer_i64(x: i64, y: i64) -> i64 { x & y } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn integer_u8(x: u8, y: u8) -> u8 { x & y } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn integer_u16(x: u16, y: u16) -> u16 { x & y } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn integer_u32(x: u32, y: u32) -> u32 { x & y } -#[ensures(result == x & y)] +#['ensures(result == x & y)] fn integer_u64(x: u64, y: u64) -> u64 { x & y } diff --git a/test_programs/formal_verify_success/integer_consts/src/main.nr b/test_programs/formal_verify_success/integer_consts/src/main.nr index 7732226fcd8..d614e1a0ee7 100644 --- a/test_programs/formal_verify_success/integer_consts/src/main.nr +++ b/test_programs/formal_verify_success/integer_consts/src/main.nr @@ -1,7 +1,7 @@ use fv_std; -#[requires(x <= fv_std::U8_MAX & y <= fv_std::U8_MAX)] -#[ensures((x <= fv_std::U8_MAX - y) ==> result == x + y)] +#['requires(x <= fv_std::U8_MAX & y <= fv_std::U8_MAX)] +#['ensures((x <= fv_std::U8_MAX - y) ==> result == x + y)] fn safe_add_u8(x: u8, y: u8) -> u8 { if x <= fv_std::U8_MAX - y { x + y @@ -18,13 +18,13 @@ fn safe_sub_i16(a: i16, b: i16) -> i16 { } } -#[ensures(result == (p & q))] +#['ensures(result == (p & q))] fn bitwise_and_u32(p: u32, q: u32) -> u32 { p & q } -#[requires(p <= fv_std::U32_MAX & q <= fv_std::U32_MAX)] -#[ensures((p <= fv_std::U32_MAX - q) ==> result == p + q)] +#['requires(p <= fv_std::U32_MAX & q <= fv_std::U32_MAX)] +#['ensures((p <= fv_std::U32_MAX - q) ==> result == p + q)] fn safe_add_u32(p: u32, q: u32) -> u32 { if p <= fv_std::U32_MAX - q { p + q @@ -33,7 +33,7 @@ fn safe_add_u32(p: u32, q: u32) -> u32 { } } -#[requires((x <= fv_std::U8_MAX) & (y <= fv_std::U8_MAX))] +#['requires((x <= fv_std::U8_MAX) & (y <= fv_std::U8_MAX))] fn main(x: u8, y: u8, a: i16, b: i16, p: u32, q: u32) { let _ = safe_add_u8(x, y); let _ = safe_sub_i16(a, b); diff --git a/test_programs/formal_verify_success/integer_div/src/main.nr b/test_programs/formal_verify_success/integer_div/src/main.nr index a8f70478ef2..ea3fb99eb7d 100644 --- a/test_programs/formal_verify_success/integer_div/src/main.nr +++ b/test_programs/formal_verify_success/integer_div/src/main.nr @@ -1,4 +1,4 @@ -#[requires((-10 < x) & (x < 10) & (0 < y) & (y < 10))] +#['requires((-10 < x) & (x < 10) & (0 < y) & (y < 10))] fn main(x: i64, y: i64) { let _ = integer_i8(x as i8, y as i8); let _ = integer_i16(x as i16, y as i16); @@ -10,50 +10,50 @@ fn main(x: i64, y: i64) { let _ = integer_u64(x as u64, y as u64); } -#[requires(y != 0)] -#[ensures(result == x / y)] +#['requires(y != 0)] +#['ensures(result == x / y)] fn integer_i8(x: i8, y: i8) -> i8 { x / y } -#[requires(y != 0)] -#[ensures(result == x / y)] +#['requires(y != 0)] +#['ensures(result == x / y)] fn integer_i16(x: i16, y: i16) -> i16 { x / y } -#[requires(y != 0)] -#[ensures(result == x / y)] +#['requires(y != 0)] +#['ensures(result == x / y)] fn integer_i32(x: i32, y: i32) -> i32 { x / y } -#[requires(y != 0)] -#[ensures(result == x / y)] +#['requires(y != 0)] +#['ensures(result == x / y)] fn integer_i64(x: i64, y: i64) -> i64 { x / y } -#[requires(y != 0)] -#[ensures(result == x / y)] +#['requires(y != 0)] +#['ensures(result == x / y)] fn integer_u8(x: u8, y: u8) -> u8 { x / y } -#[requires(y != 0)] -#[ensures(result == x / y)] +#['requires(y != 0)] +#['ensures(result == x / y)] fn integer_u16(x: u16, y: u16) -> u16 { x / y } -#[requires(y != 0)] -#[ensures(result == x / y)] +#['requires(y != 0)] +#['ensures(result == x / y)] fn integer_u32(x: u32, y: u32) -> u32 { x / y } -#[requires(y != 0)] -#[ensures(result == x / y)] +#['requires(y != 0)] +#['ensures(result == x / y)] fn integer_u64(x: u64, y: u64) -> u64 { x / y } diff --git a/test_programs/formal_verify_success/integer_mod/src/main.nr b/test_programs/formal_verify_success/integer_mod/src/main.nr index 4e6020d5dc6..119fb67cec9 100644 --- a/test_programs/formal_verify_success/integer_mod/src/main.nr +++ b/test_programs/formal_verify_success/integer_mod/src/main.nr @@ -1,4 +1,4 @@ -#[requires((-10 < x) & (x < 10) & (0 < y) & (y < 10))] +#['requires((-10 < x) & (x < 10) & (0 < y) & (y < 10))] fn main(x: i64, y: i64) { let _ = integer_i8(x as i8, y as i8); let _ = integer_i16(x as i16, y as i16); @@ -10,50 +10,50 @@ fn main(x: i64, y: i64) { let _ = integer_u64(x as u64, y as u64); } -#[requires(y != 0)] -#[ensures(result == x % y)] +#['requires(y != 0)] +#['ensures(result == x % y)] fn integer_i8(x: i8, y: i8) -> i8 { x % y } -#[requires(y != 0)] -#[ensures(result == x % y)] +#['requires(y != 0)] +#['ensures(result == x % y)] fn integer_i16(x: i16, y: i16) -> i16 { x % y } -#[requires(y != 0)] -#[ensures(result == x % y)] +#['requires(y != 0)] +#['ensures(result == x % y)] fn integer_i32(x: i32, y: i32) -> i32 { x % y } -#[requires(y != 0)] -#[ensures(result == x % y)] +#['requires(y != 0)] +#['ensures(result == x % y)] fn integer_i64(x: i64, y: i64) -> i64 { x % y } -#[requires(y != 0)] -#[ensures(result == x % y)] +#['requires(y != 0)] +#['ensures(result == x % y)] fn integer_u8(x: u8, y: u8) -> u8 { x % y } -#[requires(y != 0)] -#[ensures(result == x % y)] +#['requires(y != 0)] +#['ensures(result == x % y)] fn integer_u16(x: u16, y: u16) -> u16 { x % y } -#[requires(y != 0)] -#[ensures(result == x % y)] +#['requires(y != 0)] +#['ensures(result == x % y)] fn integer_u32(x: u32, y: u32) -> u32 { x % y } -#[requires(y != 0)] -#[ensures(result == x % y)] +#['requires(y != 0)] +#['ensures(result == x % y)] fn integer_u64(x: u64, y: u64) -> u64 { x % y } diff --git a/test_programs/formal_verify_success/integer_not/src/main.nr b/test_programs/formal_verify_success/integer_not/src/main.nr index 0a5d276897a..f7c6a81031b 100644 --- a/test_programs/formal_verify_success/integer_not/src/main.nr +++ b/test_programs/formal_verify_success/integer_not/src/main.nr @@ -1,4 +1,4 @@ -#[requires((0 < x) & (x < 10))] +#['requires((0 < x) & (x < 10))] fn main(x: i64) { let _ = integer_i8(x as i8); let _ = integer_i16(x as i16); @@ -10,50 +10,50 @@ fn main(x: i64) { let _ = integer_u64(x as u64); } -#[requires((0 < x) & (x < 10))] -#[ensures(result == ! x)] +#['requires((0 < x) & (x < 10))] +#['ensures(result == ! x)] fn integer_i8(x: i8) -> i8 { ! x } -#[requires((0 < x) & (x < 10))] -#[ensures(result == ! x)] +#['requires((0 < x) & (x < 10))] +#['ensures(result == ! x)] fn integer_i16(x: i16) -> i16 { ! x } -#[requires((0 < x) & (x < 10))] -#[ensures(result == ! x)] +#['requires((0 < x) & (x < 10))] +#['ensures(result == ! x)] fn integer_i32(x: i32) -> i32 { ! x } -#[requires((0 < x) & (x < 10))] -#[ensures(result == ! x)] +#['requires((0 < x) & (x < 10))] +#['ensures(result == ! x)] fn integer_i64(x: i64) -> i64 { ! x } -#[requires(x < 10)] -#[ensures(result == ! x)] +#['requires(x < 10)] +#['ensures(result == ! x)] fn integer_u8(x: u8) -> u8 { ! x } -#[requires(x < 10)] -#[ensures(result == ! x)] +#['requires(x < 10)] +#['ensures(result == ! x)] fn integer_u16(x: u16) -> u16 { ! x } -#[requires(x < 10)] -#[ensures(result == ! x)] +#['requires(x < 10)] +#['ensures(result == ! x)] fn integer_u32(x: u32) -> u32 { ! x } -#[requires(x < 10)] -#[ensures(result == ! x)] +#['requires(x < 10)] +#['ensures(result == ! x)] fn integer_u64(x: u64) -> u64 { ! x } diff --git a/test_programs/formal_verify_success/integer_or/src/main.nr b/test_programs/formal_verify_success/integer_or/src/main.nr index 858d036a08c..358aacd6bea 100644 --- a/test_programs/formal_verify_success/integer_or/src/main.nr +++ b/test_programs/formal_verify_success/integer_or/src/main.nr @@ -9,42 +9,42 @@ fn main(x: i64, y: i64) { let _ = integer_u64(x as u64, y as u64); } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn integer_i8(x: i8, y: i8) -> i8 { x | y } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn integer_i16(x: i16, y: i16) -> i16 { x | y } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn integer_i32(x: i32, y: i32) -> i32 { x | y } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn integer_i64(x: i64, y: i64) -> i64 { x | y } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn integer_u8(x: u8, y: u8) -> u8 { x | y } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn integer_u16(x: u16, y: u16) -> u16 { x | y } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn integer_u32(x: u32, y: u32) -> u32 { x | y } -#[ensures(result == x | y)] +#['ensures(result == x | y)] fn integer_u64(x: u64, y: u64) -> u64 { x | y } diff --git a/test_programs/formal_verify_success/integer_sub/src/main.nr b/test_programs/formal_verify_success/integer_sub/src/main.nr index b98ffd375c2..4c9d619a4f1 100644 --- a/test_programs/formal_verify_success/integer_sub/src/main.nr +++ b/test_programs/formal_verify_success/integer_sub/src/main.nr @@ -1,4 +1,4 @@ -#[requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] +#['requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] fn main(x: i64, y: i64) { let _ = integer_i8(x as i8, y as i8); let _ = integer_i16(x as i16, y as i16); @@ -10,50 +10,50 @@ fn main(x: i64, y: i64) { let _ = integer_u64(x as u64, y as u64); } -#[requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] -#[ensures(result == x - y)] +#['requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] +#['ensures(result == x - y)] fn integer_i8(x: i8, y: i8) -> i8 { x - y } -#[requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] -#[ensures(result == x - y)] +#['requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] +#['ensures(result == x - y)] fn integer_i16(x: i16, y: i16) -> i16 { x - y } -#[requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] -#[ensures(result == x - y)] +#['requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] +#['ensures(result == x - y)] fn integer_i32(x: i32, y: i32) -> i32 { x - y } -#[requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] -#[ensures(result == x - y)] +#['requires((10 < x) & (x < 100) & (0 < y) & (y < 10))] +#['ensures(result == x - y)] fn integer_i64(x: i64, y: i64) -> i64 { x - y } -#[requires((10 < x) & (x < 100) & (y < 10))] -#[ensures(result == x - y)] +#['requires((10 < x) & (x < 100) & (y < 10))] +#['ensures(result == x - y)] fn integer_u8(x: u8, y: u8) -> u8 { x - y } -#[requires((10 < x) & (x < 100) & (y < 10))] -#[ensures(result == x - y)] +#['requires((10 < x) & (x < 100) & (y < 10))] +#['ensures(result == x - y)] fn integer_u16(x: u16, y: u16) -> u16 { x - y } -#[requires((10 < x) & (x < 100) & (y < 10))] -#[ensures(result == x - y)] +#['requires((10 < x) & (x < 100) & (y < 10))] +#['ensures(result == x - y)] fn integer_u32(x: u32, y: u32) -> u32 { x - y } -#[requires((10 < x) & (x < 100) & (y < 10))] -#[ensures(result == x - y)] +#['requires((10 < x) & (x < 100) & (y < 10))] +#['ensures(result == x - y)] fn integer_u64(x: u64, y: u64) -> u64 { x - y } diff --git a/test_programs/formal_verify_success/integer_sum/src/main.nr b/test_programs/formal_verify_success/integer_sum/src/main.nr index fa6d8cb178d..f02898da06f 100644 --- a/test_programs/formal_verify_success/integer_sum/src/main.nr +++ b/test_programs/formal_verify_success/integer_sum/src/main.nr @@ -1,4 +1,4 @@ -#[requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] +#['requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] fn main(x: u64, y: u64) { let _ = integer_i8(x as i8, y as i8); let _ = integer_i16(x as i16, y as i16); @@ -10,50 +10,50 @@ fn main(x: u64, y: u64) { let _ = integer_u64(x, y); } -#[requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] -#[ensures(result == x + y)] +#['requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] +#['ensures(result == x + y)] fn integer_i8(x: i8, y: i8) -> i8 { x + y } -#[requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] -#[ensures(result == x + y)] +#['requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] +#['ensures(result == x + y)] fn integer_i16(x: i16, y: i16) -> i16 { x + y } -#[requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] -#[ensures(result == x + y)] +#['requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] +#['ensures(result == x + y)] fn integer_i32(x: i32, y: i32) -> i32 { x + y } -#[requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] -#[ensures(result == x + y)] +#['requires((0 < x) & (x < 10) & (0 < y) & (y < 10))] +#['ensures(result == x + y)] fn integer_i64(x: i64, y: i64) -> i64 { x + y } -#[requires((x < 10) & (y < 10))] -#[ensures(result == x + y)] +#['requires((x < 10) & (y < 10))] +#['ensures(result == x + y)] fn integer_u8(x: u8, y: u8) -> u8 { x + y } -#[requires((x < 10) & (y < 10))] -#[ensures(result == x + y)] +#['requires((x < 10) & (y < 10))] +#['ensures(result == x + y)] fn integer_u16(x: u16, y: u16) -> u16 { x + y } -#[requires((x < 10) & (y < 10))] -#[ensures(result == x + y)] +#['requires((x < 10) & (y < 10))] +#['ensures(result == x + y)] fn integer_u32(x: u32, y: u32) -> u32 { x + y } -#[requires((x < 10) & (y < 10))] -#[ensures(result == x + y)] +#['requires((x < 10) & (y < 10))] +#['ensures(result == x + y)] fn integer_u64(x: u64, y: u64) -> u64 { x + y } diff --git a/test_programs/formal_verify_success/integer_xor/src/main.nr b/test_programs/formal_verify_success/integer_xor/src/main.nr index eacffbb7078..4c479d874e6 100644 --- a/test_programs/formal_verify_success/integer_xor/src/main.nr +++ b/test_programs/formal_verify_success/integer_xor/src/main.nr @@ -9,42 +9,42 @@ fn main(x: i64, y: i64) { let _ = integer_u64(x as u64, y as u64); } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn integer_i8(x: i8, y: i8) -> i8 { x ^ y } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn integer_i16(x: i16, y: i16) -> i16 { x ^ y } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn integer_i32(x: i32, y: i32) -> i32 { x ^ y } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn integer_i64(x: i64, y: i64) -> i64 { x ^ y } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn integer_u8(x: u8, y: u8) -> u8 { x ^ y } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn integer_u16(x: u16, y: u16) -> u16 { x ^ y } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn integer_u32(x: u32, y: u32) -> u32 { x ^ y } -#[ensures(result == x ^ y)] +#['ensures(result == x ^ y)] fn integer_u64(x: u64, y: u64) -> u64 { x ^ y } diff --git a/test_programs/formal_verify_success/is_even/src/main.nr b/test_programs/formal_verify_success/is_even/src/main.nr index f11a3ff2768..77f8d2c241d 100644 --- a/test_programs/formal_verify_success/is_even/src/main.nr +++ b/test_programs/formal_verify_success/is_even/src/main.nr @@ -1,7 +1,7 @@ -#[requires((x % 2 == 0) & (z % 2 == 0) +#['requires((x % 2 == 0) & (z % 2 == 0) & (x < z ) & (z < 1000))] -#[ensures(result == 0)] +#['ensures(result == 0)] fn main(x: u32, z: u32) -> pub u32 { let mut y = x + z; y % 2 diff --git a/test_programs/formal_verify_success/is_power_of_2/src/main.nr b/test_programs/formal_verify_success/is_power_of_2/src/main.nr index b3468773c3e..8ad18c6c1b2 100644 --- a/test_programs/formal_verify_success/is_power_of_2/src/main.nr +++ b/test_programs/formal_verify_success/is_power_of_2/src/main.nr @@ -1,4 +1,4 @@ -#[ghost] +#['ghost] fn is_power_of_2(x: i32) -> bool { if x <= 0 { false @@ -7,8 +7,8 @@ fn is_power_of_2(x: i32) -> bool { } } -#[requires(is_power_of_2(arr[0]))] -#[ensures(is_power_of_2(result))] +#['requires(is_power_of_2(arr[0]))] +#['ensures(is_power_of_2(result))] fn main(arr: [i32; 3]) -> pub i32 { arr[0] } diff --git a/test_programs/formal_verify_success/lhs_tuple_access/src/main.nr b/test_programs/formal_verify_success/lhs_tuple_access/src/main.nr index 31216a63d71..be6079e625a 100644 --- a/test_programs/formal_verify_success/lhs_tuple_access/src/main.nr +++ b/test_programs/formal_verify_success/lhs_tuple_access/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result.1 == x)] +#['ensures(result.1 == x)] fn main(x: u8) -> pub (u8, u8) { let mut y = (3, 5); y.1 = x; diff --git a/test_programs/formal_verify_success/mutate_array_of_tuples/src/main.nr b/test_programs/formal_verify_success/mutate_array_of_tuples/src/main.nr index 5520bafc886..49455b34703 100644 --- a/test_programs/formal_verify_success/mutate_array_of_tuples/src/main.nr +++ b/test_programs/formal_verify_success/mutate_array_of_tuples/src/main.nr @@ -1,6 +1,6 @@ -#[requires(x[3].0 == -2)] -#[requires(i < 5)] -#[ensures(result[i].1 == y)] +#['requires(x[3].0 == -2)] +#['requires(i < 5)] +#['ensures(result[i].1 == y)] fn main(mut x: [(i32, u32); 5], i: u32, y: u32) -> pub [(i32, u32); 5] { x[i].1 = y; x diff --git a/test_programs/formal_verify_success/operation_comutativity/src/main.nr b/test_programs/formal_verify_success/operation_comutativity/src/main.nr index fbda307da46..efbef804a09 100644 --- a/test_programs/formal_verify_success/operation_comutativity/src/main.nr +++ b/test_programs/formal_verify_success/operation_comutativity/src/main.nr @@ -1,17 +1,17 @@ -#[requires(x < 127)] +#['requires(x < 127)] fn main(x: u8) { let _ = left(x); let _ = right(x); } -#[requires(x <= 126)] -#[ensures(result == ((x + 1) * 2))] +#['requires(x <= 126)] +#['ensures(result == ((x + 1) * 2))] fn left(x: u8) -> u8 { (x + 1) * 2 } -#[requires(x <= 126)] -#[ensures(result == (2 * (x + 1)))] +#['requires(x <= 126)] +#['ensures(result == (2 * (x + 1)))] fn right(x: u8) -> u8 { (x + 1) * 2 } diff --git a/test_programs/formal_verify_success/struct/src/main.nr b/test_programs/formal_verify_success/struct/src/main.nr index 49270bb3c50..71bc460d8fa 100644 --- a/test_programs/formal_verify_success/struct/src/main.nr +++ b/test_programs/formal_verify_success/struct/src/main.nr @@ -1,7 +1,7 @@ struct A { x: u8, y: u8 } -#[requires((a.x < 10) & (a.y < 10))] -#[ensures((result.x == a.x + a.y) & (result.y == a.y))] +#['requires((a.x < 10) & (a.y < 10))] +#['ensures((result.x == a.x + a.y) & (result.y == a.y))] fn main(a: A) -> pub A { A { x: a.x + a.y, y: a.y } } diff --git a/test_programs/formal_verify_success/struct_return/src/main.nr b/test_programs/formal_verify_success/struct_return/src/main.nr index a10f65455f3..9897974caf5 100644 --- a/test_programs/formal_verify_success/struct_return/src/main.nr +++ b/test_programs/formal_verify_success/struct_return/src/main.nr @@ -3,20 +3,20 @@ struct A { y: u32, } -#[requires(x < 1000)] -#[ensures((result.x == x + 1) & (result.y == x + 2))] +#['requires(x < 1000)] +#['ensures((result.x == x + 1) & (result.y == x + 2))] fn f(x: u32) -> A { A { x: x + 1, y: x + 2 } } -#[requires(x < 1000)] -#[ensures((result.x == x + 2) & (result.y == x + 1))] +#['requires(x < 1000)] +#['ensures((result.x == x + 2) & (result.y == x + 1))] fn g(x: u32) -> A { let res = f(x); A { x: res.y, y: res.x } } -#[ensures(result == 13)] +#['ensures(result == 13)] fn main() -> pub u32 { let A { x, y } = g(5); x + y diff --git a/test_programs/formal_verify_success/struct_return_basic/src/main.nr b/test_programs/formal_verify_success/struct_return_basic/src/main.nr index f864789bbdb..9ef2cd157e2 100644 --- a/test_programs/formal_verify_success/struct_return_basic/src/main.nr +++ b/test_programs/formal_verify_success/struct_return_basic/src/main.nr @@ -3,7 +3,7 @@ struct A { y: u32, } -#[ensures(result == 3)] +#['ensures(result == 3)] fn main() -> pub u32 { let A { x, y } = A {x: 1, y: 2}; x + y diff --git a/test_programs/formal_verify_success/tuple_deconstruct/src/main.nr b/test_programs/formal_verify_success/tuple_deconstruct/src/main.nr index 3b26b63eaa3..2443e0e5da3 100644 --- a/test_programs/formal_verify_success/tuple_deconstruct/src/main.nr +++ b/test_programs/formal_verify_success/tuple_deconstruct/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 1)] +#['ensures(result == 1)] fn main() -> pub u32 { let (a, b) = (1, 2); a diff --git a/test_programs/formal_verify_success/tuple_deconstruct_2/src/main.nr b/test_programs/formal_verify_success/tuple_deconstruct_2/src/main.nr index 9b4b89fe998..710d4b446b3 100644 --- a/test_programs/formal_verify_success/tuple_deconstruct_2/src/main.nr +++ b/test_programs/formal_verify_success/tuple_deconstruct_2/src/main.nr @@ -1,4 +1,4 @@ -#[ensures(result == 5)] +#['ensures(result == 5)] fn main() -> pub u32 { let (a, b) = (1, 2); let (c, d) = (3, 4); diff --git a/test_programs/formal_verify_success/tuple_return/src/main.nr b/test_programs/formal_verify_success/tuple_return/src/main.nr index 93ea288448e..8810c30fa9a 100644 --- a/test_programs/formal_verify_success/tuple_return/src/main.nr +++ b/test_programs/formal_verify_success/tuple_return/src/main.nr @@ -1,17 +1,17 @@ -#[requires(x < 1000)] -#[ensures((result.0 == x + 1) & (result.1 == x + 2) & (result.2 == x + 3))] +#['requires(x < 1000)] +#['ensures((result.0 == x + 1) & (result.1 == x + 2) & (result.2 == x + 3))] fn f(x: u32) -> (u32, u32, u32) { (x + 1, x + 2, x + 3) } -#[requires(x < 1000)] -#[ensures((result.0 == x + 3) & (result.1 == x + 1) & (result.2 == x + 2))] +#['requires(x < 1000)] +#['ensures((result.0 == x + 3) & (result.1 == x + 1) & (result.2 == x + 2))] fn g(x: u32) -> (u32, u32, u32) { let res = f(x); (res.2, res.0, res.1) } -#[ensures(result == 7)] +#['ensures(result == 7)] fn main() -> pub u32 { let (a, b, c) = g(5); a + b - c diff --git a/test_programs/formal_verify_success/tuple_return_basic/src/main.nr b/test_programs/formal_verify_success/tuple_return_basic/src/main.nr index a8a17b602f8..d7c4c9ae920 100644 --- a/test_programs/formal_verify_success/tuple_return_basic/src/main.nr +++ b/test_programs/formal_verify_success/tuple_return_basic/src/main.nr @@ -1,10 +1,10 @@ -#[requires(x < 1000)] -#[ensures((result.0 == x + 1) & (result.1 == x + 2) & (result.2 == x + 3))] +#['requires(x < 1000)] +#['ensures((result.0 == x + 1) & (result.1 == x + 2) & (result.2 == x + 3))] fn f(x: u32) -> (u32, u32, u32) { (x + 1, x + 2, x + 3) } -#[ensures(result == 5)] +#['ensures(result == 5)] fn main() -> pub u32 { let (a, b, c) = f(5); a + b - c diff --git a/test_programs/formal_verify_success/tuples/src/main.nr b/test_programs/formal_verify_success/tuples/src/main.nr index 3168ea9757d..e2b4267fd34 100644 --- a/test_programs/formal_verify_success/tuples/src/main.nr +++ b/test_programs/formal_verify_success/tuples/src/main.nr @@ -1,5 +1,5 @@ -#[requires((x.0 < 10) & (x.2 < 10))] -#[ensures((result.0 == x.0) & (result.2 == x.2 + x.0))] +#['requires((x.0 < 10) & (x.2 < 10))] +#['ensures((result.0 == x.0) & (result.2 == x.2 + x.0))] fn main(x: (u8, Field, u8)) -> pub (u8, Field, u8) { (x.0, x.1, x.2 + x.0) } From 37c389fbe88d5dac0dec02550b8ab83caf04c3f5 Mon Sep 17 00:00:00 2001 From: reo101 Date: Fri, 11 Jul 2025 16:41:37 +0300 Subject: [PATCH 09/86] chore(new-syntax): parse, don't validate --- .../src/vir/vir_gen/function.rs | 49 ++++++------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/function.rs b/compiler/noirc_evaluator/src/vir/vir_gen/function.rs index 31225d7214e..10734c950ad 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/function.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/function.rs @@ -167,42 +167,25 @@ pub fn build_funx_with_ready_annotations( globals: &BTreeMap, annotations: Vec, ) -> Result { - let is_ghost = annotations.iter().any(|x| matches!(x, Attribute::Ghost)); + let mut is_ghost = false; + let mut requires_annotations_inner = vec![]; + let mut ensures_annotations_inner = vec![]; + + for a in annotations.into_iter() { + match a { + Attribute::Ghost => { + is_ghost = true; + } + Attribute::Ensures(expr) => ensures_annotations_inner.push(expr), + Attribute::Requires(expr) => requires_annotations_inner.push(expr), + } + } + let mode = get_function_mode(is_ghost); let function_params = get_function_params(function, mode)?; let function_return_param = get_function_return_param(function, mode)?; - let (requires_annotations, ensures_annotations): (Vec, Vec) = annotations - .into_iter() - .filter(|attribute| { - matches!(attribute, Attribute::Requires(_)) - || matches!(attribute, Attribute::Ensures(_)) - }) - .partition(|attribute| matches!(attribute, Attribute::Requires(_))); - - let requires_annotations_inner: Exprs = Arc::new( - requires_annotations - .into_iter() - .filter_map(|x| match x { - Attribute::Ghost => None, - Attribute::Ensures(_) => None, - Attribute::Requires(expr) => Some(expr), - }) - .collect(), - ); - - let ensures_annotations_inner: Exprs = Arc::new( - ensures_annotations - .into_iter() - .filter_map(|x| match x { - Attribute::Ghost => None, - Attribute::Requires(_) => None, - Attribute::Ensures(expr) => Some(expr), - }) - .collect(), - ); - let funx = FunctionX { name: function_into_funx_name(function), proxy: None, // Only needed for external fn specifications which we currently don't support @@ -223,8 +206,8 @@ pub fn build_funx_with_ready_annotations( params: function_params, ret: function_return_param, ens_has_return: !is_function_return_void(function), - require: requires_annotations_inner, - ensure: ensures_annotations_inner, + require: Arc::new(requires_annotations_inner), + ensure: Arc::new(ensures_annotations_inner), returns: None, // We don't support the special clause called `return` decrease: Arc::new(vec![]), // Annotation for recursive functions. We currently don't support it decrease_when: None, // Annotation for recursive functions. We currently don't support it From 37e426d22582cc163014dcb3c37a2c3b3bd126ca Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 10/86] feat(annotations)!: new AST and full type inference Make a minimal `AST` `ExprF` type, wrapping it with a `Cofree`-style recursive *annotating* `struct`, which inserts arbitrary data (generic) at each *recursion* level. That is used to have a few different "versions" of the same `AST`, depending on what phase we're in: - pure parsing (`OffsetExpr`): just keeping context-free offset from the start of the input `&str` (two numbers) - span attachment (`SpannedExpr`): convert the offsets to real `Noir` `Span`s, referring to the real place in code (both file and location inside of it) - type checking (`Spanned{Optionally,}TypedExpr`): type-infer and add `Noir` `Type` information to all expressions (using an intermediary `SpannedOptionallyTypedExpr` for when we're still not sure of integer literals' types, as they have to come from above) All of the conversions between the different stages are implemented as variants of a [catamorphism](https://en.wikipedia.org/wiki/Catamorphism) and its derivatives. Also split all the code in separate modules (`ast`, `parse`, `typing`), with some of the main driver logic in the top-level `lib.rs` TODO: - Connection with the rest of the codebase - Quantifiers (parsing and type inference) - Array and tuple indexing (parsing and type inference) - Better errors (still...) - More organization of the API itself (cleaner `lib.rs`, etc.) - More tests --- Cargo.lock | 21 + compiler/formal_verification/Cargo.toml | 1 + compiler/formal_verification/src/ast.rs | 133 +++ compiler/formal_verification/src/lib.rs | 85 ++ compiler/formal_verification/src/parse.rs | 539 ++++++++++++ compiler/formal_verification/src/parse/mod.rs | 774 ------------------ compiler/formal_verification/src/typing.rs | 259 ++++++ 7 files changed, 1038 insertions(+), 774 deletions(-) create mode 100644 compiler/formal_verification/src/ast.rs create mode 100644 compiler/formal_verification/src/parse.rs delete mode 100644 compiler/formal_verification/src/parse/mod.rs create mode 100644 compiler/formal_verification/src/typing.rs diff --git a/Cargo.lock b/Cargo.lock index 5c7a9ac1fb1..f11ca628e95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,6 +1546,26 @@ dependencies = [ "syn 2.0.102", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.102", +] + [[package]] name = "difflib" version = "0.4.0" @@ -1949,6 +1969,7 @@ dependencies = [ "base64", "bn254_blackbox_solver", "cfg-if", + "derive_more", "function_name", "insta", "noirc_errors", diff --git a/compiler/formal_verification/Cargo.toml b/compiler/formal_verification/Cargo.toml index 2ab6908abf5..4ebe28f7434 100644 --- a/compiler/formal_verification/Cargo.toml +++ b/compiler/formal_verification/Cargo.toml @@ -21,6 +21,7 @@ tracing.workspace = true strum.workspace = true strum_macros.workspace = true nom = "8.0" +derive_more = { version = "2.0.1", features = ["deref", "from", "into"] } [dev-dependencies] base64.workspace = true diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs new file mode 100644 index 00000000000..a5ef104ec0a --- /dev/null +++ b/compiler/formal_verification/src/ast.rs @@ -0,0 +1,133 @@ +use std::ops::Try; + +use noirc_errors::Location; +use noirc_frontend::monomorphization::ast::Type as NoirType; +use num_bigint::BigInt; +use serde::{Deserialize, Serialize}; + +pub type Identifier = String; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ExprF { + Literal { value: Literal }, + Variable { name: Identifier }, + FnCall { name: Identifier, args: Vec }, + Parenthesised { expr: R }, + UnaryOp { op: UnaryOp, expr: R }, + BinaryOp { op: BinaryOp, expr_left: R, expr_right: R }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AnnExpr { + pub ann: A, + pub expr: Box>>, +} + +pub type SpannedOptionallyTypedExpr = AnnExpr<(Location, Option)>; +pub type SpannedTypedExpr = AnnExpr<(Location, NoirType)>; +pub type SpannedExpr = AnnExpr; +pub type OffsetExpr = AnnExpr<(u32, u32)>; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Literal { + Bool(bool), + Int(BigInt), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum UnaryOp { + Not, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ArithmeticOp { + Mul, + Div, + Mod, + Add, + Sub, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum PredicateOp { + Eq, + Neq, + Lt, + Le, + Gt, + Ge, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum BooleanOp { + And, + Or, + Implies, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum BinaryOp { + // Arithmetic (data -> data) + ArithmeticOp(ArithmeticOp), + // Predicates (data -> bool) + PredicateOp(PredicateOp), + // Boolean (bool -> bool) + BooleanOp(BooleanOp), +} + +pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { + match expr { + ExprF::Literal { value } => ExprF::Literal { value }, + ExprF::Variable { name } => ExprF::Variable { name }, + ExprF::FnCall { name, args } => { + let processed_args = args.into_iter().map(cata_fn).collect(); + ExprF::FnCall { name, args: processed_args } + } + ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr) }, + ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr) }, + ExprF::BinaryOp { op, expr_left, expr_right } => { + ExprF::BinaryOp { op, expr_left: cata_fn(expr_left), expr_right: cata_fn(expr_right) } + } + } +} + +fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Result, E> { + Ok(match expr { + ExprF::Literal { value } => ExprF::Literal { value }, + ExprF::Variable { name } => ExprF::Variable { name }, + ExprF::FnCall { name, args } => { + let processed_args = args.into_iter().map(cata_fn).collect::, _>>()?; + ExprF::FnCall { name, args: processed_args } + } + ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr)? }, + ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr)? }, + ExprF::BinaryOp { op, expr_left, expr_right } => { + ExprF::BinaryOp { op, expr_left: cata_fn(expr_left)?, expr_right: cata_fn(expr_right)? } + } + }) +} + +// TODO: `impl` vs` `dyn` for `cata_fn` +pub fn cata(expr: AnnExpr, algebra: &dyn Fn(A, ExprF) -> B) -> B { + let children_results = fmap(*expr.expr, &|child| cata(child, algebra)); + + algebra(expr.ann, children_results) +} + +pub fn try_cata( + expr: AnnExpr, + algebra: &dyn Fn(A, ExprF) -> Result, +) -> Result { + let children_results = try_fmap(*expr.expr, &|child| try_cata(child, algebra))?; + + algebra(expr.ann, children_results) +} + +pub fn try_cata_recoverable( + expr: AnnExpr, + algebra: &dyn Fn(A, Result, E>) -> Result, +) -> Result { + let children_results = try_fmap(*expr.expr, &|child| try_cata_recoverable(child, algebra)); + + algebra(expr.ann, children_results) +} diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index ea868482d66..45bf04b1e84 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -1 +1,86 @@ +#![feature(try_trait_v2)] + +use noirc_errors::{Location, Span}; +use noirc_frontend::monomorphization::ast as mast; +use nom::Finish; +use std::{collections::BTreeMap, fmt::Debug}; +use vir::messages::Span as VirSpan; + +use crate::{ + ast::{OffsetExpr, SpannedExpr, cata}, + parse::{Error as ParseError, build_location, parse_expression_expr, parse_identifier}, +}; + +// NOTE: all types inside are not prefixed, to be used as `ast::OffsetExpr` +pub mod ast; pub mod parse; +pub mod typing; + +#[derive(Debug)] +pub struct State<'a> { + pub full_length: u32, + pub location: Location, + pub function: &'a mast::Function, + pub global_constants: &'a BTreeMap, + pub functions: &'a BTreeMap, +} + +#[derive(Debug, Clone)] +pub enum Attribute { + Ghost, + Ensures(ast::SpannedExpr), + Requires(ast::SpannedExpr), +} + +#[derive(Debug, Clone)] +pub struct MonomorphizationRequest { + pub function_identifier: String, + // NOTE: `None` for untyped integer literals + pub param_types: Vec>, +} + +fn span_expr(annotation_location: Location, full_length: u32, expr: OffsetExpr) -> SpannedExpr { + cata(expr, &|(prev_offset, after_offset), exprf| SpannedExpr { + ann: build_location(annotation_location, full_length, prev_offset, after_offset), + expr: Box::new(exprf), + }) +} + +pub fn parse_attribute<'a>( + annotation: &'a str, + mut location: Location, + function: &'a mast::Function, + global_constants: &'a BTreeMap, + functions: &'a BTreeMap, +) -> Result { + // NOTE: #['...] + // ^^^^^^^ - received `Location` + // ^^^ - relevant stuff + // TODO: don't do this here + location = Location { + span: Span::inclusive(location.span.start() + 3, location.span.end() - 1), + ..location + }; + + let input = annotation; + let (input, attribute_type) = parse_identifier(input).finish()?; //.map_err(|e| e.parser_errors)?; + + let get_expr = || -> Result { + let (rest, expr) = parse_expression_expr(input).finish()?; //.map_err(|e| e.parser_errors)?; + assert_eq!(rest, ""); + Ok(span_expr(location, annotation.len() as u32, expr)) + }; + + Ok(match attribute_type { + "ghost" => Attribute::Ghost, + "ensures" => Attribute::Ensures(get_expr()?), + "requires" => Attribute::Requires(get_expr()?), + _ => { + // return Err(vec![ResolverError::VariableNotDeclared { + // name: attribute_type.to_string(), + // location, + // }]); + return Err(ParseError { parser_errors: vec![] }); + } + }) +} diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs new file mode 100644 index 00000000000..579b602ef47 --- /dev/null +++ b/compiler/formal_verification/src/parse.rs @@ -0,0 +1,539 @@ +use noirc_errors::{Location, Span}; +use nom::{ + Err, IResult, Parser, + branch::alt, + bytes::complete::{tag, take_while, take_while1}, + character::complete::{digit1 as digit, multispace0 as multispace}, + combinator::{map, opt, recognize, value}, + error::{ErrorKind, ParseError}, + multi::{many0, separated_list0}, + sequence::{delimited, pair, preceded, terminated}, +}; +use num_bigint::{BigInt, BigUint, Sign}; +use std::fmt::Debug; + +use crate::ast::{ + ArithmeticOp, BinaryOp, BooleanOp, ExprF, Literal, OffsetExpr, PredicateOp, UnaryOp, +}; + +#[derive(Debug)] +pub enum ParserError { + // TODO: real errors + Oops, +} + +#[derive(Debug)] +pub struct Error { + pub parser_errors: Vec, +} + +pub type Input<'a> = &'a str; +pub type PResult<'a, T> = IResult, T, Error>; + +impl<'a, 'b> ParseError> for Error { + fn from_error_kind(input: Input<'a>, kind: ErrorKind) -> Self { + Self { + parser_errors: vec![ParserError::Oops], + // location: build_location(input.len(), input.len()), + } + } + + fn append(input: Input<'a>, kind: ErrorKind, mut other: Self) -> Self { + // TODO: smart stuff + other.parser_errors.push(ParserError::Oops); + other + } +} + +// https://github.com/rust-bakery/nom/blob/main/doc/error_management.md + +pub(crate) fn build_location( + annotation_location: Location, + full_length: u32, + prev_offset: u32, + after_offset: u32, +) -> Location { + Location { + span: Span::inclusive( + annotation_location.span.start() + full_length - prev_offset, + annotation_location.span.start() + full_length - after_offset, + ), + file: annotation_location.file, + } +} + +pub(crate) fn build_expr( + prev_offset: usize, + after_offset: usize, + exprf: ExprF, +) -> OffsetExpr { + OffsetExpr { ann: (prev_offset as u32, after_offset as u32), expr: Box::new(exprf) } +} + +pub(crate) fn build_offset_from_exprs(left: &OffsetExpr, right: &OffsetExpr) -> (u32, u32) { + (left.ann.0, right.ann.1) +} + +// TODO: array indexing - ast_index_to_vir_expr +// TODO: tuple indexing - ast_tuple_access_to_vir_expr + +pub(crate) fn parse_bool<'a, 'b>(input: Input<'a>) -> PResult<'a, bool> { + alt((map(tag("true"), |_| true), map(tag("false"), |_| false))).parse(input) +} + +pub(crate) fn parse_sign<'a, 'b>(input: Input<'a>) -> PResult<'a, bool> { + let (input, opt_sign) = opt(alt(( + // + value(false, tag(&b"-"[..])), + value(true, tag(&b"+"[..])), + ))) + .parse(input)?; + let sign = opt_sign.unwrap_or(true); + + Ok((input, sign)) +} + +pub(crate) fn parse_int<'a, 'b>(input: Input<'a>) -> PResult<'a, BigInt> { + let (input, sign) = parse_sign(input)?; + let (input, digits) = digit(input)?; + + let biguint = digits + .chars() + .map(|c| c.to_digit(10).expect("`digit1` should return digits")) + .fold(BigUint::ZERO, |acc, d| acc * 10u8 + d); + + let bigint = BigInt::from_biguint( + match sign { + true => Sign::Plus, + false => Sign::Minus, + }, + biguint, + ); + + Ok((input, bigint)) +} + +pub(crate) fn parse_constant_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying constant: {}", input); + let prev_offset = input.len(); + let (input, exprf) = alt(( + map(parse_bool, |b| ExprF::Literal { value: Literal::Bool(b) }), + map(parse_int, |bi| ExprF::Literal { value: Literal::Int(bi) }), + )) + .parse(input)?; + + let after_offset = input.len(); + + let res = build_expr(prev_offset, after_offset, exprf); + + Ok((input, res)) +} + +// TODO: parse identifier expression +// TODO: parse module references `fv_std::SOMETHING` +pub(crate) fn parse_identifier<'a, 'b>(input: Input<'a>) -> PResult<'a, &'a str> { + fn is_valid_start(c: char) -> bool { + c.is_ascii_alphabetic() || c == '_' + } + + fn is_valid_char(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '_' + } + + let mut parser = recognize(pair( + // + take_while1(is_valid_start), + take_while(is_valid_char), + )); + + let (input, name) = parser.parse(input)?; + + Ok((input, name)) +} + +pub(crate) fn parse_var_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying var: {}", input); + let prev_offset = input.len(); + let (input, ident) = parse_identifier(input).map_err(|_| { + // Err::Error(Error { + // resolver_errors: vec![ResolverError::ParserError(Box::new( + // NoirParserError::with_reason( + // // TODO: ne + // ParserErrorReason::DocCommentDoesNotDocumentAnything, + // Location::dummy(), + // ), + // ))], + // location: Location::dummy(), + // }) + Err::Error(Error { parser_errors: vec![ParserError::Oops] }) + })?; + let after_offset = input.len(); + + Ok((input, build_expr(prev_offset, after_offset, ExprF::Variable { name: ident.to_string() }))) +} + +pub(crate) fn parse_fn_call_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying call: {}", input); + let prev_offset = input.len(); + let (input, name) = parse_identifier(input)?; + + let (input, params) = delimited( + tag("("), + separated_list0(pair(tag(","), multispace), parse_expression_expr), + tag(")"), + ) + .parse(input)?; + let after_offset = input.len(); + + Ok(( + input, + build_expr( + prev_offset, + after_offset, + ExprF::FnCall { name: name.to_string(), args: params }, + ), + )) +} + +pub(crate) fn parse_additive_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying additive: {}", input); + let (input, (first, remainder)) = pair( + parse_multiplicative_expr, + many0(pair( + delimited(multispace, alt((tag("+"), tag("-"))), multispace), + parse_multiplicative_expr, + )), + ) + .parse(input)?; + + Ok(( + input, + remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { + let op_kind = match op { + "+" => BinaryOp::ArithmeticOp(ArithmeticOp::Add), + "-" => BinaryOp::ArithmeticOp(ArithmeticOp::Sub), + _ => unreachable!(), + }; + OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + } + }), + )) +} + +pub(crate) fn parse_multiplicative_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying multiplicative: {}", input); + let (input, (first, remainder)) = pair( + parse_unary_expr, + many0(pair( + delimited(multispace, alt((tag("*"), tag("/"), tag("%"))), multispace), + parse_unary_expr, + )), + ) + .parse(input)?; + + Ok(( + input, + remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { + let op_kind = match op { + "*" => BinaryOp::ArithmeticOp(ArithmeticOp::Mul), + "/" => BinaryOp::ArithmeticOp(ArithmeticOp::Div), + "%" => BinaryOp::ArithmeticOp(ArithmeticOp::Mod), + _ => unreachable!(), + }; + OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + } + }), + )) +} + +pub(crate) fn parse_expression_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying expression: {}", input); + // NOTE: Start parsing from the lowest precedence operator + alt(( + // + parse_implication_expr, + )) + .parse(input) +} + +pub(crate) fn parse_parenthesised_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying parenthesised: {}", input); + delimited(multispace, delimited(tag("("), parse_expression_expr, tag(")")), multispace) + .parse(input) +} + +pub(crate) fn parse_primary_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying primary: {}", input); + alt(( + // + parse_parenthesised_expr, + parse_fn_call_expr, + parse_constant_expr, + parse_var_expr, + )) + .parse(input) +} + +pub(crate) fn parse_unary_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying unary: {}", input); + alt(( + map(preceded(terminated(tag("!"), multispace), parse_unary_expr), |expr| OffsetExpr { + ann: expr.ann, + expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), + }), + parse_primary_expr, + )) + .parse(input) +} + +pub(crate) fn parse_comparison_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying comparison: {}", input); + let (input, mut expr_left) = parse_additive_expr(input)?; + + // Comparison operators are not associative (e.g., `a < b < c` is invalid), + // so we just look for one optional occurrence. + if let Ok((input, (op, expr_right))) = pair( + delimited( + multispace, + alt((tag("=="), tag("!="), tag("<="), tag(">="), tag("<"), tag(">"))), + multispace, + ), + parse_additive_expr, + ) + .parse(input) + { + let op_kind = match op { + "==" => BinaryOp::PredicateOp(PredicateOp::Eq), + "!=" => BinaryOp::PredicateOp(PredicateOp::Neq), + "<" => BinaryOp::PredicateOp(PredicateOp::Lt), + "<=" => BinaryOp::PredicateOp(PredicateOp::Le), + ">" => BinaryOp::PredicateOp(PredicateOp::Gt), + ">=" => BinaryOp::PredicateOp(PredicateOp::Ge), + _ => unreachable!(), + }; + expr_left = OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + }; + + return Ok((input, expr_left)); + } + + Ok((input, expr_left)) +} + +pub(crate) fn parse_logical_and_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying and: {}", input); + let (input, (first, remainder)) = pair( + parse_comparison_expr, + many0(pair(delimited(multispace, tag("&"), multispace), parse_comparison_expr)), + ) + .parse(input)?; + + Ok(( + input, + remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { + op: BinaryOp::BooleanOp(BooleanOp::And), + expr_left, + expr_right, + }), + }), + )) +} + +pub(crate) fn parse_logical_or_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying or: {}", input); + let (input, (first, remainder)) = pair( + parse_logical_and_expr, + many0(pair(delimited(multispace, tag("|"), multispace), parse_logical_and_expr)), + ) + .parse(input)?; + + Ok(( + input, + remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { + op: BinaryOp::BooleanOp(BooleanOp::Or), + expr_left, + expr_right, + }), + }), + )) +} + +pub(crate) fn parse_implication_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + // eprintln!("trying implication: {}", input); + let (input, (first, remainder)) = pair( + parse_logical_or_expr, + many0(pair(delimited(multispace, tag("==>"), multispace), parse_logical_or_expr)), + ) + .parse(input)?; + + Ok(( + input, + remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { + op: BinaryOp::BooleanOp(BooleanOp::Implies), + expr_left, + expr_right, + }), + }), + )) +} + +#[cfg(test)] +pub mod tests { + use noirc_frontend::{ + monomorphization::ast::{ + Expression, FuncId, Function, InlineType, LocalId, Type as NoirType, + }, + shared::Visibility, + }; + + use crate::{Attribute, State, ast::Literal, parse_attribute}; + + use super::*; + + pub fn empty_state(full_length: u32) -> State<'static> { + State { + full_length, + location: Location { span: Span::inclusive(1000, 2000), file: Default::default() }, + function: Box::leak(Box::new(Function { + id: FuncId(4321), + name: "tutmanik".to_string(), + parameters: vec![ + ( + LocalId(0), + false, + "a".to_string(), + NoirType::Integer( + noirc_frontend::shared::Signedness::Signed, + noirc_frontend::ast::IntegerBitSize::ThirtyTwo, + ), + Visibility::Public, + ), + (LocalId(1), false, "kek".to_string(), NoirType::Unit, Visibility::Public), + ( + LocalId(2), + false, + "Banica_123_".to_string(), + NoirType::Bool, + Visibility::Public, + ), + ], + body: Expression::Block(vec![]), + return_type: NoirType::Integer( + noirc_frontend::shared::Signedness::Signed, + noirc_frontend::ast::IntegerBitSize::ThirtyTwo, + ), + return_visibility: Visibility::Public, + unconstrained: false, + inline_type: InlineType::Inline, + func_sig: (vec![], None), + formal_verification_attributes: vec![], + })), + global_constants: Box::leak(Box::new(vec![].into_iter().collect())), + functions: Box::leak(Box::new( + vec![( + FuncId(0), + Function { + id: FuncId(0), + name: "banica".to_string(), + // TODO: not type-checking parameters, yet + // might need to do some manual dispatching + parameters: vec![], + body: Expression::Block(vec![]), + return_type: NoirType::Field, + return_visibility: Visibility::Public, + unconstrained: false, + inline_type: InlineType::Inline, + func_sig: (vec![], None), + formal_verification_attributes: vec![], + }, + )] + .into_iter() + .collect(), + )), + } + } + + pub fn parse<'a, 'b>(input: &'a str) -> PResult<'a, OffsetExpr> { + parse_expression_expr(input) + } + + #[test] + fn test_bool_true() { + let (input, expr) = parse("true").unwrap(); + assert_eq!(input, ""); + // assert!(matches!(*expr.1.typ, TypX::Bool)); + assert!(matches!(*expr.expr, ExprF::Literal { value: Literal::Bool(true) })); + } + + #[test] + fn test_int() { + let chislo = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; + let (input, expr) = parse(chislo).unwrap(); + assert_eq!(input, ""); + // assert!(matches!(*expr.1.typ, TypX::Int(IntRange::Int))); + let ExprF::Literal { value: Literal::Int(ref bi) } = *expr.expr else { panic!() }; + assert_eq!(bi.to_str_radix(10), chislo); + } + + #[test] + fn test_ident() { + let identche = "Banica_123_"; + let (input, expr) = parse(identche).unwrap(); + assert_eq!(input, ""); + // assert!(matches!(*expr.1.typ, TypX::Bool)); + let ExprF::Variable { name: i } = *expr.expr else { panic!() }; + assert_eq!(&i, identche); + } + + #[test] + #[should_panic] + fn test_ident_starts_with_digit() { + let identche = "1Banica_123_"; + let expr = parse_var_expr(identche).unwrap(); + assert_eq!(expr.0, ""); + dbg!(expr); + } + + #[test] + fn test_function_call() { + let expr = parse("banica(1, banica(a, kek))").unwrap(); + assert_eq!(expr.0, ""); + dbg!(expr); + } + + #[test] + fn test_sum() { + let identche = "1 + 2 * 3"; + let expr = parse(identche).unwrap(); + assert_eq!(expr.0, ""); + dbg!(expr); + } + + #[test] + fn test_ghost() { + let annotation = "ghost"; + let state = empty_state(annotation.len() as u32); + let attribute = parse_attribute( + annotation, + state.location, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + assert!(matches!(attribute, Attribute::Ghost)); + } +} diff --git a/compiler/formal_verification/src/parse/mod.rs b/compiler/formal_verification/src/parse/mod.rs deleted file mode 100644 index 85bf9bfd133..00000000000 --- a/compiler/formal_verification/src/parse/mod.rs +++ /dev/null @@ -1,774 +0,0 @@ -use noirc_errors::{Location, Span}; -use noirc_evaluator::vir::vir_gen::{ - Attribute, build_span_no_id, - expr_to_vir::{expr::function_name_to_vir_fun, types::ast_type_to_vir_type}, -}; -use noirc_frontend::{ - hir::resolution::errors::ResolverError, - monomorphization::{FUNC_RETURN_VAR_NAME, ast::{Expression, FuncId, Function, GlobalId, Type}}, - parser::{ParserError as NoirParserError, ParserErrorReason}, -}; -use nom::{ - Err, Finish, IResult, Parser, - branch::alt, - bytes::complete::{tag, take_while, take_while1}, - character::complete::{digit1 as digit, multispace0 as multispace, space0 as space}, - combinator::{cut, map, opt, recognize, value}, - error::{ContextError, ErrorKind, ParseError, context}, - multi::{fold_many0, many0, separated_list0, separated_list1}, - sequence::{delimited, pair, preceded, separated_pair, terminated}, -}; -use num_bigint::{BigInt, BigUint, Sign}; -use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; -use vir::ast::{ - ArithOp, AutospecUsage, BinaryOp, CallTarget, CallTargetKind, Constant, Expr, ExprX, FunX, - Ident, IntRange, Mode, Path, PathX, SpannedTyped, Typ, TypX, UnaryOp, VarIdent, - VarIdentDisambiguate, -}; -use vir::messages::Span as VirSpan; - -#[derive(Debug)] -struct State<'a> { - // span: &'a Span, - full_length: u32, - location: Location, - function: &'a Function, - global_constants: &'a BTreeMap, - functions: &'a BTreeMap, -} - -#[derive(Clone, Debug)] -struct Input<'a, 'b>(&'a str, &'b State<'b>); - -impl<'a, 'b> nom::Offset for Input<'a, 'b> { - fn offset(&self, second: &Self) -> usize { - <&'a str as nom::Offset>::offset(&self.0, &second.0) - } -} - -impl<'a, 'b> nom::Compare<&'a [u8]> for Input<'a, 'b> { - fn compare(&self, t: &'a [u8]) -> nom::CompareResult { - <&'a str as nom::Compare<&'a [u8]>>::compare(&self.0, t) - } - - fn compare_no_case(&self, t: &'a [u8]) -> nom::CompareResult { - <&'a str as nom::Compare<&'a [u8]>>::compare_no_case(&self.0, t) - } -} - -impl<'a, 'b> nom::Compare<&'a str> for Input<'a, 'b> { - fn compare(&self, t: &'a str) -> nom::CompareResult { - <&'a str as nom::Compare<&'a str>>::compare(&self.0, t) - } - - fn compare_no_case(&self, t: &'a str) -> nom::CompareResult { - <&'a str as nom::Compare<&'a str>>::compare_no_case(&self.0, t) - } -} - -impl<'a, 'b> nom::Input for Input<'a, 'b> { - type Item = <&'a str as nom::Input>::Item; - - type Iter = <&'a str as nom::Input>::Iter; - - type IterIndices = <&'a str as nom::Input>::IterIndices; - - fn input_len(&self) -> usize { - <&'a str as nom::Input>::input_len(&self.0) - } - - fn take(&self, index: usize) -> Self { - let s = <&'a str as nom::Input>::take(&self.0, index); - Self(s, self.1) - } - - fn take_from(&self, index: usize) -> Self { - let s = <&'a str as nom::Input>::take_from(&self.0, index); - Self(s, self.1) - } - - fn take_split(&self, index: usize) -> (Self, Self) { - let (l, r) = <&'a str as nom::Input>::take_split(&self.0, index); - (Self(l, self.1), Self(r, self.1)) - } - - fn position

(&self, predicate: P) -> Option - where - P: Fn(Self::Item) -> bool, - { - <&'a str as nom::Input>::position(&self.0, predicate) - } - - fn iter_elements(&self) -> Self::Iter { - <&'a str as nom::Input>::iter_elements(&self.0) - } - - fn iter_indices(&self) -> Self::IterIndices { - <&'a str as nom::Input>::iter_indices(&self.0) - } - - fn slice_index(&self, count: usize) -> Result { - <&'a str as nom::Input>::slice_index(&self.0, count) - } -} - -#[derive(Debug)] -struct Error { - resolver_errors: Vec, - location: Location, -} - -type PResult<'a, 'b, T> = IResult, T, Error>; - -impl<'a, 'b> ParseError> for Error { - fn from_error_kind(Input(input, state): Input<'a, 'b>, kind: ErrorKind) -> Self { - Self { - resolver_errors: vec![ResolverError::ParserError(Box::new(NoirParserError::empty( - noirc_frontend::token::Token::Dot, - // FIXME: whole thing - state.location, - )))], - // NOTE: 1 char - location: build_location(state, input.len(), input.len()), - // location: Location { - // span: Span::inclusive( - // state.location.span.start() + state.full_length - input.len() as u32, - // state.location.span.end() + state.full_length - input.len() as u32 + 1, - // ), - // file: state.location.file, - // }, - } - } - - fn append(Input(input, state): Input<'a, 'b>, kind: ErrorKind, mut other: Self) -> Self { - // TODO: umni neshta - other.resolver_errors.push(ResolverError::ParserError(Box::new(NoirParserError::empty( - noirc_frontend::token::Token::Caret, - build_location(state, input.len(), input.len()), - )))); - other - } -} - -// https://github.com/rust-bakery/nom/blob/main/doc/error_management.md - -fn build_location(state: &State, prev_offset: usize, after_offset: usize) -> Location { - Location { - span: Span::inclusive( - state.location.span.start() + state.full_length - prev_offset as u32, - state.location.span.start() + state.full_length - after_offset as u32, - ), - file: state.location.file, - } -} - -fn build_span(state: &State, prev_offset: usize, after_offset: usize) -> VirSpan { - build_span_no_id( - // TODO: smart debug info from `nom`? - "".to_string(), - Some(build_location(state, prev_offset, after_offset)), - ) -} - -fn build_expr( - state: &State, - prev_offset: usize, - after_offset: usize, - atypx: Arc, - exprx: ExprX, -) -> Expr { - let span = build_span(state, prev_offset, after_offset); - - SpannedTyped::new(&span, &atypx, exprx) -} - -fn build_span_from_exprs(state: &State, left: &Expr, right: &Expr) -> VirSpan { - fn convert_span(input: &str) -> Option<(u32, u32, usize)> { - if input.is_empty() { - // Input is empty, cannot decode a span - return None; - } - - let trimmed = input.trim_matches(|c| c == '(' || c == ')'); - let parts: Vec<&str> = trimmed.split(',').map(str::trim).collect(); - - if parts.len() != 3 { - // Span must have exactly three components: start, end, file_id - return None; - } - - let start_byte = parts[0].parse::().ok()?; - let final_byte = parts[1].parse::().ok()?; - let file_id = parts[2].parse::().ok()?; - - Some((start_byte, final_byte, file_id)) - } - - let new_noir_span = Location { - span: Span::inclusive( - convert_span(&left.span.as_string).unwrap().0, - convert_span(&right.span.as_string).unwrap().1, - ), - file: state.location.file, - }; - - build_span_no_id( - "".to_string(), // The text content is for debug info, not critical here - Some(new_noir_span), - ) -} - -// TODO: array indexing - ast_index_to_vir_expr -// TODO: tuple indexing - ast_tuple_access_to_vir_expr - -pub fn parse_attribute<'a>( - annotation: &'a str, - mut location: Location, - function: &'a Function, - global_constants: &'a BTreeMap, - functions: &'a BTreeMap, -) -> Result> { - // NOTE: #['...] - // ^^^^^^^ - received `Location` - // ^^^ - relevant stuff - // TODO: don't do this here - location = Location { - span: Span::inclusive(location.span.start() + 3, location.span.end() - 1), - ..location - }; - let state = State { - full_length: annotation.len() as u32, - location, - function, - global_constants, - functions, - }; - let input = Input(annotation, &state); - let (input, attribute_type) = - parse_identifier(input).finish().map_err(|e| e.resolver_errors)?; - - let get_expr = || { - let (rest, expr) = parse_expression_expr(input).finish().map_err(|e| e.resolver_errors)?; - assert_eq!(rest.0, ""); - Ok::<_, Vec>(expr) - }; - - Ok(match attribute_type { - "ghost" => Attribute::Ghost, - "ensures" => Attribute::Ensures(get_expr()?), - "requires" => Attribute::Requires(get_expr()?), - _ => { - return Err(vec![ResolverError::VariableNotDeclared { - name: attribute_type.to_string(), - location, - }]); - } - }) -} - -fn parse_bool<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, bool> { - alt((map(tag("true"), |_| true), map(tag("false"), |_| false))).parse(Input(input, state)) -} - -fn parse_sign<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, bool> { - let (input, opt_sign) = opt(alt(( - // - value(false, tag(&b"-"[..])), - value(true, tag(&b"+"[..])), - ))) - .parse(Input(input, state))?; - let sign = opt_sign.unwrap_or(true); - - Ok((input, sign)) -} - -fn parse_int<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, BigInt> { - let (input, sign) = parse_sign(Input(input, state))?; - let (input, Input(digits, _)) = digit(input)?; - - let biguint = digits - .chars() - .map(|c| c.to_digit(10).expect("`digit1` should return digits")) - .fold(BigUint::ZERO, |acc, d| acc * 10u8 + d); - - let bigint = BigInt::from_biguint( - match sign { - true => Sign::Plus, - false => Sign::Minus, - }, - biguint, - ); - - Ok((input, bigint)) -} - -fn parse_constant_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying constant: {}", input); - let prev_offset = input.len(); - let (Input(input, _), (typx, exprx)) = alt(( - map(parse_bool, |b| (TypX::Bool, ExprX::Const(Constant::Bool(b)))), - map(parse_int, |bi| { - // TODO: Better type than `TypX::Int` - (TypX::Int(IntRange::Int), ExprX::Const(Constant::Int(bi))) - }), - )) - .parse(Input(input, state))?; - - let after_offset = input.len(); - - let res = build_expr(state, prev_offset, after_offset, Arc::new(typx), exprx); - - Ok((Input(input, state), res)) -} - -// TODO: parse identifier expression -// TODO: parse module references `fv_std::SOMETHING` -fn parse_identifier<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, &'a str> { - fn is_valid_start(c: char) -> bool { - c.is_ascii_alphabetic() || c == '_' - } - - fn is_valid_char(c: char) -> bool { - c.is_ascii_alphanumeric() || c == '_' - } - - let mut parser = recognize(pair( - // - take_while1(is_valid_start), - take_while(is_valid_char), - )); - - let (input, Input(s, _)) = parser.parse(Input(input, state))?; - - Ok((input, s)) -} - -fn parse_var_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying var: {}", input); - let prev_offset = input.len(); - let (Input(input, _), ident) = parse_identifier(Input(input, state)).map_err(|_| { - Err::Error(Error { - resolver_errors: vec![ResolverError::ParserError(Box::new( - NoirParserError::with_reason( - // TODO: ne - ParserErrorReason::DocCommentDoesNotDocumentAnything, - Location::dummy(), - ), - ))], - location: Location::dummy(), - }) - })?; - let after_offset = input.len(); - - let (variable_ident, variable_type, variable_id) = state - .function - .parameters - .iter() - .find_map(|k| { - (k.2 == ident).then(|| { - (ident, ast_type_to_vir_type(&k.3), VarIdentDisambiguate::RustcId(k.0.0 as usize)) - }) - }) - .or_else(|| { - (ident == "result").then(|| { - (FUNC_RETURN_VAR_NAME, ast_type_to_vir_type(&state.function.return_type), VarIdentDisambiguate::AirLocal) - }) - }) - .ok_or(Err::Error(Error { - resolver_errors: vec![ResolverError::VariableNotDeclared { - name: ident.to_string(), - location: build_location(state, prev_offset, after_offset), - }], - location: build_location(state, prev_offset, after_offset), - }))?; - - Ok(( - Input(input, state), - build_expr( - state, - prev_offset, - after_offset, - // NOTE: type of variable is either found in the function's parameters - // or is the magic `result`, which inherits the function's return type - variable_type, - ExprX::Var(VarIdent(Arc::new(variable_ident.to_string()), variable_id)), - ), - )) -} - -fn parse_fn_call_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying call: {}", input); - let prev_offset = input.len(); - let (Input(input, _), name) = parse_identifier(Input(input, state))?; - - let (Input(input, _), params) = delimited( - tag("("), - separated_list0(pair(tag(","), multispace), parse_expression_expr), - tag(")"), - ) - .parse(Input(input, state))?; - let after_offset = input.len(); - - Ok(( - Input(input, state), - build_expr( - state, - prev_offset, - after_offset, - state - .functions - .iter() - .find_map(|(_, func)| (func.name == name).then_some(&func.return_type)) - .map(ast_type_to_vir_type) - .ok_or(Err::Error(Error { - resolver_errors: vec![ResolverError::VariableNotDeclared { - name: name.to_string(), - location: build_location(state, prev_offset, after_offset), - }], - location: build_location(state, prev_offset, after_offset), - }))?, - ExprX::Call( - CallTarget::Fun( - CallTargetKind::Static, - function_name_to_vir_fun(name.to_string()), - Arc::new(vec![]), - Arc::new(vec![]), - AutospecUsage::Final, - ), - Arc::new(params), - ), - ), - )) -} - -fn parse_additive_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying additive: {}", input); - let (Input(input, _), (first, remainder)) = pair( - parse_multiplicative_expr, - many0(pair( - delimited(multispace, alt((tag("+"), tag("-"))), multispace), - parse_multiplicative_expr, - )), - ) - .parse(Input(input, state))?; - - Ok(( - Input(input, state), - remainder.into_iter().fold(first, |left, (Input(op, _), right)| { - let op_kind = match op { - "+" => BinaryOp::Arith(ArithOp::Add, Mode::Spec), - "-" => BinaryOp::Arith(ArithOp::Sub, Mode::Spec), - _ => unreachable!(), - }; - let new_span = build_span_from_exprs(state, &left, &right); - let typx = left.typ.clone(); - let exprx = ExprX::Binary(op_kind, left, right); - SpannedTyped::new(&new_span, &typx, exprx) - }), - )) -} - -fn parse_multiplicative_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying multiplicative: {}", input); - let (Input(input, _), (first, remainder)) = pair( - parse_unary_expr, - many0(pair( - delimited(multispace, alt((tag("*"), tag("/"), tag("%"))), multispace), - parse_unary_expr, - )), - ) - .parse(Input(input, state))?; - - Ok(( - Input(input, state), - remainder.into_iter().fold(first, |left, (Input(op, _), right)| { - let op_kind = match op { - "*" => BinaryOp::Arith(ArithOp::Mul, Mode::Spec), - "/" => BinaryOp::Arith(ArithOp::EuclideanDiv, Mode::Spec), - "%" => BinaryOp::Arith(ArithOp::EuclideanMod, Mode::Spec), - _ => unreachable!(), - }; - let new_span = build_span_from_exprs(state, &left, &right); - let typx = left.typ.clone(); - let exprx = ExprX::Binary(op_kind, left, right); - SpannedTyped::new(&new_span, &typx, exprx) - }), - )) -} - -fn parse_expression_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying expression: {}", input); - // NOTE: Start parsing from the lowest precedence operator - alt(( - // - parse_implication_expr, - )) - .parse(Input(input, state)) -} - -fn parse_parenthesised_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying parenthesised: {}", input); - delimited(multispace, delimited(tag("("), parse_expression_expr, tag(")")), multispace) - .parse(Input(input, state)) -} - -fn parse_primary_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying primary: {}", input); - alt(( - // - parse_parenthesised_expr, - parse_fn_call_expr, - parse_constant_expr, - parse_var_expr, - )) - .parse(Input(input, state)) -} - -fn parse_unary_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying unary: {}", input); - alt(( - map(preceded(terminated(tag("!"), multispace), parse_unary_expr), |expr| { - let new_span = expr.span.clone(); - let typx = Arc::new(TypX::Bool); - let exprx = ExprX::Unary(UnaryOp::Not, expr); - SpannedTyped::new(&new_span, &typx, exprx) - }), - parse_primary_expr, - )) - .parse(Input(input, state)) -} - -fn parse_comparison_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying comparison: {}", input); - let (Input(input, _), mut left) = parse_additive_expr(Input(input, state))?; - - // Comparison operators are not associative (e.g., `a < b < c` is invalid), - // so we just look for one optional occurrence. - if let Ok((Input(input_after, _), (Input(op, _), right))) = pair( - delimited( - multispace, - alt((tag("=="), tag("!="), tag("<="), tag(">="), tag("<"), tag(">"))), - multispace, - ), - parse_additive_expr, - ) - .parse(Input(input, state)) - { - let op_kind = match op { - "==" => BinaryOp::Eq(Mode::Spec), - "!=" => BinaryOp::Ne, - "<" => BinaryOp::Inequality(vir::ast::InequalityOp::Lt), - "<=" => BinaryOp::Inequality(vir::ast::InequalityOp::Le), - ">" => BinaryOp::Inequality(vir::ast::InequalityOp::Gt), - ">=" => BinaryOp::Inequality(vir::ast::InequalityOp::Ge), - _ => unreachable!(), - }; - let new_span = build_span_from_exprs(state, &left, &right); - let typx = Arc::new(TypX::Bool); - let exprx = ExprX::Binary(op_kind, left, right); - left = SpannedTyped::new(&new_span, &typx, exprx); - return Ok((Input(input_after, state), left)); - } - - Ok((Input(input, state), left)) -} - -fn parse_logical_and_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying and: {}", input); - let (Input(input, _), (first, remainder)) = pair( - parse_comparison_expr, - many0(pair(delimited(multispace, tag("&"), multispace), parse_comparison_expr)), - ) - .parse(Input(input, state))?; - - Ok(( - Input(input, state), - remainder.into_iter().fold(first, |left, (_op, right)| { - let new_span = build_span_from_exprs(state, &left, &right); - let typx = Arc::new(TypX::Bool); - let exprx = ExprX::Binary(BinaryOp::And, left, right); - SpannedTyped::new(&new_span, &typx, exprx) - }), - )) -} - -fn parse_logical_or_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying or: {}", input); - let (Input(input, _), (first, remainder)) = pair( - parse_logical_and_expr, - many0(pair(delimited(multispace, tag("|"), multispace), parse_logical_and_expr)), - ) - .parse(Input(input, state))?; - - Ok(( - Input(input, state), - remainder.into_iter().fold(first, |left, (_op, right)| { - let new_span = build_span_from_exprs(state, &left, &right); - let typx = Arc::new(TypX::Bool); - let exprx = ExprX::Binary(BinaryOp::Or, left, right); - SpannedTyped::new(&new_span, &typx, exprx) - }), - )) -} - -fn parse_implication_expr<'a, 'b>(Input(input, state): Input<'a, 'b>) -> PResult<'a, 'b, Expr> { - // eprintln!("trying implication: {}", input); - let (Input(input, _), (first, remainder)) = pair( - parse_logical_or_expr, - many0(pair(delimited(multispace, tag("==>"), multispace), parse_logical_or_expr)), - ) - .parse(Input(input, state))?; - - Ok(( - Input(input, state), - remainder.into_iter().fold(first, |left, (_op, right)| { - let new_span = build_span_from_exprs(state, &left, &right); - let typx = Arc::new(TypX::Bool); - let exprx = ExprX::Binary(BinaryOp::Implies, left, right); - SpannedTyped::new(&new_span, &typx, exprx) - }), - )) -} - -#[cfg(test)] -mod tests { - use noirc_frontend::{ - monomorphization::ast::{FuncId, InlineType, LocalId}, - shared::Visibility, - }; - - use super::*; - - fn empty_state(full_length: u32) -> State<'static> { - State { - full_length, - location: Location { span: Span::inclusive(1000, 2000), file: Default::default() }, - function: Box::leak(Box::new(Function { - id: FuncId(4321), - name: "tutmanik".to_string(), - parameters: vec![ - (LocalId(0), false, "a".to_string(), Type::Bool, Visibility::Public), - (LocalId(1), false, "kek".to_string(), Type::Unit, Visibility::Public), - (LocalId(2), false, "Banica_123_".to_string(), Type::Bool, Visibility::Public), - ], - body: Expression::Block(vec![]), - return_type: Type::Unit, - return_visibility: Visibility::Public, - unconstrained: false, - inline_type: InlineType::Inline, - func_sig: (vec![], None), - formal_verification_attributes: vec![], - })), - global_constants: Box::leak(Box::new(vec![].into_iter().collect())), - functions: Box::leak(Box::new( - vec![( - FuncId(0), - Function { - id: FuncId(0), - name: "banica".to_string(), - // TODO: not type-checking parameters, yet - // might need to do some manual dispatching - parameters: vec![], - body: Expression::Block(vec![]), - return_type: Type::Field, - return_visibility: Visibility::Public, - unconstrained: false, - inline_type: InlineType::Inline, - func_sig: (vec![], None), - formal_verification_attributes: vec![], - }, - )] - .into_iter() - .collect(), - )), - } - } - - fn parse<'a, 'b>(input: &'a str) -> PResult<'a, 'static, Expr> { - parse_expression_expr(Input(input, Box::leak(Box::new(empty_state(input.len() as u32))))) - } - - #[test] - fn test_bool_true() { - let expr = parse("true").unwrap(); - assert_eq!(expr.0.0, ""); - assert!(matches!(*expr.1.typ, TypX::Bool)); - assert!(matches!(expr.1.x, ExprX::Const(Constant::Bool(true)))); - } - - #[test] - fn test_int() { - let chislo = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; - let expr = parse(chislo).unwrap(); - assert_eq!(expr.0.0, ""); - assert!(matches!(*expr.1.typ, TypX::Int(IntRange::Int))); - let ExprX::Const(Constant::Int(ref bi)) = expr.1.x else { panic!() }; - assert_eq!(bi.to_str_radix(10), chislo); - } - - #[test] - fn test_ident() { - let identche = "Banica_123_"; - let expr = parse(identche).unwrap(); - assert_eq!(expr.0.0, ""); - // TODO: same as definition - assert!(matches!(*expr.1.typ, TypX::Bool)); - let ExprX::Var(VarIdent(ref i, _)) = expr.1.x else { panic!() }; - assert_eq!(**i, identche.to_string()); - } - - // #[test] - // #[should_panic] - // fn test_ident_starts_with_digit() { - // let identche = "1Banica_123_"; - // let expr = parse_var_expr(&empty_state(12), identche).unwrap(); - // assert_eq!(expr.0.0, ""); - // dbg!(expr); - // } - - #[test] - fn test_function_call() { - let expr = parse("banica(1, banica(a, kek))").unwrap(); - assert_eq!(expr.0.0, ""); - dbg!(expr); - } - - #[test] - fn test_sum() { - let identche = "1 + 2 * 3"; - let expr = parse(identche).unwrap(); - assert_eq!(expr.0.0, ""); - dbg!(expr); - } - - #[test] - fn test_ghost() { - let annotation = "ghost"; - let state = empty_state(annotation.len() as u32); - let kek = parse_attribute( - annotation, - state.location, - state.function, - state.global_constants, - state.functions, - ) - .unwrap(); - } - - #[test] - fn test_ensures() { - let annotation = "ensures(kek < 127)"; - let state = empty_state(annotation.len() as u32); - let kek = parse_attribute( - annotation, - state.location, - state.function, - state.global_constants, - state.functions, - ) - .unwrap(); - - // dbg!(kek); - panic!("{:?}", kek); - } -} diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs new file mode 100644 index 00000000000..7a2a2fcdf76 --- /dev/null +++ b/compiler/formal_verification/src/typing.rs @@ -0,0 +1,259 @@ +use crate::{ + MonomorphizationRequest, State, + ast::{ + BinaryOp, ExprF, Literal, SpannedExpr, SpannedOptionallyTypedExpr, SpannedTypedExpr, + UnaryOp, cata, try_cata, + }, +}; +use noirc_frontend::monomorphization::{FUNC_RETURN_VAR_NAME, ast::Type as NoirType}; + +#[derive(Debug, Clone)] +pub enum TypeInferenceError { + MonomorphizationRequest(MonomorphizationRequest), + TypeError { got: Option, wanted: Option, message: Option }, +} + +pub fn type_infer(state: State, expr: SpannedExpr) -> Result { + let sote: SpannedOptionallyTypedExpr = try_cata(expr, &|location, exprf| { + let (exprf, exprf_type) = match &exprf { + ExprF::Literal { value } => match value { + Literal::Bool(_) => (exprf, Some(NoirType::Bool)), + Literal::Int(_) => { + // NOTE: `None` signifies that this has to be inferred up the chain, will gain + // a concrete type when it gets matched (in an arithmetic or predicate) + // operation with a variable with a real (integer) type + (exprf, None) + } + }, + ExprF::Variable { name } => { + let (variable_ident, variable_type): (&str, &NoirType) = state + .function + .parameters + .iter() + .find_map(|k| (k.2 == *name).then(|| (name.as_str(), &k.3))) + .or_else(|| { + (name == "result") + .then(|| (FUNC_RETURN_VAR_NAME, &state.function.return_type)) + }) + .ok_or(TypeInferenceError::TypeError { + got: None, + wanted: None, + message: Some(format!("Undefined variable {}", name)), + })?; + + (ExprF::Variable { name: variable_ident.to_string() }, Some(variable_type.clone())) + } + ExprF::FnCall { name, args } => { + let return_type = state + .functions + .iter() + .find_map(|(_, func)| (func.name == *name).then_some(&func.return_type)) + .ok_or(TypeInferenceError::MonomorphizationRequest( + MonomorphizationRequest { + function_identifier: name.clone(), + param_types: args + .iter() + .map(|arg: &SpannedOptionallyTypedExpr| arg.ann.1.clone()) + .collect(), + }, + ))?; + + (exprf, Some(return_type.clone())) + } + ExprF::Parenthesised { expr } => (exprf.clone(), expr.ann.1.clone()), + ExprF::UnaryOp { op, expr } => { + let expr_type = match op { + UnaryOp::Not => { + if expr.ann.1 != Some(NoirType::Bool) { + return Err(TypeInferenceError::TypeError { + got: expr.ann.1.clone(), + wanted: Some(NoirType::Bool), + message: Some("Non-boolean passed to logical not".to_string()), + }); + } + + NoirType::Bool + } + }; + + (exprf, Some(expr_type)) + } + ExprF::BinaryOp { op, expr_left, expr_right } => match op { + BinaryOp::ArithmeticOp(_) | BinaryOp::PredicateOp(_) => { + match (&expr_left.ann.1, &expr_right.ann.1) { + (None, None) => (exprf, None), + (None, Some(t2)) => { + let expr_left_inner = + cata(expr_left.clone(), &|(location, _type), expr| { + debug_assert!(_type == None); + SpannedOptionallyTypedExpr { + expr: Box::new(expr), + ann: (location, Some(t2.clone())), + } + }); + + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: expr_left_inner, + expr_right: expr_right.clone(), + }, + Some(t2.clone()), + ) + } + (Some(t1), None) => { + let expr_right_inner = + cata(expr_right.clone(), &|(location, _type), expr| { + debug_assert!(_type == None); + SpannedOptionallyTypedExpr { + expr: Box::new(expr), + ann: (location, Some(t1.clone())), + } + }); + + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: expr_left.clone(), + expr_right: expr_right_inner, + }, + Some(t1.clone()), + ) + } + (Some(t1), Some(t2)) => { + if t1 != t2 { + return Err(TypeInferenceError::TypeError { + got: Some(t2.clone()), + wanted: Some(t1.clone()), + message: Some(format!( + "Different types of arguments to {} operation", + match op { + BinaryOp::ArithmeticOp(_) => "arithmetic", + BinaryOp::PredicateOp(_) => "predicate", + _ => "unknown", + } + )), + }); + } + + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: expr_left.clone(), + expr_right: expr_right.clone(), + }, + Some(t1.clone()), + ) + } + } + } + BinaryOp::BooleanOp(_) => { + if expr_left.ann.1 != Some(NoirType::Bool) { + return Err(TypeInferenceError::TypeError { + got: expr_left.ann.1.clone(), + wanted: Some(NoirType::Bool), + message: Some( + "Boolean operations work on boolean arguments".to_string(), + ), + }); + } + if expr_right.ann.1 != Some(NoirType::Bool) { + return Err(TypeInferenceError::TypeError { + got: expr_right.ann.1.clone(), + wanted: Some(NoirType::Bool), + message: Some( + "Boolean operations work on boolean arguments".to_string(), + ), + }); + } + + (exprf, Some(NoirType::Bool)) + } + }, + }; + + Ok(SpannedOptionallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) + })?; + + let fully_typed_expr: SpannedTypedExpr = + try_cata(sote, &|(location, otype), exprf| match otype { + Some(t) => Ok(SpannedTypedExpr { ann: (location, t), expr: Box::new(exprf) }), + None => Err(()), + }) + .expect("Typing should have either succeeded or have resulted in an expected error"); + + Ok(fully_typed_expr) +} + +#[cfg(test)] +mod tests { + use noirc_frontend::ast::IntegerBitSize; + use noirc_frontend::shared::Signedness; + use std::convert::identity; + + use super::*; + + use crate::{Attribute, ast::Literal, parse::tests::*, parse_attribute}; + + #[test] + fn test_whole_attribute() { + let attribute = "ensures(result >= a + (16 / 2 % (7 * 4)))"; + let state = empty_state(attribute.len() as u32); + let attribute = parse_attribute( + attribute, + state.location, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + assert!( + cata(spanned_typed_expr, &|(_, expr_type), expr| { + match expr { + ExprF::Literal { value: Literal::Int(_) } => { + expr_type + == NoirType::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) + } + ExprF::FnCall { args, .. } => args.into_iter().all(identity), + ExprF::Parenthesised { expr } => expr, + ExprF::UnaryOp { expr, .. } => expr, + ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, + + // Non-recursive variants don't carry information + ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable { .. } => true, + } + }), + "All integer literals have the correct inferred type" + ); + } + + #[test] + fn test_monomorphization_request() { + let attribute = "ensures(f(result))"; + let state = empty_state(attribute.len() as u32); + let attribute = parse_attribute( + attribute, + state.location, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let type_inference_error = type_infer(state, spanned_expr).unwrap_err(); + let TypeInferenceError::MonomorphizationRequest(MonomorphizationRequest { + function_identifier, + param_types, + }) = type_inference_error + else { + panic!() + }; + assert_eq!(function_identifier, "f"); + assert_eq!( + param_types, + vec![Some(NoirType::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo))] + ); + } +} From a2f7e5b5922cad72aa2d8665feeb00eb32e05738 Mon Sep 17 00:00:00 2001 From: reo101 Date: Tue, 22 Jul 2025 11:02:13 +0300 Subject: [PATCH 11/86] chore(annotations): `<'a, 'b>` -> `<'a>` Remnants of the old `Input` type, which required two lifetimes --- compiler/formal_verification/src/parse.rs | 38 +++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 579b602ef47..dc56f58cea4 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -30,7 +30,7 @@ pub struct Error { pub type Input<'a> = &'a str; pub type PResult<'a, T> = IResult, T, Error>; -impl<'a, 'b> ParseError> for Error { +impl<'a> ParseError> for Error { fn from_error_kind(input: Input<'a>, kind: ErrorKind) -> Self { Self { parser_errors: vec![ParserError::Oops], @@ -77,11 +77,11 @@ pub(crate) fn build_offset_from_exprs(left: &OffsetExpr, right: &OffsetExpr) -> // TODO: array indexing - ast_index_to_vir_expr // TODO: tuple indexing - ast_tuple_access_to_vir_expr -pub(crate) fn parse_bool<'a, 'b>(input: Input<'a>) -> PResult<'a, bool> { +pub(crate) fn parse_bool<'a>(input: Input<'a>) -> PResult<'a, bool> { alt((map(tag("true"), |_| true), map(tag("false"), |_| false))).parse(input) } -pub(crate) fn parse_sign<'a, 'b>(input: Input<'a>) -> PResult<'a, bool> { +pub(crate) fn parse_sign<'a>(input: Input<'a>) -> PResult<'a, bool> { let (input, opt_sign) = opt(alt(( // value(false, tag(&b"-"[..])), @@ -93,7 +93,7 @@ pub(crate) fn parse_sign<'a, 'b>(input: Input<'a>) -> PResult<'a, bool> { Ok((input, sign)) } -pub(crate) fn parse_int<'a, 'b>(input: Input<'a>) -> PResult<'a, BigInt> { +pub(crate) fn parse_int<'a>(input: Input<'a>) -> PResult<'a, BigInt> { let (input, sign) = parse_sign(input)?; let (input, digits) = digit(input)?; @@ -113,7 +113,7 @@ pub(crate) fn parse_int<'a, 'b>(input: Input<'a>) -> PResult<'a, BigInt> { Ok((input, bigint)) } -pub(crate) fn parse_constant_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_constant_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying constant: {}", input); let prev_offset = input.len(); let (input, exprf) = alt(( @@ -131,7 +131,7 @@ pub(crate) fn parse_constant_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, Offse // TODO: parse identifier expression // TODO: parse module references `fv_std::SOMETHING` -pub(crate) fn parse_identifier<'a, 'b>(input: Input<'a>) -> PResult<'a, &'a str> { +pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { fn is_valid_start(c: char) -> bool { c.is_ascii_alphabetic() || c == '_' } @@ -151,7 +151,7 @@ pub(crate) fn parse_identifier<'a, 'b>(input: Input<'a>) -> PResult<'a, &'a str> Ok((input, name)) } -pub(crate) fn parse_var_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying var: {}", input); let prev_offset = input.len(); let (input, ident) = parse_identifier(input).map_err(|_| { @@ -172,7 +172,7 @@ pub(crate) fn parse_var_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr Ok((input, build_expr(prev_offset, after_offset, ExprF::Variable { name: ident.to_string() }))) } -pub(crate) fn parse_fn_call_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_fn_call_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying call: {}", input); let prev_offset = input.len(); let (input, name) = parse_identifier(input)?; @@ -195,7 +195,7 @@ pub(crate) fn parse_fn_call_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, Offset )) } -pub(crate) fn parse_additive_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying additive: {}", input); let (input, (first, remainder)) = pair( parse_multiplicative_expr, @@ -222,7 +222,7 @@ pub(crate) fn parse_additive_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, Offse )) } -pub(crate) fn parse_multiplicative_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying multiplicative: {}", input); let (input, (first, remainder)) = pair( parse_unary_expr, @@ -250,7 +250,7 @@ pub(crate) fn parse_multiplicative_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, )) } -pub(crate) fn parse_expression_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_expression_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying expression: {}", input); // NOTE: Start parsing from the lowest precedence operator alt(( @@ -260,13 +260,13 @@ pub(crate) fn parse_expression_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, Off .parse(input) } -pub(crate) fn parse_parenthesised_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying parenthesised: {}", input); delimited(multispace, delimited(tag("("), parse_expression_expr, tag(")")), multispace) .parse(input) } -pub(crate) fn parse_primary_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_primary_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying primary: {}", input); alt(( // @@ -278,7 +278,7 @@ pub(crate) fn parse_primary_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, Offset .parse(input) } -pub(crate) fn parse_unary_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_unary_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying unary: {}", input); alt(( map(preceded(terminated(tag("!"), multispace), parse_unary_expr), |expr| OffsetExpr { @@ -290,7 +290,7 @@ pub(crate) fn parse_unary_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetEx .parse(input) } -pub(crate) fn parse_comparison_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying comparison: {}", input); let (input, mut expr_left) = parse_additive_expr(input)?; @@ -326,7 +326,7 @@ pub(crate) fn parse_comparison_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, Off Ok((input, expr_left)) } -pub(crate) fn parse_logical_and_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_logical_and_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying and: {}", input); let (input, (first, remainder)) = pair( parse_comparison_expr, @@ -347,7 +347,7 @@ pub(crate) fn parse_logical_and_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, Of )) } -pub(crate) fn parse_logical_or_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_logical_or_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying or: {}", input); let (input, (first, remainder)) = pair( parse_logical_and_expr, @@ -368,7 +368,7 @@ pub(crate) fn parse_logical_or_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, Off )) } -pub(crate) fn parse_implication_expr<'a, 'b>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_implication_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // eprintln!("trying implication: {}", input); let (input, (first, remainder)) = pair( parse_logical_or_expr, @@ -465,7 +465,7 @@ pub mod tests { } } - pub fn parse<'a, 'b>(input: &'a str) -> PResult<'a, OffsetExpr> { + pub fn parse<'a>(input: &'a str) -> PResult<'a, OffsetExpr> { parse_expression_expr(input) } From 30ccf2d3069187d0a191c790d8ec527052f1f4da Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 12/86] fix: stop mistakingly using nightly features - Erroneously left out from some prior experiments - Also fix default `devShell` to not provide a nightly toolchain but a stable one --- compiler/formal_verification/src/ast.rs | 2 -- compiler/formal_verification/src/lib.rs | 2 -- flake.nix | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index a5ef104ec0a..05e102bb5f3 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -1,5 +1,3 @@ -use std::ops::Try; - use noirc_errors::Location; use noirc_frontend::monomorphization::ast::Type as NoirType; use num_bigint::BigInt; diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 45bf04b1e84..ab7e4a313bb 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(try_trait_v2)] - use noirc_errors::{Location, Span}; use noirc_frontend::monomorphization::ast as mast; use nom::Finish; diff --git a/flake.nix b/flake.nix index 17377bc5b75..b83134451e0 100644 --- a/flake.nix +++ b/flake.nix @@ -47,7 +47,7 @@ { legacyPackages.rustToolchain = with inputs'.fenix.packages; - with latest; + with stable; combine [ cargo clippy From d366248bb60965c38b0dc19f5756bde2ac9a956e Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 13/86] fix(annotations): predicate operations' return type Also add a few more `TODO`s --- compiler/formal_verification/src/typing.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 7a2a2fcdf76..e1d68a5fbf6 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -64,6 +64,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { let expr_type = match op { UnaryOp::Not => { + // TODO: can work with non-booleans if expr.ann.1 != Some(NoirType::Bool) { return Err(TypeInferenceError::TypeError { got: expr.ann.1.clone(), @@ -80,6 +81,11 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result match op { BinaryOp::ArithmeticOp(_) | BinaryOp::PredicateOp(_) => { + let is_arith = match op { + BinaryOp::ArithmeticOp(_) => true, + BinaryOp::PredicateOp(_) => false, + _ => unreachable!(), + }; match (&expr_left.ann.1, &expr_right.ann.1) { (None, None) => (exprf, None), (None, Some(t2)) => { @@ -98,7 +104,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { @@ -117,7 +123,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { @@ -127,11 +133,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result "arithmetic", - BinaryOp::PredicateOp(_) => "predicate", - _ => "unknown", - } + if is_arith { "arithmetic" } else { "predicate" } )), }); } @@ -142,12 +144,13 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { + // TODO: can work with non-booleans (except implication) if expr_left.ann.1 != Some(NoirType::Bool) { return Err(TypeInferenceError::TypeError { got: expr_left.ann.1.clone(), From 2f054ceb326e233808f99bddc24746f86a39837c Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Thu, 24 Jul 2025 16:22:29 +0300 Subject: [PATCH 14/86] chore(fv_bridge): Use new `formal_verification` module interface We now call the `parse_attribute` function and the result we pass to the `type_infer` function. The typed attributes are then passed to the unimplemented function `convert_typed_attribute_to_vir_attribute`. The final results are then collected and given to the original pipeline for generating VIR. --- compiler/formal_verification/src/lib.rs | 4 +- compiler/formal_verification/src/parse.rs | 8 +- compiler/formal_verification/src/typing.rs | 9 ++- compiler/fv_bridge/src/lib.rs | 91 ++++++++++++++++------ 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index ab7e4a313bb..0c0f006d209 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -14,10 +14,8 @@ pub mod ast; pub mod parse; pub mod typing; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct State<'a> { - pub full_length: u32, - pub location: Location, pub function: &'a mast::Function, pub global_constants: &'a BTreeMap, pub functions: &'a BTreeMap, diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index dc56f58cea4..b820e0c83a3 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -402,10 +402,8 @@ pub mod tests { use super::*; - pub fn empty_state(full_length: u32) -> State<'static> { + pub fn empty_state() -> State<'static> { State { - full_length, - location: Location { span: Span::inclusive(1000, 2000), file: Default::default() }, function: Box::leak(Box::new(Function { id: FuncId(4321), name: "tutmanik".to_string(), @@ -524,10 +522,10 @@ pub mod tests { #[test] fn test_ghost() { let annotation = "ghost"; - let state = empty_state(annotation.len() as u32); + let state = empty_state(); let attribute = parse_attribute( annotation, - state.location, + Location { span: Span::inclusive(0, annotation.len() as u32), file: Default::default() }, state.function, state.global_constants, state.functions, diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index e1d68a5fbf6..0f763240ab6 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -190,6 +190,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result), } +enum TypedAttribute { + Ghost, + Requires(SpannedTypedExpr), + Ensures(SpannedTypedExpr), +} + fn modified_monomorphize( main: node_interner::FuncId, interner: &mut NodeInterner, @@ -181,39 +189,65 @@ fn modified_monomorphize( let fv_annotations: Vec<(FuncId, Vec)> = monomorphizer .finished_functions .iter() + // Find the original function ID for each new monomorphized function. .filter_map(|(new_func_id, function)| { - new_ids_to_old_ids.get(new_func_id).map(|old_id| (new_func_id, old_id, function)) + new_ids_to_old_ids.get(new_func_id).map(|old_id| (*new_func_id, *old_id, function)) }) .map(|(new_func_id, old_id, function)| { - let tag_attributes: Vec<(&str, Location)> = monomorphizer + // Create the state once per function to avoid repeated lookups inside a loop. + let state = State { + function, + global_constants: &globals, + functions: &monomorphizer.finished_functions, + }; + + let attributes = monomorphizer .interner - .function_attributes(old_id) + .function_attributes(&old_id) .secondary .iter() - .filter_map(|attribute| match &attribute.kind { - SecondaryAttributeKind::Tag(annotation) => { + // Extract only the string-based 'tag' attributes for processing. + .filter_map(|attribute| { + if let SecondaryAttributeKind::Tag(annotation) = &attribute.kind { Some((annotation.as_str(), attribute.location)) + } else { + None } - _ => None, }) - .collect(); - - Ok(( - new_func_id.clone(), - tag_attributes - .into_iter() - .map(|(annotation_body, location)| { - parse_attribute( - annotation_body, - location, - function, - &globals, - &monomorphizer.finished_functions, - ) - .map_err(MonomorphOrResolverError::ResolverErrors) - }) - .collect::, _>>()?, - )) + .map(|(annotation_body, location)| { + // Step 1: Parse the attribute string. + let parsed_attribute = parse_attribute( + annotation_body, + location, + function, + &globals, + &monomorphizer.finished_functions, + ) + .map_err(|_| panic!() as MonomorphOrResolverError)?; + + // Step 2: Type-infer the parsed attribute expression. + let typed_attribute = match parsed_attribute { + formal_verification::Attribute::Ghost => TypedAttribute::Ghost, + formal_verification::Attribute::Ensures(expr) => { + // TODO(totel): Handle MonomorphRequest error type + let typed_expr = type_infer(state.clone(), expr) + .map_err(|_| panic!() as MonomorphOrResolverError)?; + TypedAttribute::Ensures(typed_expr) + } + formal_verification::Attribute::Requires(expr) => { + // TODO(totel): Handle MonomorphRequest error type + let typed_expr = type_infer(state.clone(), expr) + .map_err(|_| panic!() as MonomorphOrResolverError)?; + TypedAttribute::Requires(typed_expr) + } + }; + + // Step 3: Convert the typed attribute into its final representation. + Ok(convert_typed_attribute_to_vir_attribute(typed_attribute, state.clone())) + }) + .collect::, _>>()?; + + Ok((new_func_id, attributes)) }) .collect::, _>>()?; @@ -241,3 +275,10 @@ pub struct KrateAndWarnings { pub warnings: Vec, pub parse_annotations_errors: Vec, } + +fn convert_typed_attribute_to_vir_attribute( + typed_attribute: TypedAttribute, + state: State, +) -> Attribute { + todo!() +} From 78922bcb590790959ac60ee1f12d78b4c73de6cc Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 15/86] feat(annotations): quantifiers - Parse quantifier expressions (with a test) - Implement `nom::error::ContextError` for our `Error` type (now has a field keeping track of all the reported contexts) - Add `context` calls throughout the parser (replaces the now-deleted debug `eprintln!` calls) - Try using `cut` more for better early-errors (for now just in `fn_call` and `quantifier` expressions) - Make a new `try_contextual_cata` for carrying extra context - Employ `try_contextual_cata` in `type_infer` (carry extra bound variables by quantifiers until now) - Type-infer quantifier expressions (their variables employ the same type-inference strategy as number literals) - Change model of `BinaryOp` (one big `enum` again) - Fix type inference for `BinaryOp` (`[&|^]` can act as both arithmetic and boolean operators) - Add test for type-inferring quantifiers (also uses `cata` for verifying the resulting types) TODO: - Parse and type-infer array and tuple indexing expressions - Parse equality operators with a higher precedence (not personal preference, it's just how it's done in `Noir` itself) - In the type-inference algorithm, use a custom type for propagating the "untyped" terms, instead of `Option` --- compiler/formal_verification/src/ast.rs | 112 ++++++- compiler/formal_verification/src/lib.rs | 11 +- compiler/formal_verification/src/parse.rs | 191 +++++++---- compiler/formal_verification/src/typing.rs | 372 +++++++++++++-------- 4 files changed, 447 insertions(+), 239 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 05e102bb5f3..dbb44f20c74 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -10,6 +10,7 @@ pub enum ExprF { Literal { value: Literal }, Variable { name: Identifier }, FnCall { name: Identifier, args: Vec }, + Quantified { quantifier: Quantifier, name: Identifier, expr: R }, Parenthesised { expr: R }, UnaryOp { op: UnaryOp, expr: R }, BinaryOp { op: BinaryOp, expr_left: R, expr_right: R }, @@ -32,45 +33,88 @@ pub enum Literal { Int(BigInt), } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Quantifier { + Forall, + Exists, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub enum UnaryOp { + // Arithmetic and Boolean Not, } #[derive(Clone, Debug, Serialize, Deserialize)] -pub enum ArithmeticOp { +pub enum BinaryOp { + // pure Arithmetic (data -> data) Mul, Div, Mod, Add, Sub, -} + ShiftLeft, + ShiftRight, -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum PredicateOp { + // pure Predicates (data -> bool) Eq, Neq, Lt, Le, Gt, Ge, -} -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum BooleanOp { + // pure Boolean (bool -> bool) + Implies, + + // Arithmentic and Boolean And, Or, - Implies, + Xor, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum BinaryOp { - // Arithmetic (data -> data) - ArithmeticOp(ArithmeticOp), - // Predicates (data -> bool) - PredicateOp(PredicateOp), - // Boolean (bool -> bool) - BooleanOp(BooleanOp), +impl BinaryOp { + pub fn is_arithmetic(&self) -> bool { + matches!( + self, + // pure + BinaryOp::Mul + | BinaryOp::Div + | BinaryOp::Mod + | BinaryOp::Add + | BinaryOp::Sub + | BinaryOp::ShiftLeft + | BinaryOp::ShiftRight + // generic + | BinaryOp::And + | BinaryOp::Or + | BinaryOp::Xor + ) + } + + pub fn is_predicate(&self) -> bool { + matches!( + self, + BinaryOp::Eq + | BinaryOp::Neq + | BinaryOp::Lt + | BinaryOp::Le + | BinaryOp::Gt + | BinaryOp::Ge + ) + } + + pub fn is_boolean(&self) -> bool { + matches!( + self, + // pure + BinaryOp::Implies + // generic + | BinaryOp::And + | BinaryOp::Or + | BinaryOp::Xor + ) + } } pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { @@ -81,6 +125,9 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { let processed_args = args.into_iter().map(cata_fn).collect(); ExprF::FnCall { name, args: processed_args } } + ExprF::Quantified { quantifier, name, expr } => { + ExprF::Quantified { quantifier, name, expr: cata_fn(expr) } + } ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr) }, ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr) }, ExprF::BinaryOp { op, expr_left, expr_right } => { @@ -97,6 +144,9 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res let processed_args = args.into_iter().map(cata_fn).collect::, _>>()?; ExprF::FnCall { name, args: processed_args } } + ExprF::Quantified { quantifier, name, expr } => { + ExprF::Quantified { quantifier, name, expr: cata_fn(expr)? } + } ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr)? }, ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr)? }, ExprF::BinaryOp { op, expr_left, expr_right } => { @@ -121,6 +171,36 @@ pub fn try_cata( algebra(expr.ann, children_results) } +pub fn try_contextual_cata( + expr: AnnExpr, + initial_context: C, + update_context: &dyn Fn(C, &AnnExpr) -> C, + algebra: &dyn Fn(A, C, ExprF) -> Result, +) -> Result +where + C: Clone, +{ + fn recurse( + expr: AnnExpr, + context: C, + update_context: &dyn Fn(C, &AnnExpr) -> C, + algebra: &dyn Fn(A, C, ExprF) -> Result, + ) -> Result + where + C: Clone, + { + let children_context = update_context(context.clone(), &expr); + + let children_results = try_fmap(*expr.expr, &|child_expr| { + recurse(child_expr, children_context.clone(), update_context, algebra) + })?; + + algebra(expr.ann, context, children_results) + } + + recurse(expr, initial_context, update_context, algebra) +} + pub fn try_cata_recoverable( expr: AnnExpr, algebra: &dyn Fn(A, Result, E>) -> Result, diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 0c0f006d209..e3ed28a25cc 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -61,7 +61,7 @@ pub fn parse_attribute<'a>( let input = annotation; let (input, attribute_type) = parse_identifier(input).finish()?; //.map_err(|e| e.parser_errors)?; - let get_expr = || -> Result { + let parse_expr = || -> Result { let (rest, expr) = parse_expression_expr(input).finish()?; //.map_err(|e| e.parser_errors)?; assert_eq!(rest, ""); Ok(span_expr(location, annotation.len() as u32, expr)) @@ -69,14 +69,17 @@ pub fn parse_attribute<'a>( Ok(match attribute_type { "ghost" => Attribute::Ghost, - "ensures" => Attribute::Ensures(get_expr()?), - "requires" => Attribute::Requires(get_expr()?), + "ensures" => Attribute::Ensures(parse_expr()?), + "requires" => Attribute::Requires(parse_expr()?), _ => { // return Err(vec![ResolverError::VariableNotDeclared { // name: attribute_type.to_string(), // location, // }]); - return Err(ParseError { parser_errors: vec![] }); + return Err(ParseError { + parser_errors: vec![], + contexts: vec![], + }); } }) } diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index b820e0c83a3..d8cf9a75392 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -4,17 +4,15 @@ use nom::{ branch::alt, bytes::complete::{tag, take_while, take_while1}, character::complete::{digit1 as digit, multispace0 as multispace}, - combinator::{map, opt, recognize, value}, - error::{ErrorKind, ParseError}, + combinator::{cut, map, opt, recognize, value}, + error::{ContextError, ErrorKind, ParseError, context}, multi::{many0, separated_list0}, sequence::{delimited, pair, preceded, terminated}, }; use num_bigint::{BigInt, BigUint, Sign}; use std::fmt::Debug; -use crate::ast::{ - ArithmeticOp, BinaryOp, BooleanOp, ExprF, Literal, OffsetExpr, PredicateOp, UnaryOp, -}; +use crate::ast::{BinaryOp, ExprF, Literal, OffsetExpr, Quantifier, UnaryOp}; #[derive(Debug)] pub enum ParserError { @@ -25,6 +23,7 @@ pub enum ParserError { #[derive(Debug)] pub struct Error { pub parser_errors: Vec, + pub contexts: Vec, } pub type Input<'a> = &'a str; @@ -34,6 +33,7 @@ impl<'a> ParseError> for Error { fn from_error_kind(input: Input<'a>, kind: ErrorKind) -> Self { Self { parser_errors: vec![ParserError::Oops], + contexts: vec![], // location: build_location(input.len(), input.len()), } } @@ -45,6 +45,13 @@ impl<'a> ParseError> for Error { } } +impl<'a> ContextError> for Error { + fn add_context(input: Input<'a>, ctx: &'static str, mut other: Self) -> Self { + other.contexts.push(ctx.to_string()); + other + } +} + // https://github.com/rust-bakery/nom/blob/main/doc/error_management.md pub(crate) fn build_location( @@ -114,7 +121,6 @@ pub(crate) fn parse_int<'a>(input: Input<'a>) -> PResult<'a, BigInt> { } pub(crate) fn parse_constant_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying constant: {}", input); let prev_offset = input.len(); let (input, exprf) = alt(( map(parse_bool, |b| ExprF::Literal { value: Literal::Bool(b) }), @@ -129,7 +135,6 @@ pub(crate) fn parse_constant_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp Ok((input, res)) } -// TODO: parse identifier expression // TODO: parse module references `fv_std::SOMETHING` pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { fn is_valid_start(c: char) -> bool { @@ -152,7 +157,6 @@ pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { } pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying var: {}", input); let prev_offset = input.len(); let (input, ident) = parse_identifier(input).map_err(|_| { // Err::Error(Error { @@ -165,21 +169,57 @@ pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { // ))], // location: Location::dummy(), // }) - Err::Error(Error { parser_errors: vec![ParserError::Oops] }) + Err::Error(Error { parser_errors: vec![ParserError::Oops], contexts: vec![] }) })?; let after_offset = input.len(); Ok((input, build_expr(prev_offset, after_offset, ExprF::Variable { name: ident.to_string() }))) } +pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + let (input, quantifier_kind) = parse_identifier(input)?; + + let quantifier = match quantifier_kind { + "forall" => Quantifier::Forall, + "exists" => Quantifier::Exists, + _ => return Err(nom::Err::Error(Error { parser_errors: vec![], contexts: vec![] })), + }; + + let (input, _) = multispace(input)?; + // TODO: better `space` management + let (input, (name, expr)) = delimited( + tag("("), + cut(pair( + delimited( + delimited(multispace, tag("|"), multispace), + parse_identifier, + delimited(multispace, tag("|"), multispace), + ), + delimited(multispace, parse_expression_expr, multispace), + )), + tag(")"), + ) + .parse(input)?; + let after_offset = input.len(); + + Ok(( + input, + build_expr( + prev_offset, + after_offset, + ExprF::Quantified { quantifier, name: name.to_string(), expr }, + ), + )) +} + pub(crate) fn parse_fn_call_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying call: {}", input); let prev_offset = input.len(); - let (input, name) = parse_identifier(input)?; + let (input, name) = context("fn_call name", parse_identifier).parse(input)?; let (input, params) = delimited( tag("("), - separated_list0(pair(tag(","), multispace), parse_expression_expr), + cut(separated_list0(pair(tag(","), multispace), parse_expression_expr)), tag(")"), ) .parse(input)?; @@ -196,13 +236,15 @@ pub(crate) fn parse_fn_call_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr } pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying additive: {}", input); let (input, (first, remainder)) = pair( parse_multiplicative_expr, - many0(pair( - delimited(multispace, alt((tag("+"), tag("-"))), multispace), - parse_multiplicative_expr, - )), + context( + "additive", + many0(pair( + delimited(multispace, alt((tag("+"), tag("-"))), multispace), + parse_multiplicative_expr, + )), + ), ) .parse(input)?; @@ -210,8 +252,8 @@ pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp input, remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { let op_kind = match op { - "+" => BinaryOp::ArithmeticOp(ArithmeticOp::Add), - "-" => BinaryOp::ArithmeticOp(ArithmeticOp::Sub), + "+" => BinaryOp::Add, + "-" => BinaryOp::Sub, _ => unreachable!(), }; OffsetExpr { @@ -223,13 +265,15 @@ pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp } pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying multiplicative: {}", input); let (input, (first, remainder)) = pair( parse_unary_expr, - many0(pair( - delimited(multispace, alt((tag("*"), tag("/"), tag("%"))), multispace), - parse_unary_expr, - )), + context( + "multiplicative", + many0(pair( + delimited(multispace, alt((tag("*"), tag("/"), tag("%"))), multispace), + parse_unary_expr, + )), + ), ) .parse(input)?; @@ -237,9 +281,9 @@ pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, Off input, remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { let op_kind = match op { - "*" => BinaryOp::ArithmeticOp(ArithmeticOp::Mul), - "/" => BinaryOp::ArithmeticOp(ArithmeticOp::Div), - "%" => BinaryOp::ArithmeticOp(ArithmeticOp::Mod), + "*" => BinaryOp::Mul, + "/" => BinaryOp::Div, + "%" => BinaryOp::Mod, _ => unreachable!(), }; OffsetExpr { @@ -251,7 +295,6 @@ pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, Off } pub(crate) fn parse_expression_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying expression: {}", input); // NOTE: Start parsing from the lowest precedence operator alt(( // @@ -261,58 +304,61 @@ pub(crate) fn parse_expression_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE } pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying parenthesised: {}", input); delimited(multispace, delimited(tag("("), parse_expression_expr, tag(")")), multispace) .parse(input) } pub(crate) fn parse_primary_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying primary: {}", input); alt(( // - parse_parenthesised_expr, - parse_fn_call_expr, - parse_constant_expr, - parse_var_expr, + context("parenthesised", parse_parenthesised_expr), + context("quantifier", parse_quantifier_expr), + context("fn_call", parse_fn_call_expr), + context("constant", parse_constant_expr), + context("var", parse_var_expr), )) .parse(input) } pub(crate) fn parse_unary_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying unary: {}", input); alt(( - map(preceded(terminated(tag("!"), multispace), parse_unary_expr), |expr| OffsetExpr { - ann: expr.ann, - expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), - }), + context( + "unary", + map(preceded(terminated(tag("!"), multispace), parse_unary_expr), |expr| OffsetExpr { + ann: expr.ann, + expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), + }), + ), parse_primary_expr, )) .parse(input) } pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying comparison: {}", input); let (input, mut expr_left) = parse_additive_expr(input)?; // Comparison operators are not associative (e.g., `a < b < c` is invalid), // so we just look for one optional occurrence. if let Ok((input, (op, expr_right))) = pair( - delimited( - multispace, - alt((tag("=="), tag("!="), tag("<="), tag(">="), tag("<"), tag(">"))), - multispace, + context( + "comparison", + delimited( + multispace, + alt((tag("=="), tag("!="), tag("<="), tag(">="), tag("<"), tag(">"))), + multispace, + ), ), parse_additive_expr, ) .parse(input) { let op_kind = match op { - "==" => BinaryOp::PredicateOp(PredicateOp::Eq), - "!=" => BinaryOp::PredicateOp(PredicateOp::Neq), - "<" => BinaryOp::PredicateOp(PredicateOp::Lt), - "<=" => BinaryOp::PredicateOp(PredicateOp::Le), - ">" => BinaryOp::PredicateOp(PredicateOp::Gt), - ">=" => BinaryOp::PredicateOp(PredicateOp::Ge), + "==" => BinaryOp::Eq, + "!=" => BinaryOp::Neq, + "<" => BinaryOp::Lt, + "<=" => BinaryOp::Le, + ">" => BinaryOp::Gt, + ">=" => BinaryOp::Ge, _ => unreachable!(), }; expr_left = OffsetExpr { @@ -327,10 +373,12 @@ pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE } pub(crate) fn parse_logical_and_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying and: {}", input); let (input, (first, remainder)) = pair( parse_comparison_expr, - many0(pair(delimited(multispace, tag("&"), multispace), parse_comparison_expr)), + context( + "logical and", + many0(pair(delimited(multispace, tag("&"), multispace), parse_comparison_expr)), + ), ) .parse(input)?; @@ -338,20 +386,18 @@ pub(crate) fn parse_logical_and_expr<'a>(input: Input<'a>) -> PResult<'a, Offset input, remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { - op: BinaryOp::BooleanOp(BooleanOp::And), - expr_left, - expr_right, - }), + expr: Box::new(ExprF::BinaryOp { op: BinaryOp::And, expr_left, expr_right }), }), )) } pub(crate) fn parse_logical_or_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying or: {}", input); let (input, (first, remainder)) = pair( parse_logical_and_expr, - many0(pair(delimited(multispace, tag("|"), multispace), parse_logical_and_expr)), + context( + "logical or", + many0(pair(delimited(multispace, tag("|"), multispace), parse_logical_and_expr)), + ), ) .parse(input)?; @@ -359,20 +405,20 @@ pub(crate) fn parse_logical_or_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE input, remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { - op: BinaryOp::BooleanOp(BooleanOp::Or), - expr_left, - expr_right, - }), + expr: Box::new(ExprF::BinaryOp { op: BinaryOp::Or, expr_left, expr_right }), }), )) } pub(crate) fn parse_implication_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // eprintln!("trying implication: {}", input); + // TODO: parse_equality ? `compiler/noirc_frontend/src/parser/parser/infix.rs` + // `a == b | c == d` should be `a == ((b | c) == d)` let (input, (first, remainder)) = pair( parse_logical_or_expr, - many0(pair(delimited(multispace, tag("==>"), multispace), parse_logical_or_expr)), + context( + "implication", + many0(pair(delimited(multispace, tag("==>"), multispace), parse_logical_or_expr)), + ), ) .parse(input)?; @@ -380,11 +426,7 @@ pub(crate) fn parse_implication_expr<'a>(input: Input<'a>) -> PResult<'a, Offset input, remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { - op: BinaryOp::BooleanOp(BooleanOp::Implies), - expr_left, - expr_right, - }), + expr: Box::new(ExprF::BinaryOp { op: BinaryOp::Implies, expr_left, expr_right }), }), )) } @@ -519,6 +561,13 @@ pub mod tests { dbg!(expr); } + #[test] + fn test_quantifier() { + let expr = parse("exists(|x| 1 + x)").unwrap(); + assert_eq!(expr.0, ""); + dbg!(expr); + } + #[test] fn test_ghost() { let annotation = "ghost"; diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 0f763240ab6..ff87679e3ae 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -2,7 +2,7 @@ use crate::{ MonomorphizationRequest, State, ast::{ BinaryOp, ExprF, Literal, SpannedExpr, SpannedOptionallyTypedExpr, SpannedTypedExpr, - UnaryOp, cata, try_cata, + UnaryOp, cata, try_cata, try_contextual_cata, }, }; use noirc_frontend::monomorphization::{FUNC_RETURN_VAR_NAME, ast::Type as NoirType}; @@ -14,169 +14,184 @@ pub enum TypeInferenceError { } pub fn type_infer(state: State, expr: SpannedExpr) -> Result { - let sote: SpannedOptionallyTypedExpr = try_cata(expr, &|location, exprf| { - let (exprf, exprf_type) = match &exprf { - ExprF::Literal { value } => match value { - Literal::Bool(_) => (exprf, Some(NoirType::Bool)), - Literal::Int(_) => { - // NOTE: `None` signifies that this has to be inferred up the chain, will gain - // a concrete type when it gets matched (in an arithmetic or predicate) - // operation with a variable with a real (integer) type - (exprf, None) - } - }, - ExprF::Variable { name } => { - let (variable_ident, variable_type): (&str, &NoirType) = state - .function - .parameters - .iter() - .find_map(|k| (k.2 == *name).then(|| (name.as_str(), &k.3))) - .or_else(|| { - (name == "result") - .then(|| (FUNC_RETURN_VAR_NAME, &state.function.return_type)) - }) - .ok_or(TypeInferenceError::TypeError { - got: None, - wanted: None, - message: Some(format!("Undefined variable {}", name)), - })?; - - (ExprF::Variable { name: variable_ident.to_string() }, Some(variable_type.clone())) + let sote: SpannedOptionallyTypedExpr = try_contextual_cata( + expr, + vec![], + &|mut quantifier_bound_variables, e| { + if let ExprF::Quantified { name, .. } = e.expr.as_ref() { + quantifier_bound_variables.push(name.clone()) } - ExprF::FnCall { name, args } => { - let return_type = state - .functions - .iter() - .find_map(|(_, func)| (func.name == *name).then_some(&func.return_type)) - .ok_or(TypeInferenceError::MonomorphizationRequest( - MonomorphizationRequest { - function_identifier: name.clone(), - param_types: args - .iter() - .map(|arg: &SpannedOptionallyTypedExpr| arg.ann.1.clone()) - .collect(), - }, - ))?; + quantifier_bound_variables + }, + &|location, quantifier_bound_variables, exprf| { + let (exprf, exprf_type) = match &exprf { + ExprF::Literal { value } => match value { + Literal::Bool(_) => (exprf, Some(NoirType::Bool)), + Literal::Int(_) => { + // NOTE: `None` signifies that this has to be inferred up the chain, will gain + // a concrete type when it gets matched (in an arithmetic or predicate) + // operation with a variable with a real (integer) type + (exprf, None) + } + }, + ExprF::Variable { name } => { + let (variable_ident, variable_type): (&str, Option) = + quantifier_bound_variables + .iter() + .find_map(|bound_variable| { + (bound_variable == name).then(|| (name.as_str(), None)) + }) + .or_else(|| { + state.function.parameters.iter().find_map(|k| { + (k.2 == *name).then(|| (name.as_str(), Some(k.3.clone()))) + }) + }) + .or_else(|| { + (name == "result").then(|| { + (FUNC_RETURN_VAR_NAME, Some(state.function.return_type.clone())) + }) + }) + .ok_or(TypeInferenceError::TypeError { + got: None, + wanted: None, + message: Some(format!("Undefined variable {}", name)), + })?; - (exprf, Some(return_type.clone())) - } - ExprF::Parenthesised { expr } => (exprf.clone(), expr.ann.1.clone()), - ExprF::UnaryOp { op, expr } => { - let expr_type = match op { - UnaryOp::Not => { - // TODO: can work with non-booleans - if expr.ann.1 != Some(NoirType::Bool) { - return Err(TypeInferenceError::TypeError { - got: expr.ann.1.clone(), - wanted: Some(NoirType::Bool), - message: Some("Non-boolean passed to logical not".to_string()), - }); - } + (ExprF::Variable { name: variable_ident.to_string() }, variable_type) + } + ExprF::FnCall { name, args } => { + let return_type = state + .functions + .iter() + .find_map(|(_, func)| (func.name == *name).then_some(&func.return_type)) + .ok_or(TypeInferenceError::MonomorphizationRequest( + MonomorphizationRequest { + function_identifier: name.clone(), + param_types: args + .iter() + .map(|arg: &SpannedOptionallyTypedExpr| arg.ann.1.clone()) + .collect(), + }, + ))?; - NoirType::Bool - } - }; + (exprf, Some(return_type.clone())) + } + ExprF::Quantified { .. } => (exprf, Some(NoirType::Bool)), + ExprF::Parenthesised { expr } => (exprf.clone(), expr.ann.1.clone()), + ExprF::UnaryOp { op: _, expr } => (exprf.clone(), expr.ann.1.clone()), + ExprF::BinaryOp { op, expr_left, expr_right } => { + match op { + BinaryOp::Mul + | BinaryOp::Div + | BinaryOp::Mod + | BinaryOp::Add + | BinaryOp::Sub + | BinaryOp::ShiftLeft + | BinaryOp::ShiftRight + | BinaryOp::Eq + | BinaryOp::Neq + | BinaryOp::Lt + | BinaryOp::Le + | BinaryOp::Gt + | BinaryOp::Ge + | BinaryOp::And + | BinaryOp::Or + | BinaryOp::Xor => { + let is_arith = op.is_arithmetic(); + match (&expr_left.ann.1, &expr_right.ann.1) { + (None, None) => (exprf, None), + (None, Some(t2)) => { + let expr_left_inner = + cata(expr_left.clone(), &|(location, _type), expr| { + debug_assert!(_type == None); + SpannedOptionallyTypedExpr { + expr: Box::new(expr), + ann: (location, Some(t2.clone())), + } + }); - (exprf, Some(expr_type)) - } - ExprF::BinaryOp { op, expr_left, expr_right } => match op { - BinaryOp::ArithmeticOp(_) | BinaryOp::PredicateOp(_) => { - let is_arith = match op { - BinaryOp::ArithmeticOp(_) => true, - BinaryOp::PredicateOp(_) => false, - _ => unreachable!(), - }; - match (&expr_left.ann.1, &expr_right.ann.1) { - (None, None) => (exprf, None), - (None, Some(t2)) => { - let expr_left_inner = - cata(expr_left.clone(), &|(location, _type), expr| { - debug_assert!(_type == None); - SpannedOptionallyTypedExpr { - expr: Box::new(expr), - ann: (location, Some(t2.clone())), - } - }); + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: expr_left_inner, + expr_right: expr_right.clone(), + }, + Some(if is_arith { t2.clone() } else { NoirType::Bool }), + ) + } + (Some(t1), None) => { + let expr_right_inner = + cata(expr_right.clone(), &|(location, _type), expr| { + debug_assert!(_type == None); + SpannedOptionallyTypedExpr { + expr: Box::new(expr), + ann: (location, Some(t1.clone())), + } + }); - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: expr_left_inner, - expr_right: expr_right.clone(), - }, - Some(if is_arith { t2.clone() } else { NoirType::Bool }), - ) - } - (Some(t1), None) => { - let expr_right_inner = - cata(expr_right.clone(), &|(location, _type), expr| { - debug_assert!(_type == None); - SpannedOptionallyTypedExpr { - expr: Box::new(expr), - ann: (location, Some(t1.clone())), + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: expr_left.clone(), + expr_right: expr_right_inner, + }, + Some(if is_arith { t1.clone() } else { NoirType::Bool }), + ) + } + (Some(t1), Some(t2)) => { + if t1 != t2 { + return Err(TypeInferenceError::TypeError { + got: Some(t2.clone()), + wanted: Some(t1.clone()), + message: Some(format!( + "Different types of arguments to {} operation", + if is_arith { "arithmetic" } else { "predicate" } + )), + }); } - }); - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: expr_left.clone(), - expr_right: expr_right_inner, - }, - Some(if is_arith { t1.clone() } else { NoirType::Bool }), - ) + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: expr_left.clone(), + expr_right: expr_right.clone(), + }, + Some(if is_arith { t1.clone() } else { NoirType::Bool }), + ) + } + } } - (Some(t1), Some(t2)) => { - if t1 != t2 { + + // pure Boolean + BinaryOp::Implies => { + if expr_left.ann.1 != Some(NoirType::Bool) { + return Err(TypeInferenceError::TypeError { + got: expr_left.ann.1.clone(), + wanted: Some(NoirType::Bool), + message: Some( + "Boolean operations work on boolean arguments".to_string(), + ), + }); + } + if expr_right.ann.1 != Some(NoirType::Bool) { return Err(TypeInferenceError::TypeError { - got: Some(t2.clone()), - wanted: Some(t1.clone()), - message: Some(format!( - "Different types of arguments to {} operation", - if is_arith { "arithmetic" } else { "predicate" } - )), + got: expr_right.ann.1.clone(), + wanted: Some(NoirType::Bool), + message: Some( + "Boolean operations work on boolean arguments".to_string(), + ), }); } - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: expr_left.clone(), - expr_right: expr_right.clone(), - }, - Some(if is_arith { t1.clone() } else { NoirType::Bool }), - ) + (exprf, Some(NoirType::Bool)) } } } - BinaryOp::BooleanOp(_) => { - // TODO: can work with non-booleans (except implication) - if expr_left.ann.1 != Some(NoirType::Bool) { - return Err(TypeInferenceError::TypeError { - got: expr_left.ann.1.clone(), - wanted: Some(NoirType::Bool), - message: Some( - "Boolean operations work on boolean arguments".to_string(), - ), - }); - } - if expr_right.ann.1 != Some(NoirType::Bool) { - return Err(TypeInferenceError::TypeError { - got: expr_right.ann.1.clone(), - wanted: Some(NoirType::Bool), - message: Some( - "Boolean operations work on boolean arguments".to_string(), - ), - }); - } - - (exprf, Some(NoirType::Bool)) - } - }, - }; + }; - Ok(SpannedOptionallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) - })?; + Ok(SpannedOptionallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) + }, + )?; let fully_typed_expr: SpannedTypedExpr = try_cata(sote, &|(location, otype), exprf| match otype { @@ -221,6 +236,43 @@ mod tests { == NoirType::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) } ExprF::FnCall { args, .. } => args.into_iter().all(identity), + ExprF::Quantified { expr, .. } => expr, + ExprF::Parenthesised { expr } => expr, + ExprF::UnaryOp { expr, .. } => expr, + ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, + + // Non-recursive variants don't carry information + ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable { .. } => true, + } + }), + "All integer literals have the correct inferred type" + ); + } + + #[test] + fn test_quantifiers() { + let attribute = "ensures(exists(|i| result >= a + (16 / i % (7 * 4))))"; + let state = empty_state(); + let attribute = parse_attribute( + attribute, + Location { span: Span::inclusive(0, attribute.len() as u32), file: Default::default() }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + assert!( + cata(spanned_typed_expr.clone(), &|(_, expr_type), expr| { + match expr { + ExprF::Literal { value: Literal::Int(_) } => { + expr_type + == NoirType::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) + } + + ExprF::FnCall { args, .. } => args.into_iter().all(identity), + ExprF::Quantified { expr, .. } => expr, ExprF::Parenthesised { expr } => expr, ExprF::UnaryOp { expr, .. } => expr, ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, @@ -231,6 +283,30 @@ mod tests { }), "All integer literals have the correct inferred type" ); + assert!( + cata(spanned_typed_expr, &|(_, expr_type), expr| { + match expr { + ExprF::Variable { name } => { + if name == "i" { + expr_type + == NoirType::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) + } else { + true + } + } + + ExprF::FnCall { args, .. } => args.into_iter().all(identity), + ExprF::Quantified { expr, .. } => expr, + ExprF::Parenthesised { expr } => expr, + ExprF::UnaryOp { expr, .. } => expr, + ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, + + // Non-recursive variants don't carry information + ExprF::Literal { .. } => true, + } + }), + "All bound variables have the correct inferred type" + ); } #[test] From 81c4fe1b5dbbe2e12f9adc883f81bc8b9e755bb3 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 16/86] chore(rust): update to `1.88.0` --- flake.lock | 30 +++++++++++++++--------------- rust-toolchain.toml | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/flake.lock b/flake.lock index 46fba19bf79..5727fd6914e 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1748846362, - "narHash": "sha256-D2LUpaxJFrkahPE1U6S01u1ZY9Wsr82jOSjAVoIZ/hs=", + "lastModified": 1753339339, + "narHash": "sha256-r9Frae3VnbgKrWWQcV6WEykm6PxfPHVrxdOqgyt1VcU=", "owner": "nix-community", "repo": "fenix", - "rev": "6a5e421c05cb29bffecdb3a1c3c80cec22d62efd", + "rev": "af1c3d7dd242ebf50ac75665d5c44af79730b491", "type": "github" }, "original": { @@ -26,11 +26,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1748821116, - "narHash": "sha256-F82+gS044J1APL0n4hH50GYdPRv/5JWm34oCJYmVKdE=", + "lastModified": 1753121425, + "narHash": "sha256-TVcTNvOeWWk1DXljFxVRp+E0tzG1LhrVjOGGoMHuXio=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "49f0870db23e8c1ca0b5259734a02cd9e1e371a1", + "rev": "644e0fc48951a860279da645ba77fe4a6e814c5e", "type": "github" }, "original": { @@ -41,11 +41,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748693115, - "narHash": "sha256-StSrWhklmDuXT93yc3GrTlb0cKSS0agTAxMGjLKAsY8=", + "lastModified": 1753250450, + "narHash": "sha256-i+CQV2rPmP8wHxj0aq4siYyohHwVlsh40kV89f3nw1s=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "910796cabe436259a29a72e8d3f5e180fc6dfacc", + "rev": "fc02ee70efb805d3b2865908a13ddd4474557ecf", "type": "github" }, "original": { @@ -73,11 +73,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1748740939, - "narHash": "sha256-rQaysilft1aVMwF14xIdGS3sj1yHlI6oKQNBRTF40cc=", + "lastModified": 1751159883, + "narHash": "sha256-urW/Ylk9FIfvXfliA1ywh75yszAbiTEVgpPeinFyVZo=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "656a64127e9d791a334452c6b6606d17539476e2", + "rev": "14a40a1d7fb9afa4739275ac642ed7301a9ba1ab", "type": "github" }, "original": { @@ -98,11 +98,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1748695646, - "narHash": "sha256-VwSuuRF4NvAoeHZJRRlX8zAFZ+nZyuiIvmVqBAX0Bcg=", + "lastModified": 1753282007, + "narHash": "sha256-PgTUdSShfGVqZtDJk5noAB1rKdD/MeZ1zq4jY8n9p3c=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "2a388d1103450d814a84eda98efe89c01b158343", + "rev": "b5b10fb10facef4d18b4cae8ef22e640ca601255", "type": "github" }, "original": { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ed1915d2613..0aaa62c24ab 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.85.0" +channel = "1.88.0" components = [ "rust-src" ] targets = [ "wasm32-unknown-unknown", "wasm32-wasip1", "aarch64-apple-darwin" ] profile = "default" From 56ea2fcd7aadc3102989fe9f9a4e897cffb1bef2 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 17/86] feat(annotations): check if integer literals fit in their holes - Create a `bi_can_fit_in` function that finds the smallest type, in which a `BigInteger` can fit and checks if that fits inside a predetermined type. - Use that to verify if all integer literals fit in their inferred types - Add a big test that tests all edge cases --- compiler/formal_verification/src/typing.rs | 191 +++++++++++++++++++-- 1 file changed, 176 insertions(+), 15 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index ff87679e3ae..fcb18dedfc8 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -5,7 +5,13 @@ use crate::{ UnaryOp, cata, try_cata, try_contextual_cata, }, }; -use noirc_frontend::monomorphization::{FUNC_RETURN_VAR_NAME, ast::Type as NoirType}; +use noirc_frontend::{ + ast::IntegerBitSize, + monomorphization::{FUNC_RETURN_VAR_NAME, ast::Type as NoirType}, + shared::Signedness, +}; +use num_bigint::{BigInt, BigUint}; +use num_traits::{One, Zero}; #[derive(Debug, Clone)] pub enum TypeInferenceError { @@ -13,6 +19,64 @@ pub enum TypeInferenceError { TypeError { got: Option, wanted: Option, message: Option }, } +#[derive(Debug, PartialEq, Eq)] +enum FitsIn { + Yes, + No { + /// `None` means there is no possible `IntegerBitSize` that could contain us + need: Option, + }, +} +fn bi_can_fit_in(bi: &BigInt, hole_size: &IntegerBitSize, hole_sign: &Signedness) -> FitsIn { + let is_negative = match bi.sign() { + num_bigint::Sign::Minus => true, + num_bigint::Sign::Plus | num_bigint::Sign::NoSign => false, + }; + + // NOTE: if we have a negative literal, but try to fit into an unsigned integer + let is_wrong_sign = is_negative && !hole_sign.is_signed(); + + let mut bits = bi.bits(); + + // NOTE: Add one bit for the sign. + if hole_sign.is_signed() { + bits += 1; + } + + if is_negative { + fn is_power_of_two(n: &BigUint) -> bool { + // 10000000 + // 01111111 + !n.is_zero() && ((n & (n - BigUint::one())) == BigUint::zero()) + } + + // NOTE: ...unless we have a negative number whose magnitude is a power of two. + // This is the because it fits perfectly into the two's complement minimum value. + // (e.g., -128 fits in `i8`, the same as -127, but not 128). + if is_power_of_two(bi.magnitude()) { + bits -= 1; + } + } + + // NOTE: find the smallest `IntegerBitSize` that could contain us, `None` if no such one exists + let smallest_fit_size = + IntegerBitSize::allowed_sizes().into_iter().find(|size| bits <= size.bit_size() as u64); + + // NOTE: using the `PartialOrd` instance for `IntegerBitSize`, + // which definitionally orders the bit sizes in an increasing order + let size_fits = smallest_fit_size.map(|sfs| sfs <= *hole_size).unwrap_or(false); + + if !size_fits || is_wrong_sign { + return FitsIn::No { + need: smallest_fit_size.map(|sfs| { + NoirType::Integer(if is_wrong_sign { Signedness::Signed } else { *hole_sign }, sfs) + }), + }; + } + + return FitsIn::Yes; +} + pub fn type_infer(state: State, expr: SpannedExpr) -> Result { let sote: SpannedOptionallyTypedExpr = try_contextual_cata( expr, @@ -98,17 +162,48 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { let is_arith = op.is_arithmetic(); + + let propagate_concrete_type = + |e: SpannedOptionallyTypedExpr, t: NoirType| { + let NoirType::Integer(hole_sign, hole_size) = &t else { + todo!("Can only propagate integer types"); + }; + try_cata(e, &|(location, _type), expr| { + debug_assert!(_type == None); + match expr { + ExprF::Literal { value: Literal::Int(ref bi) } => { + let fits = bi_can_fit_in(bi, hole_size, hole_sign); + match fits { + FitsIn::Yes => {} + FitsIn::No { need } => { + return Err( + TypeInferenceError::TypeError { + got: need.clone(), + wanted: Some(t.clone()), + message: Some(format!( + "Integer literal {} cannot fit in {}, needs at least {:?} or larger", + bi, t, need, + )), + }, + ); + } + } + } + _ => {} + } + + Ok(SpannedOptionallyTypedExpr { + expr: Box::new(expr), + ann: (location, Some(t.clone())), + }) + }) + }; + match (&expr_left.ann.1, &expr_right.ann.1) { (None, None) => (exprf, None), (None, Some(t2)) => { let expr_left_inner = - cata(expr_left.clone(), &|(location, _type), expr| { - debug_assert!(_type == None); - SpannedOptionallyTypedExpr { - expr: Box::new(expr), - ann: (location, Some(t2.clone())), - } - }); + propagate_concrete_type(expr_left.clone(), t2.clone())?; ( ExprF::BinaryOp { @@ -121,13 +216,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { let expr_right_inner = - cata(expr_right.clone(), &|(location, _type), expr| { - debug_assert!(_type == None); - SpannedOptionallyTypedExpr { - expr: Box::new(expr), - ann: (location, Some(t1.clone())), - } - }); + propagate_concrete_type(expr_right.clone(), t1.clone())?; ( ExprF::BinaryOp { @@ -208,12 +297,84 @@ mod tests { use noirc_errors::{Location, Span}; use noirc_frontend::ast::IntegerBitSize; use noirc_frontend::shared::Signedness; + use num_traits::Num; use std::convert::identity; use super::*; use crate::{Attribute, ast::Literal, parse::tests::*, parse_attribute}; + #[test] + fn test_integer_types() { + { + let bi127 = BigInt::from_str_radix("127", 10).unwrap(); + let bi128 = BigInt::from_str_radix("128", 10).unwrap(); + let bi129 = BigInt::from_str_radix("129", 10).unwrap(); + let bin127 = BigInt::from_str_radix("-127", 10).unwrap(); + let bin128 = BigInt::from_str_radix("-128", 10).unwrap(); + let bin129 = BigInt::from_str_radix("-129", 10).unwrap(); + + let hole_size = IntegerBitSize::Eight; + let hole_sign = Signedness::Signed; + + let bi127_fit = bi_can_fit_in(&bi127, &hole_size, &hole_sign); + let bi128_fit = bi_can_fit_in(&bi128, &hole_size, &hole_sign); + let bi129_fit = bi_can_fit_in(&bi129, &hole_size, &hole_sign); + let bin127_fit = bi_can_fit_in(&bin127, &hole_size, &hole_sign); + let bin128_fit = bi_can_fit_in(&bin128, &hole_size, &hole_sign); + let bin129_fit = bi_can_fit_in(&bin129, &hole_size, &hole_sign); + + assert_eq!(bi127_fit, FitsIn::Yes); + assert_eq!( + bi128_fit, + FitsIn::No { need: Some(NoirType::Integer(hole_sign, IntegerBitSize::Sixteen)) } + ); + assert_eq!( + bi129_fit, + FitsIn::No { need: Some(NoirType::Integer(hole_sign, IntegerBitSize::Sixteen)) } + ); + + assert_eq!(bin127_fit, FitsIn::Yes); + assert_eq!(bin128_fit, FitsIn::Yes); + assert_eq!( + bin129_fit, + FitsIn::No { need: Some(NoirType::Integer(hole_sign, IntegerBitSize::Sixteen)) } + ); + } + + { + let bi255 = BigInt::from_str_radix("255", 10).unwrap(); + let bi256 = BigInt::from_str_radix("256", 10).unwrap(); + let bi257 = BigInt::from_str_radix("257", 10).unwrap(); + let bin1 = BigInt::from_str_radix("-1", 10).unwrap(); + + let hole_size = IntegerBitSize::Eight; + let hole_sign = Signedness::Unsigned; + + let bi255_fit = bi_can_fit_in(&bi255, &hole_size, &hole_sign); + let bi256_fit = bi_can_fit_in(&bi256, &hole_size, &hole_sign); + let bi257_fit = bi_can_fit_in(&bi257, &hole_size, &hole_sign); + let bin1_fit = bi_can_fit_in(&bin1, &hole_size, &hole_sign); + + assert_eq!(bi255_fit, FitsIn::Yes); + assert_eq!( + bi256_fit, + FitsIn::No { need: Some(NoirType::Integer(hole_sign, IntegerBitSize::Sixteen)) } + ); + assert_eq!( + bi257_fit, + FitsIn::No { need: Some(NoirType::Integer(hole_sign, IntegerBitSize::Sixteen)) } + ); + + assert_eq!( + bin1_fit, + FitsIn::No { + need: Some(NoirType::Integer(Signedness::Signed, IntegerBitSize::One)) + } + ); + } + } + #[test] fn test_whole_attribute() { let attribute = "ensures(result >= a + (16 / 2 % (7 * 4)))"; From 3665291e5829cdd1cb5bb1b02e704a7c4d040741 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 18/86] feat(annotations): reorder all parsers to adhere to upstream Noir - We now (should) parse much more closely to Noir Example is `a == b | c == d` being `((a == (b | c)) == d)` - Add comments for missing parsers in the correct places (in terms of parsing precedence) --- compiler/formal_verification/src/lib.rs | 4 +- compiler/formal_verification/src/parse.rs | 550 +++++++++++++--------- 2 files changed, 329 insertions(+), 225 deletions(-) diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index e3ed28a25cc..aada4a149ce 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -6,7 +6,7 @@ use vir::messages::Span as VirSpan; use crate::{ ast::{OffsetExpr, SpannedExpr, cata}, - parse::{Error as ParseError, build_location, parse_expression_expr, parse_identifier}, + parse::{Error as ParseError, build_location, parse_expression, parse_identifier}, }; // NOTE: all types inside are not prefixed, to be used as `ast::OffsetExpr` @@ -62,7 +62,7 @@ pub fn parse_attribute<'a>( let (input, attribute_type) = parse_identifier(input).finish()?; //.map_err(|e| e.parser_errors)?; let parse_expr = || -> Result { - let (rest, expr) = parse_expression_expr(input).finish()?; //.map_err(|e| e.parser_errors)?; + let (rest, expr) = parse_expression(input).finish()?; //.map_err(|e| e.parser_errors)?; assert_eq!(rest, ""); Ok(span_expr(location, annotation.len() as u32, expr)) }; diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index d8cf9a75392..b2c315e543e 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -81,170 +81,101 @@ pub(crate) fn build_offset_from_exprs(left: &OffsetExpr, right: &OffsetExpr) -> (left.ann.0, right.ann.1) } +/************************************************************************* +* Main parser combinators, in order of precedence, like in upstream Noir * +**************************************************************************/ + // TODO: array indexing - ast_index_to_vir_expr // TODO: tuple indexing - ast_tuple_access_to_vir_expr -pub(crate) fn parse_bool<'a>(input: Input<'a>) -> PResult<'a, bool> { - alt((map(tag("true"), |_| true), map(tag("false"), |_| false))).parse(input) -} - -pub(crate) fn parse_sign<'a>(input: Input<'a>) -> PResult<'a, bool> { - let (input, opt_sign) = opt(alt(( +pub(crate) fn parse_expression<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + alt(( // - value(false, tag(&b"-"[..])), - value(true, tag(&b"+"[..])), - ))) - .parse(input)?; - let sign = opt_sign.unwrap_or(true); - - Ok((input, sign)) -} - -pub(crate) fn parse_int<'a>(input: Input<'a>) -> PResult<'a, BigInt> { - let (input, sign) = parse_sign(input)?; - let (input, digits) = digit(input)?; - - let biguint = digits - .chars() - .map(|c| c.to_digit(10).expect("`digit1` should return digits")) - .fold(BigUint::ZERO, |acc, d| acc * 10u8 + d); - - let bigint = BigInt::from_biguint( - match sign { - true => Sign::Plus, - false => Sign::Minus, - }, - biguint, - ); - - Ok((input, bigint)) -} - -pub(crate) fn parse_constant_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let prev_offset = input.len(); - let (input, exprf) = alt(( - map(parse_bool, |b| ExprF::Literal { value: Literal::Bool(b) }), - map(parse_int, |bi| ExprF::Literal { value: Literal::Int(bi) }), + parse_implication_expr, )) - .parse(input)?; - - let after_offset = input.len(); - - let res = build_expr(prev_offset, after_offset, exprf); - - Ok((input, res)) -} - -// TODO: parse module references `fv_std::SOMETHING` -pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { - fn is_valid_start(c: char) -> bool { - c.is_ascii_alphabetic() || c == '_' - } - - fn is_valid_char(c: char) -> bool { - c.is_ascii_alphanumeric() || c == '_' - } - - let mut parser = recognize(pair( - // - take_while1(is_valid_start), - take_while(is_valid_char), - )); - - let (input, name) = parser.parse(input)?; - - Ok((input, name)) + .parse(input) } -pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let prev_offset = input.len(); - let (input, ident) = parse_identifier(input).map_err(|_| { - // Err::Error(Error { - // resolver_errors: vec![ResolverError::ParserError(Box::new( - // NoirParserError::with_reason( - // // TODO: ne - // ParserErrorReason::DocCommentDoesNotDocumentAnything, - // Location::dummy(), - // ), - // ))], - // location: Location::dummy(), - // }) - Err::Error(Error { parser_errors: vec![ParserError::Oops], contexts: vec![] }) - })?; - let after_offset = input.len(); +pub(crate) fn parse_implication_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let (input, (first, remainder)) = pair( + parse_equality_expr, + context( + "implication", + many0(pair(delimited(multispace, tag("==>"), multispace), parse_equality_expr)), + ), + ) + .parse(input)?; - Ok((input, build_expr(prev_offset, after_offset, ExprF::Variable { name: ident.to_string() }))) + Ok(( + input, + remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { + let op_kind = match op { + "==>" => BinaryOp::Implies, + _ => unreachable!(), + }; + OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + } + }), + )) } -pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let prev_offset = input.len(); - let (input, quantifier_kind) = parse_identifier(input)?; - - let quantifier = match quantifier_kind { - "forall" => Quantifier::Forall, - "exists" => Quantifier::Exists, - _ => return Err(nom::Err::Error(Error { parser_errors: vec![], contexts: vec![] })), - }; - - let (input, _) = multispace(input)?; - // TODO: better `space` management - let (input, (name, expr)) = delimited( - tag("("), - cut(pair( - delimited( - delimited(multispace, tag("|"), multispace), - parse_identifier, - delimited(multispace, tag("|"), multispace), - ), - delimited(multispace, parse_expression_expr, multispace), - )), - tag(")"), +pub(crate) fn parse_equality_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let (input, (first, remainder)) = pair( + parse_or_expr, + context( + "equality", + many0(pair( + delimited(multispace, alt((tag("=="), tag("!="))), multispace), + parse_or_expr, + )), + ), ) .parse(input)?; - let after_offset = input.len(); Ok(( input, - build_expr( - prev_offset, - after_offset, - ExprF::Quantified { quantifier, name: name.to_string(), expr }, - ), + remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { + let op_kind = match op { + "==" => BinaryOp::Eq, + "!=" => BinaryOp::Neq, + _ => unreachable!(), + }; + OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + } + }), )) } -pub(crate) fn parse_fn_call_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let prev_offset = input.len(); - let (input, name) = context("fn_call name", parse_identifier).parse(input)?; - - let (input, params) = delimited( - tag("("), - cut(separated_list0(pair(tag(","), multispace), parse_expression_expr)), - tag(")"), +pub(crate) fn parse_or_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let (input, (first, remainder)) = pair( + parse_and_expr, + context("or", many0(pair(delimited(multispace, tag("|"), multispace), parse_and_expr))), ) .parse(input)?; - let after_offset = input.len(); Ok(( input, - build_expr( - prev_offset, - after_offset, - ExprF::FnCall { name: name.to_string(), args: params }, - ), + remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { + let op_kind = match op { + "|" => BinaryOp::Or, + _ => unreachable!(), + }; + OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + } + }), )) } -pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_and_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (input, (first, remainder)) = pair( - parse_multiplicative_expr, - context( - "additive", - many0(pair( - delimited(multispace, alt((tag("+"), tag("-"))), multispace), - parse_multiplicative_expr, - )), - ), + parse_xor_expr, + context("and", many0(pair(delimited(multispace, tag("&"), multispace), parse_xor_expr))), ) .parse(input)?; @@ -252,8 +183,7 @@ pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp input, remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { let op_kind = match op { - "+" => BinaryOp::Add, - "-" => BinaryOp::Sub, + "&" => BinaryOp::And, _ => unreachable!(), }; OffsetExpr { @@ -264,15 +194,12 @@ pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp )) } -pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_xor_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (input, (first, remainder)) = pair( - parse_unary_expr, + parse_comparison_expr, context( - "multiplicative", - many0(pair( - delimited(multispace, alt((tag("*"), tag("/"), tag("%"))), multispace), - parse_unary_expr, - )), + "xor", + many0(pair(delimited(multispace, tag("^"), multispace), parse_comparison_expr)), ), ) .parse(input)?; @@ -281,9 +208,7 @@ pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, Off input, remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { let op_kind = match op { - "*" => BinaryOp::Mul, - "/" => BinaryOp::Div, - "%" => BinaryOp::Mod, + "^" => BinaryOp::Xor, _ => unreachable!(), }; OffsetExpr { @@ -293,48 +218,39 @@ pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, Off }), )) } +pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let (input, mut expr_left) = parse_shift_expr(input)?; -pub(crate) fn parse_expression_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // NOTE: Start parsing from the lowest precedence operator - alt(( - // - parse_implication_expr, - )) - .parse(input) -} - -pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - delimited(multispace, delimited(tag("("), parse_expression_expr, tag(")")), multispace) - .parse(input) -} - -pub(crate) fn parse_primary_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - alt(( - // - context("parenthesised", parse_parenthesised_expr), - context("quantifier", parse_quantifier_expr), - context("fn_call", parse_fn_call_expr), - context("constant", parse_constant_expr), - context("var", parse_var_expr), - )) - .parse(input) -} - -pub(crate) fn parse_unary_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - alt(( + // Comparison operators are not associative (e.g., `a < b < c` is invalid), + // so we just look for one optional occurrence. + if let Ok((input, (op, expr_right))) = pair( context( - "unary", - map(preceded(terminated(tag("!"), multispace), parse_unary_expr), |expr| OffsetExpr { - ann: expr.ann, - expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), - }), + "comparison", + delimited(multispace, alt((tag("<="), tag(">="), tag("<"), tag(">"))), multispace), ), - parse_primary_expr, - )) + parse_shift_expr, + ) .parse(input) + { + let op_kind = match op { + "<" => BinaryOp::Lt, + "<=" => BinaryOp::Le, + ">" => BinaryOp::Gt, + ">=" => BinaryOp::Ge, + _ => unreachable!(), + }; + expr_left = OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + }; + + return Ok((input, expr_left)); + } + + Ok((input, expr_left)) } -pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_shift_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (input, mut expr_left) = parse_additive_expr(input)?; // Comparison operators are not associative (e.g., `a < b < c` is invalid), @@ -342,19 +258,13 @@ pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE if let Ok((input, (op, expr_right))) = pair( context( "comparison", - delimited( - multispace, - alt((tag("=="), tag("!="), tag("<="), tag(">="), tag("<"), tag(">"))), - multispace, - ), + delimited(multispace, alt((tag("<="), tag(">="), tag("<"), tag(">"))), multispace), ), parse_additive_expr, ) .parse(input) { let op_kind = match op { - "==" => BinaryOp::Eq, - "!=" => BinaryOp::Neq, "<" => BinaryOp::Lt, "<=" => BinaryOp::Le, ">" => BinaryOp::Gt, @@ -372,65 +282,249 @@ pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE Ok((input, expr_left)) } -pub(crate) fn parse_logical_and_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (input, (first, remainder)) = pair( - parse_comparison_expr, + parse_multiplicative_expr, context( - "logical and", - many0(pair(delimited(multispace, tag("&"), multispace), parse_comparison_expr)), + "additive", + many0(pair( + delimited(multispace, alt((tag("+"), tag("-"))), multispace), + parse_multiplicative_expr, + )), ), ) .parse(input)?; Ok(( input, - remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { - ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: BinaryOp::And, expr_left, expr_right }), + remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { + let op_kind = match op { + "+" => BinaryOp::Add, + "-" => BinaryOp::Sub, + _ => unreachable!(), + }; + OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + } }), )) } -pub(crate) fn parse_logical_or_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (input, (first, remainder)) = pair( - parse_logical_and_expr, + parse_unary_expr, context( - "logical or", - many0(pair(delimited(multispace, tag("|"), multispace), parse_logical_and_expr)), + "multiplicative", + many0(pair( + delimited(multispace, alt((tag("*"), tag("/"), tag("%"))), multispace), + parse_unary_expr, + )), ), ) .parse(input)?; Ok(( input, - remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { - ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: BinaryOp::Or, expr_left, expr_right }), + remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { + let op_kind = match op { + "*" => BinaryOp::Mul, + "/" => BinaryOp::Div, + "%" => BinaryOp::Mod, + _ => unreachable!(), + }; + OffsetExpr { + ann: build_offset_from_exprs(&expr_left, &expr_right), + expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + } }), )) } -pub(crate) fn parse_implication_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - // TODO: parse_equality ? `compiler/noirc_frontend/src/parser/parser/infix.rs` - // `a == b | c == d` should be `a == ((b | c) == d)` - let (input, (first, remainder)) = pair( - parse_logical_or_expr, +pub(crate) fn parse_unary_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + alt(( context( - "implication", - many0(pair(delimited(multispace, tag("==>"), multispace), parse_logical_or_expr)), + "unary", + map(preceded(terminated(tag("!"), multispace), parse_unary_expr), |expr| OffsetExpr { + ann: expr.ann, + expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), + }), ), + parse_atom_expr, + )) + .parse(input) +} + +pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + alt(( + context("parenthesised", parse_parenthesised_expr), + context("quantifier", parse_quantifier_expr), + // context("path", parse_path_expr), + context("fn_call", parse_fn_call_expr), + // context("member", parse_member_expr), + // context("method_call", parse_method_call_expr), + // context("index", parse_index_expr), + context("literal", parse_literal_expr), + context("var", parse_var_expr), + )) + .parse(input) +} + +pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + delimited(multispace, delimited(tag("("), parse_expression, tag(")")), multispace).parse(input) +} + +pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + let (input, quantifier_kind) = parse_identifier(input)?; + + let quantifier = match quantifier_kind { + "forall" => Quantifier::Forall, + "exists" => Quantifier::Exists, + _ => return Err(nom::Err::Error(Error { parser_errors: vec![], contexts: vec![] })), + }; + + let (input, _) = multispace(input)?; + // TODO: better `space` management + let (input, (name, expr)) = delimited( + tag("("), + cut(pair( + delimited( + delimited(multispace, tag("|"), multispace), + parse_identifier, + delimited(multispace, tag("|"), multispace), + ), + delimited(multispace, parse_expression, multispace), + )), + tag(")"), ) .parse(input)?; + let after_offset = input.len(); Ok(( input, - remainder.into_iter().fold(first, |expr_left, (_op, expr_right)| OffsetExpr { - ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: BinaryOp::Implies, expr_left, expr_right }), - }), + build_expr( + prev_offset, + after_offset, + ExprF::Quantified { quantifier, name: name.to_string(), expr }, + ), )) } +pub(crate) fn parse_fn_call_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + let (input, name) = context("fn_call name", parse_identifier).parse(input)?; + + let (input, params) = delimited( + tag("("), + cut(separated_list0(pair(tag(","), multispace), parse_expression)), + tag(")"), + ) + .parse(input)?; + let after_offset = input.len(); + + Ok(( + input, + build_expr( + prev_offset, + after_offset, + ExprF::FnCall { name: name.to_string(), args: params }, + ), + )) +} + +pub(crate) fn parse_literal_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + let (input, exprf) = alt(( + map(parse_bool, |b| ExprF::Literal { value: Literal::Bool(b) }), + map(parse_int, |bi| ExprF::Literal { value: Literal::Int(bi) }), + )) + .parse(input)?; + + let after_offset = input.len(); + + let res = build_expr(prev_offset, after_offset, exprf); + + Ok((input, res)) +} + +pub(crate) fn parse_bool<'a>(input: Input<'a>) -> PResult<'a, bool> { + alt((map(tag("true"), |_| true), map(tag("false"), |_| false))).parse(input) +} + +pub(crate) fn parse_int<'a>(input: Input<'a>) -> PResult<'a, BigInt> { + let (input, sign) = parse_sign(input)?; + let (input, digits) = digit(input)?; + + let biguint = digits + .chars() + .map(|c| c.to_digit(10).expect("`digit1` should return digits")) + .fold(BigUint::ZERO, |acc, d| acc * 10u8 + d); + + let bigint = BigInt::from_biguint( + match sign { + true => Sign::Plus, + false => Sign::Minus, + }, + biguint, + ); + + Ok((input, bigint)) +} + +pub(crate) fn parse_sign<'a>(input: Input<'a>) -> PResult<'a, bool> { + let (input, opt_sign) = opt(alt(( + // + value(false, tag(&b"-"[..])), + value(true, tag(&b"+"[..])), + ))) + .parse(input)?; + let sign = opt_sign.unwrap_or(true); + + Ok((input, sign)) +} + +pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + let (input, ident) = parse_identifier(input).map_err(|_| { + // Err::Error(Error { + // resolver_errors: vec![ResolverError::ParserError(Box::new( + // NoirParserError::with_reason( + // // TODO: ne + // ParserErrorReason::DocCommentDoesNotDocumentAnything, + // Location::dummy(), + // ), + // ))], + // location: Location::dummy(), + // }) + Err::Error(Error { parser_errors: vec![ParserError::Oops], contexts: vec![] }) + })?; + let after_offset = input.len(); + + Ok((input, build_expr(prev_offset, after_offset, ExprF::Variable { name: ident.to_string() }))) +} + +// TODO: parse module references `fv_std::SOMETHING` +pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { + fn is_valid_start(c: char) -> bool { + c.is_ascii_alphabetic() || c == '_' + } + + fn is_valid_char(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '_' + } + + let mut parser = recognize(pair( + // + take_while1(is_valid_start), + take_while(is_valid_char), + )); + + let (input, name) = parser.parse(input)?; + + Ok((input, name)) +} + #[cfg(test)] pub mod tests { use noirc_frontend::{ @@ -506,7 +600,7 @@ pub mod tests { } pub fn parse<'a>(input: &'a str) -> PResult<'a, OffsetExpr> { - parse_expression_expr(input) + parse_expression(input) } #[test] @@ -542,30 +636,37 @@ pub mod tests { fn test_ident_starts_with_digit() { let identche = "1Banica_123_"; let expr = parse_var_expr(identche).unwrap(); + dbg!(&expr); assert_eq!(expr.0, ""); - dbg!(expr); } #[test] fn test_function_call() { let expr = parse("banica(1, banica(a, kek))").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + + #[test] + fn test_equality() { + let expr = parse("a == b | c == d").unwrap(); + dbg!(&expr); assert_eq!(expr.0, ""); - dbg!(expr); } #[test] fn test_sum() { let identche = "1 + 2 * 3"; let expr = parse(identche).unwrap(); + dbg!(&expr); assert_eq!(expr.0, ""); - dbg!(expr); } #[test] fn test_quantifier() { let expr = parse("exists(|x| 1 + x)").unwrap(); + dbg!(&expr); assert_eq!(expr.0, ""); - dbg!(expr); } #[test] @@ -574,7 +675,10 @@ pub mod tests { let state = empty_state(); let attribute = parse_attribute( annotation, - Location { span: Span::inclusive(0, annotation.len() as u32), file: Default::default() }, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, state.function, state.global_constants, state.functions, From fb2de5f76ba13cc165cdc4c255e66683a739cfc5 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 19/86] feat(annotations)!: add array and tuple indexing - Correctly parse and type infer array and tuple indexes (array indexes get an index expression, tuple ones get a literal) - Add tests for parsing and type inferrence of array and tuple indexes - Add an array and a tuple to the `empty_state()` parameters (for tests) - Promote `propagate_concrete_type` to a global function (now also used in the `ExprF::Index` type inferrence) TODO: - Better look at the spans of `parse_portfix_expr` - Quantifier/Variable `id` field (needed for the later conversion to `vir`) --- compiler/formal_verification/src/ast.rs | 69 +++++--- compiler/formal_verification/src/parse.rs | 120 ++++++++++++-- compiler/formal_verification/src/typing.rs | 174 ++++++++++++++++----- 3 files changed, 286 insertions(+), 77 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index dbb44f20c74..6bc479671ea 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -7,13 +7,25 @@ pub type Identifier = String; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum ExprF { - Literal { value: Literal }, - Variable { name: Identifier }, - FnCall { name: Identifier, args: Vec }, - Quantified { quantifier: Quantifier, name: Identifier, expr: R }, - Parenthesised { expr: R }, - UnaryOp { op: UnaryOp, expr: R }, BinaryOp { op: BinaryOp, expr_left: R, expr_right: R }, + UnaryOp { op: UnaryOp, expr: R }, + Parenthesised { expr: R }, + Quantified { + quantifier: Quantifier, + // TODO: ids + name: Identifier, + expr: R, + }, + FnCall { name: Identifier, args: Vec }, + Index { expr: R, index: R }, + TupleAccess { expr: R, index: u32 }, + Literal { value: Literal }, + Variable { + name: Identifier, + // TODO: ids + // LocalId from Noir + // local_id: u32, + }, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -119,39 +131,50 @@ impl BinaryOp { pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { match expr { - ExprF::Literal { value } => ExprF::Literal { value }, - ExprF::Variable { name } => ExprF::Variable { name }, - ExprF::FnCall { name, args } => { - let processed_args = args.into_iter().map(cata_fn).collect(); - ExprF::FnCall { name, args: processed_args } + ExprF::BinaryOp { op, expr_left, expr_right } => { + ExprF::BinaryOp { op, expr_left: cata_fn(expr_left), expr_right: cata_fn(expr_right) } } + ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr) }, + ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr) }, ExprF::Quantified { quantifier, name, expr } => { ExprF::Quantified { quantifier, name, expr: cata_fn(expr) } } - ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr) }, - ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr) }, - ExprF::BinaryOp { op, expr_left, expr_right } => { - ExprF::BinaryOp { op, expr_left: cata_fn(expr_left), expr_right: cata_fn(expr_right) } + ExprF::FnCall { name, args } => { + ExprF::FnCall { name, args: args.into_iter().map(cata_fn).collect() } + } + ExprF::Index { expr: indexee, index } => { + ExprF::Index { expr: cata_fn(indexee), index: cata_fn(index) } } + ExprF::TupleAccess { expr, index: member } => { + ExprF::TupleAccess { expr: cata_fn(expr), index: member } + } + ExprF::Literal { value } => ExprF::Literal { value }, + ExprF::Variable { name } => ExprF::Variable { name }, } } fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Result, E> { Ok(match expr { - ExprF::Literal { value } => ExprF::Literal { value }, - ExprF::Variable { name } => ExprF::Variable { name }, + ExprF::BinaryOp { op, expr_left, expr_right } => { + ExprF::BinaryOp { op, expr_left: cata_fn(expr_left)?, expr_right: cata_fn(expr_right)? } + } + ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr)? }, + ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr)? }, + ExprF::Quantified { quantifier, name, expr } => { + ExprF::Quantified { quantifier, name, expr: cata_fn(expr)? } + } ExprF::FnCall { name, args } => { let processed_args = args.into_iter().map(cata_fn).collect::, _>>()?; ExprF::FnCall { name, args: processed_args } } - ExprF::Quantified { quantifier, name, expr } => { - ExprF::Quantified { quantifier, name, expr: cata_fn(expr)? } + ExprF::Index { expr: indexee, index } => { + ExprF::Index { expr: cata_fn(indexee)?, index: cata_fn(index)? } } - ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr)? }, - ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr)? }, - ExprF::BinaryOp { op, expr_left, expr_right } => { - ExprF::BinaryOp { op, expr_left: cata_fn(expr_left)?, expr_right: cata_fn(expr_right)? } + ExprF::TupleAccess { expr, index: member } => { + ExprF::TupleAccess { expr: cata_fn(expr)?, index: member } } + ExprF::Literal { value } => ExprF::Literal { value }, + ExprF::Variable { name } => ExprF::Variable { name }, }) } diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index b2c315e543e..773d29ecaf5 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -1,18 +1,11 @@ use noirc_errors::{Location, Span}; use nom::{ - Err, IResult, Parser, - branch::alt, - bytes::complete::{tag, take_while, take_while1}, - character::complete::{digit1 as digit, multispace0 as multispace}, - combinator::{cut, map, opt, recognize, value}, - error::{ContextError, ErrorKind, ParseError, context}, - multi::{many0, separated_list0}, - sequence::{delimited, pair, preceded, terminated}, + branch::alt, bytes::complete::{tag, take_while, take_while1}, character::complete::{digit1 as digit, multispace0 as multispace}, combinator::{cut, fail, map, opt, recognize, value}, error::{context, ContextError, ErrorKind, ParseError}, multi::{many0, separated_list0}, sequence::{delimited, pair, preceded, terminated}, Err, IResult, Parser }; use num_bigint::{BigInt, BigUint, Sign}; use std::fmt::Debug; -use crate::ast::{BinaryOp, ExprF, Literal, OffsetExpr, Quantifier, UnaryOp}; +use crate::ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp}; #[derive(Debug)] pub enum ParserError { @@ -313,12 +306,12 @@ pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (input, (first, remainder)) = pair( - parse_unary_expr, + parse_prefix_expr, context( "multiplicative", many0(pair( delimited(multispace, alt((tag("*"), tag("/"), tag("%"))), multispace), - parse_unary_expr, + parse_prefix_expr, )), ), ) @@ -341,20 +334,81 @@ pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, Off )) } -pub(crate) fn parse_unary_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_prefix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { alt(( context( - "unary", - map(preceded(terminated(tag("!"), multispace), parse_unary_expr), |expr| OffsetExpr { + "prefix", + map(preceded(terminated(tag("!"), multispace), parse_prefix_expr), |expr| OffsetExpr { ann: expr.ann, expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), }), ), - parse_atom_expr, + parse_postfix_expr, )) .parse(input) } +enum Postfix { + ArrayIndex(OffsetExpr), + TupleMember(BigInt), +} + +pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let (input, base_expr) = parse_atom_expr(input)?; + + let (input, suffixes) = many0(parse_any_suffix).parse(input)?; + + let final_expr = suffixes.into_iter().try_fold(base_expr, |current_expr, suffix| { + // TODO: real span + let new_ann = current_expr.ann; + + Ok(match suffix { + Postfix::ArrayIndex(index_expr) => { + let ann = build_offset_from_exprs(¤t_expr, &index_expr); + OffsetExpr { + ann, + expr: Box::new(ExprF::Index { expr: current_expr, index: index_expr }), + } + } + Postfix::TupleMember(index) => { + OffsetExpr { + ann: new_ann, + expr: Box::new(ExprF::TupleAccess { + expr: current_expr, + index: index.try_into().map_err(|_| { + nom::Err::Error(Error { parser_errors: vec![], contexts: vec![] }) + })?, + }), + } + } + }) + })?; + + Ok((input, final_expr)) +} + +fn parse_any_suffix<'a>(input: Input<'a>) -> PResult<'a, Postfix> { + alt(( + map(parse_index_suffix, Postfix::ArrayIndex), + map(parse_member_suffix, Postfix::TupleMember), + )) + .parse(input) +} + +/// Parses an index suffix `[expr]` and returns just the inner expression. +fn parse_index_suffix<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + preceded( + multispace, + delimited(tag("["), cut(delimited(multispace, parse_expression, multispace)), tag("]")), + ) + .parse(input) +} + +/// Parses a member access `.field` and returns just the field identifier. +fn parse_member_suffix<'a>(input: Input<'a>) -> PResult<'a, BigInt> { + preceded(pair(multispace, tag(".")), cut(parse_int)).parse(input) +} + pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { alt(( context("parenthesised", parse_parenthesised_expr), @@ -363,7 +417,6 @@ pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { context("fn_call", parse_fn_call_expr), // context("member", parse_member_expr), // context("method_call", parse_method_call_expr), - // context("index", parse_index_expr), context("literal", parse_literal_expr), context("var", parse_var_expr), )) @@ -562,6 +615,20 @@ pub mod tests { NoirType::Bool, Visibility::Public, ), + ( + LocalId(3), + false, + "xs".to_string(), + NoirType::Array(3, Box::new(NoirType::Field)), + Visibility::Public, + ), + ( + LocalId(3), + false, + "user".to_string(), + NoirType::Tuple(vec![NoirType::Bool, NoirType::Unit]), + Visibility::Public, + ), ], body: Expression::Block(vec![]), return_type: NoirType::Integer( @@ -669,6 +736,27 @@ pub mod tests { assert_eq!(expr.0, ""); } + #[test] + fn test_index() { + let expr = parse("(tutmanik + 3)[12 < 3]").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + + #[test] + fn test_member() { + let expr = parse("ala.0.17").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + + #[test] + fn test_postfix() { + let expr = parse("5 + ala.126[nica].012[1[3]][2].15[da] / 5").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + #[test] fn test_ghost() { let annotation = "ghost"; diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index fcb18dedfc8..2a4049e0f3d 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -20,14 +20,14 @@ pub enum TypeInferenceError { } #[derive(Debug, PartialEq, Eq)] -enum FitsIn { +pub enum FitsIn { Yes, No { /// `None` means there is no possible `IntegerBitSize` that could contain us need: Option, }, } -fn bi_can_fit_in(bi: &BigInt, hole_size: &IntegerBitSize, hole_sign: &Signedness) -> FitsIn { +pub fn bi_can_fit_in(bi: &BigInt, hole_size: &IntegerBitSize, hole_sign: &Signedness) -> FitsIn { let is_negative = match bi.sign() { num_bigint::Sign::Minus => true, num_bigint::Sign::Plus | num_bigint::Sign::NoSign => false, @@ -77,6 +77,46 @@ fn bi_can_fit_in(bi: &BigInt, hole_size: &IntegerBitSize, hole_sign: &Signedness return FitsIn::Yes; } +pub fn propagate_concrete_type( + e: SpannedOptionallyTypedExpr, + t: NoirType, +) -> Result { + let limits = match &t { + NoirType::Field => None, + NoirType::Integer(hole_sign, hole_size) => Some((hole_sign, hole_size)), + _ => todo!("Can only propagate integer types, {:#?}", t), + }; + + try_cata(e, &|(location, _type), expr| { + debug_assert!(_type == None); + // NOTE: only check limits for integer types + // (assume that `NoirType::Field` can hold anything) + if let Some((hole_sign, hole_size)) = limits { + match expr { + ExprF::Literal { value: Literal::Int(ref bi) } => { + let fits = bi_can_fit_in(bi, hole_size, hole_sign); + match fits { + FitsIn::Yes => {} + FitsIn::No { need } => { + return Err(TypeInferenceError::TypeError { + got: need.clone(), + wanted: Some(t.clone()), + message: Some(format!( + "Integer literal {} cannot fit in {}, needs at least {:?} or larger", + bi, t, need, + )), + }); + } + } + } + _ => {} + } + } + + Ok(SpannedOptionallyTypedExpr { expr: Box::new(expr), ann: (location, Some(t.clone())) }) + }) +} + pub fn type_infer(state: State, expr: SpannedExpr) -> Result { let sote: SpannedOptionallyTypedExpr = try_contextual_cata( expr, @@ -163,42 +203,6 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { let is_arith = op.is_arithmetic(); - let propagate_concrete_type = - |e: SpannedOptionallyTypedExpr, t: NoirType| { - let NoirType::Integer(hole_sign, hole_size) = &t else { - todo!("Can only propagate integer types"); - }; - try_cata(e, &|(location, _type), expr| { - debug_assert!(_type == None); - match expr { - ExprF::Literal { value: Literal::Int(ref bi) } => { - let fits = bi_can_fit_in(bi, hole_size, hole_sign); - match fits { - FitsIn::Yes => {} - FitsIn::No { need } => { - return Err( - TypeInferenceError::TypeError { - got: need.clone(), - wanted: Some(t.clone()), - message: Some(format!( - "Integer literal {} cannot fit in {}, needs at least {:?} or larger", - bi, t, need, - )), - }, - ); - } - } - } - _ => {} - } - - Ok(SpannedOptionallyTypedExpr { - expr: Box::new(expr), - ann: (location, Some(t.clone())), - }) - }) - }; - match (&expr_left.ann.1, &expr_right.ann.1) { (None, None) => (exprf, None), (None, Some(t2)) => { @@ -276,6 +280,58 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { + let mut index = index.clone(); + + let Some(NoirType::Array(_, type_inner)) = &expr.ann.1 else { + return Err(TypeInferenceError::TypeError { + got: expr.ann.1.clone(), + wanted: None, + message: Some(format!("Can only index into arrays")), + }); + }; + match &index.ann.1 { + // Fine index + Some(NoirType::Integer(Signedness::Unsigned, _)) => {} + // Integer literal, try type inferring to `u32` + None => { + index = propagate_concrete_type( + index, + NoirType::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo), + )?; + } + // Not fine index + Some(_) => { + return Err(TypeInferenceError::TypeError { + got: index.ann.1.clone(), + wanted: Some(NoirType::Integer( + Signedness::Unsigned, + IntegerBitSize::ThirtyTwo, + )), + message: Some(format!("Can only index using unsigned integers")), + }); + } + } + + (ExprF::Index { expr: expr.clone(), index }, Some(*type_inner.clone())) + } + ExprF::TupleAccess { expr, index } => { + let Some(NoirType::Tuple(types)) = &expr.ann.1 else { + panic!(); + }; + let type_inner = + types.get(*index as usize).ok_or(TypeInferenceError::TypeError { + got: None, + wanted: None, + message: Some(format!( + "Cannot index tuple with {} elements with index {}", + types.len(), + index + )), + })?; + + (exprf.clone(), Some(type_inner.clone())) + } }; Ok(SpannedOptionallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) @@ -401,6 +457,8 @@ mod tests { ExprF::Parenthesised { expr } => expr, ExprF::UnaryOp { expr, .. } => expr, ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, + ExprF::Index { expr, index } => expr && index, + ExprF::TupleAccess { expr, .. } => expr, // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable { .. } => true, @@ -437,6 +495,8 @@ mod tests { ExprF::Parenthesised { expr } => expr, ExprF::UnaryOp { expr, .. } => expr, ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, + ExprF::Index { expr, index } => expr && index, + ExprF::TupleAccess { expr, .. } => expr, // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable { .. } => true, @@ -461,6 +521,8 @@ mod tests { ExprF::Parenthesised { expr } => expr, ExprF::UnaryOp { expr, .. } => expr, ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, + ExprF::Index { expr, index } => expr && index, + ExprF::TupleAccess { expr, .. } => expr, // Non-recursive variants don't carry information ExprF::Literal { .. } => true, @@ -470,6 +532,42 @@ mod tests { ); } + #[test] + fn test_index() { + let attribute = "ensures(xs[1 + 1] > 5)"; + let state = empty_state(); + let attribute = parse_attribute( + attribute, + Location { span: Span::inclusive(0, attribute.len() as u32), file: Default::default() }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + dbg!(&spanned_typed_expr); + assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); + } + + #[test] + fn test_tuple_access() { + let attribute = "ensures(user.0 ==> true)"; + let state = empty_state(); + let attribute = parse_attribute( + attribute, + Location { span: Span::inclusive(0, attribute.len() as u32), file: Default::default() }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + dbg!(&spanned_typed_expr); + assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); + } + #[test] fn test_monomorphization_request() { let attribute = "ensures(f(result))"; From ee70ca4bc8753b4c04ebe76a437063777ef88ad1 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 20/86] feat(annotations): assert precedence in `test_equality_precedence` - Rename from `test_equality{,_precedence}` --- compiler/formal_verification/src/ast.rs | 12 +++--- compiler/formal_verification/src/parse.rs | 50 ++++++++++++++++------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 6bc479671ea..d6e46d68d31 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; pub type Identifier = String; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ExprF { BinaryOp { op: BinaryOp, expr_left: R, expr_right: R }, UnaryOp { op: UnaryOp, expr: R }, @@ -28,7 +28,7 @@ pub enum ExprF { }, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct AnnExpr { pub ann: A, pub expr: Box>>, @@ -39,25 +39,25 @@ pub type SpannedTypedExpr = AnnExpr<(Location, NoirType)>; pub type SpannedExpr = AnnExpr; pub type OffsetExpr = AnnExpr<(u32, u32)>; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Literal { Bool(bool), Int(BigInt), } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Quantifier { Forall, Exists, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum UnaryOp { // Arithmetic and Boolean Not, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum BinaryOp { // pure Arithmetic (data -> data) Mul, diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 773d29ecaf5..529be8583c6 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -1,6 +1,13 @@ use noirc_errors::{Location, Span}; use nom::{ - branch::alt, bytes::complete::{tag, take_while, take_while1}, character::complete::{digit1 as digit, multispace0 as multispace}, combinator::{cut, fail, map, opt, recognize, value}, error::{context, ContextError, ErrorKind, ParseError}, multi::{many0, separated_list0}, sequence::{delimited, pair, preceded, terminated}, Err, IResult, Parser + Err, IResult, Parser, + branch::alt, + bytes::complete::{tag, take_while, take_while1}, + character::complete::{digit1 as digit, multispace0 as multispace}, + combinator::{cut, fail, map, opt, recognize, value}, + error::{ContextError, ErrorKind, ParseError, context}, + multi::{many0, separated_list0}, + sequence::{delimited, pair, preceded, terminated}, }; use num_bigint::{BigInt, BigUint, Sign}; use std::fmt::Debug; @@ -370,17 +377,15 @@ pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr expr: Box::new(ExprF::Index { expr: current_expr, index: index_expr }), } } - Postfix::TupleMember(index) => { - OffsetExpr { - ann: new_ann, - expr: Box::new(ExprF::TupleAccess { - expr: current_expr, - index: index.try_into().map_err(|_| { - nom::Err::Error(Error { parser_errors: vec![], contexts: vec![] }) - })?, - }), - } - } + Postfix::TupleMember(index) => OffsetExpr { + ann: new_ann, + expr: Box::new(ExprF::TupleAccess { + expr: current_expr, + index: index.try_into().map_err(|_| { + nom::Err::Error(Error { parser_errors: vec![], contexts: vec![] }) + })?, + }), + }, }) })?; @@ -587,7 +592,11 @@ pub mod tests { shared::Visibility, }; - use crate::{Attribute, State, ast::Literal, parse_attribute}; + use crate::{ + Attribute, State, + ast::{AnnExpr, Literal, cata}, + parse_attribute, + }; use super::*; @@ -715,10 +724,23 @@ pub mod tests { } #[test] - fn test_equality() { + fn test_equality_precedence() { let expr = parse("a == b | c == d").unwrap(); + let expr_expected = parse("((a == (b | c)) == d)").unwrap(); dbg!(&expr); assert_eq!(expr.0, ""); + assert_eq!(expr_expected.0, ""); + + fn strip_ann(expr: AnnExpr) -> AnnExpr<()> { + cata(expr, &|_, expr| AnnExpr { ann: (), expr: Box::new(expr) }) + } + + let expr_expected_flat: OffsetExpr = cata(expr_expected.1, &|ann, expr| match expr { + ExprF::Parenthesised { expr } => expr, + _ => OffsetExpr { ann, expr: Box::new(expr) }, + }); + + assert_eq!(strip_ann(expr.1), strip_ann(expr_expected_flat)); } #[test] From 6723ab383e1350ae9e59c5809c911648db6eb44b Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 21/86] feat(annotations)!: multiple bindings in quantifiers, `id`s in varibles - Parse whitespace before the commas in function call expressions - Add support for multiple bindings for quantifiers, like function calls - Change the `Variable` and `Quantifier` expression types to store an (optional) `id` for each variable - Add `TODO` about the `id` values after type-inferrence completes - Add `TODO` about type inferrence behind boolean operations - Use a new `RawExpr` type for debugging, which prints succinctly - Use `strip_ann` (gives a `RawExpr`) when debugging --- Cargo.lock | 1 + compiler/formal_verification/Cargo.toml | 2 +- compiler/formal_verification/src/ast.rs | 46 ++++++++------ compiler/formal_verification/src/parse.rs | 71 ++++++++++++++-------- compiler/formal_verification/src/typing.rs | 52 +++++++++++----- 5 files changed, 113 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f11ca628e95..3a48f422812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1564,6 +1564,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.102", + "unicode-xid", ] [[package]] diff --git a/compiler/formal_verification/Cargo.toml b/compiler/formal_verification/Cargo.toml index 4ebe28f7434..af792906fef 100644 --- a/compiler/formal_verification/Cargo.toml +++ b/compiler/formal_verification/Cargo.toml @@ -21,7 +21,7 @@ tracing.workspace = true strum.workspace = true strum_macros.workspace = true nom = "8.0" -derive_more = { version = "2.0.1", features = ["deref", "from", "into"] } +derive_more = { version = "2.0.1", features = ["deref", "from", "into", "debug"] } [dev-dependencies] base64.workspace = true diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index d6e46d68d31..5d5adbce8f4 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -1,3 +1,5 @@ +use std::fmt::Debug; + use noirc_errors::Location; use noirc_frontend::monomorphization::ast::Type as NoirType; use num_bigint::BigInt; @@ -10,22 +12,12 @@ pub enum ExprF { BinaryOp { op: BinaryOp, expr_left: R, expr_right: R }, UnaryOp { op: UnaryOp, expr: R }, Parenthesised { expr: R }, - Quantified { - quantifier: Quantifier, - // TODO: ids - name: Identifier, - expr: R, - }, + Quantified { quantifier: Quantifier, variables: Vec, expr: R }, FnCall { name: Identifier, args: Vec }, Index { expr: R, index: R }, TupleAccess { expr: R, index: u32 }, Literal { value: Literal }, - Variable { - name: Identifier, - // TODO: ids - // LocalId from Noir - // local_id: u32, - }, + Variable(Variable), } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -39,6 +31,14 @@ pub type SpannedTypedExpr = AnnExpr<(Location, NoirType)>; pub type SpannedExpr = AnnExpr; pub type OffsetExpr = AnnExpr<(u32, u32)>; +#[derive(Clone, derive_more::Debug, PartialEq, Eq, Serialize, Deserialize)] +#[debug("{_0:?}")] +pub struct RawExpr(pub Box>); + +pub fn strip_ann(expr: AnnExpr) -> RawExpr { + cata(expr, &|_, expr| RawExpr(Box::new(expr))) +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Literal { Bool(bool), @@ -129,6 +129,16 @@ impl BinaryOp { } } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Variable { + pub name: Identifier, + pub id: Option, +} + +/* +* cata stuff +* */ + pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { match expr { ExprF::BinaryOp { op, expr_left, expr_right } => { @@ -136,8 +146,8 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { } ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr) }, ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr) }, - ExprF::Quantified { quantifier, name, expr } => { - ExprF::Quantified { quantifier, name, expr: cata_fn(expr) } + ExprF::Quantified { quantifier, variables, expr } => { + ExprF::Quantified { quantifier, variables, expr: cata_fn(expr) } } ExprF::FnCall { name, args } => { ExprF::FnCall { name, args: args.into_iter().map(cata_fn).collect() } @@ -149,7 +159,7 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { ExprF::TupleAccess { expr: cata_fn(expr), index: member } } ExprF::Literal { value } => ExprF::Literal { value }, - ExprF::Variable { name } => ExprF::Variable { name }, + ExprF::Variable(Variable { name, id }) => ExprF::Variable(Variable { name, id }), } } @@ -160,8 +170,8 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res } ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr)? }, ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr)? }, - ExprF::Quantified { quantifier, name, expr } => { - ExprF::Quantified { quantifier, name, expr: cata_fn(expr)? } + ExprF::Quantified { quantifier, variables, expr } => { + ExprF::Quantified { quantifier, variables, expr: cata_fn(expr)? } } ExprF::FnCall { name, args } => { let processed_args = args.into_iter().map(cata_fn).collect::, _>>()?; @@ -174,7 +184,7 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res ExprF::TupleAccess { expr: cata_fn(expr)?, index: member } } ExprF::Literal { value } => ExprF::Literal { value }, - ExprF::Variable { name } => ExprF::Variable { name }, + ExprF::Variable(Variable { name, id }) => ExprF::Variable(Variable { name, id }), }) } diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 529be8583c6..bd0c265b1cc 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -12,7 +12,7 @@ use nom::{ use num_bigint::{BigInt, BigUint, Sign}; use std::fmt::Debug; -use crate::ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp}; +use crate::ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}; #[derive(Debug)] pub enum ParserError { @@ -444,12 +444,12 @@ pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE let (input, _) = multispace(input)?; // TODO: better `space` management - let (input, (name, expr)) = delimited( + let (input, (variables, expr)) = delimited( tag("("), cut(pair( delimited( delimited(multispace, tag("|"), multispace), - parse_identifier, + cut(separated_list0(delimited(multispace, tag(","), multispace), parse_identifier)), delimited(multispace, tag("|"), multispace), ), delimited(multispace, parse_expression, multispace), @@ -464,7 +464,14 @@ pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE build_expr( prev_offset, after_offset, - ExprF::Quantified { quantifier, name: name.to_string(), expr }, + ExprF::Quantified { + quantifier, + variables: variables + .into_iter() + .map(|name| Variable { name: name.to_string(), id: None }) + .collect(), + expr, + }, ), )) } @@ -475,7 +482,7 @@ pub(crate) fn parse_fn_call_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr let (input, params) = delimited( tag("("), - cut(separated_list0(pair(tag(","), multispace), parse_expression)), + cut(separated_list0(delimited(multispace, tag(","), multispace), parse_expression)), tag(")"), ) .parse(input)?; @@ -542,6 +549,7 @@ pub(crate) fn parse_sign<'a>(input: Input<'a>) -> PResult<'a, bool> { Ok((input, sign)) } +// TODO: parse module references `fv_std::SOMETHING` pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let prev_offset = input.len(); let (input, ident) = parse_identifier(input).map_err(|_| { @@ -559,10 +567,16 @@ pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { })?; let after_offset = input.len(); - Ok((input, build_expr(prev_offset, after_offset, ExprF::Variable { name: ident.to_string() }))) + Ok(( + input, + build_expr( + prev_offset, + after_offset, + ExprF::Variable(Variable { name: ident.to_string(), id: None }), + ), + )) } -// TODO: parse module references `fv_std::SOMETHING` pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { fn is_valid_start(c: char) -> bool { c.is_ascii_alphabetic() || c == '_' @@ -594,7 +608,7 @@ pub mod tests { use crate::{ Attribute, State, - ast::{AnnExpr, Literal, cata}, + ast::{AnnExpr, Literal, RawExpr, cata, strip_ann}, parse_attribute, }; @@ -679,6 +693,21 @@ pub mod tests { parse_expression(input) } + pub fn test_precedence_equality(raw: &str, parenthesised: &str) { + let expr = parse(raw).unwrap(); + let expr_expected = parse(parenthesised).unwrap(); + dbg!(&strip_ann(expr.1.clone())); + assert_eq!(expr.0, ""); + assert_eq!(expr_expected.0, ""); + + let expr_expected_flat: OffsetExpr = cata(expr_expected.1, &|ann, expr| match expr { + ExprF::Parenthesised { expr } => expr, + _ => OffsetExpr { ann, expr: Box::new(expr) }, + }); + + assert_eq!(strip_ann(expr.1), strip_ann(expr_expected_flat)); + } + #[test] fn test_bool_true() { let (input, expr) = parse("true").unwrap(); @@ -703,7 +732,7 @@ pub mod tests { let (input, expr) = parse(identche).unwrap(); assert_eq!(input, ""); // assert!(matches!(*expr.1.typ, TypX::Bool)); - let ExprF::Variable { name: i } = *expr.expr else { panic!() }; + let ExprF::Variable(Variable { name: i, .. }) = *expr.expr else { panic!() }; assert_eq!(&i, identche); } @@ -725,22 +754,16 @@ pub mod tests { #[test] fn test_equality_precedence() { - let expr = parse("a == b | c == d").unwrap(); - let expr_expected = parse("((a == (b | c)) == d)").unwrap(); - dbg!(&expr); - assert_eq!(expr.0, ""); - assert_eq!(expr_expected.0, ""); - - fn strip_ann(expr: AnnExpr) -> AnnExpr<()> { - cata(expr, &|_, expr| AnnExpr { ann: (), expr: Box::new(expr) }) - } - - let expr_expected_flat: OffsetExpr = cata(expr_expected.1, &|ann, expr| match expr { - ExprF::Parenthesised { expr } => expr, - _ => OffsetExpr { ann, expr: Box::new(expr) }, - }); + test_precedence_equality("a == b | c == d", "((a == (b | c)) == d)"); + } - assert_eq!(strip_ann(expr.1), strip_ann(expr_expected_flat)); + #[test] + fn test_and_precedence() { + test_precedence_equality( + "exists(|i| (0 <= i) & (i < 20) & arr[i] > 100)", + "exists(|i| (((0 <= i) & (i < 20)) & (arr[i] > 100)))", + // "exists(|i| ((((0 <= i) & (i < 20)) & arr[i]) > 100))", + ); } #[test] diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 2a4049e0f3d..243af7d7dc5 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -2,7 +2,7 @@ use crate::{ MonomorphizationRequest, State, ast::{ BinaryOp, ExprF, Literal, SpannedExpr, SpannedOptionallyTypedExpr, SpannedTypedExpr, - UnaryOp, cata, try_cata, try_contextual_cata, + UnaryOp, Variable, cata, strip_ann, try_cata, try_contextual_cata, }, }; use noirc_frontend::{ @@ -84,7 +84,11 @@ pub fn propagate_concrete_type( let limits = match &t { NoirType::Field => None, NoirType::Integer(hole_sign, hole_size) => Some((hole_sign, hole_size)), - _ => todo!("Can only propagate integer types, {:#?}", t), + _ => todo!( + "Can only propagate integer types, trying to ram {:#?} into {:#?}", + t, + strip_ann(e) + ), }; try_cata(e, &|(location, _type), expr| { @@ -109,6 +113,8 @@ pub fn propagate_concrete_type( } } } + // TODO: handle type inferring pure boolean operators + // `(1 < 2) & x > 5` _ => {} } } @@ -122,8 +128,9 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result { - let (variable_ident, variable_type): (&str, Option) = + ExprF::Variable(Variable { name, id }) => { + // NOTE: parsing should not yield `id`s + debug_assert_eq!(*id, None); + let (variable_ident, variable_id, variable_type): (&str, Option, Option) = quantifier_bound_variables .iter() .find_map(|bound_variable| { - (bound_variable == name).then(|| (name.as_str(), None)) + // TODO: `id` not `None` (when we have a way to generate new `id`s) + (bound_variable == name).then(|| (name.as_str(), None, None)) }) .or_else(|| { - state.function.parameters.iter().find_map(|k| { - (k.2 == *name).then(|| (name.as_str(), Some(k.3.clone()))) + state.function.parameters.iter().find_map(|(id, _, par_name, t, _)| { + (par_name == name).then(|| (name.as_str(), Some(id.0), Some(t.clone()))) }) }) .or_else(|| { (name == "result").then(|| { - (FUNC_RETURN_VAR_NAME, Some(state.function.return_type.clone())) + (FUNC_RETURN_VAR_NAME, None, Some(state.function.return_type.clone())) }) }) .ok_or(TypeInferenceError::TypeError { @@ -161,7 +171,10 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { let return_type = state @@ -345,6 +358,8 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result expr, // Non-recursive variants don't carry information - ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable { .. } => true, + ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, } }), "All integer literals have the correct inferred type" @@ -470,7 +490,7 @@ mod tests { #[test] fn test_quantifiers() { - let attribute = "ensures(exists(|i| result >= a + (16 / i % (7 * 4))))"; + let attribute = "ensures(exists(| i , j | result >= a + (16 / i % (7 * 4))))"; let state = empty_state(); let attribute = parse_attribute( attribute, @@ -499,7 +519,7 @@ mod tests { ExprF::TupleAccess { expr, .. } => expr, // Non-recursive variants don't carry information - ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable { .. } => true, + ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, } }), "All integer literals have the correct inferred type" @@ -507,7 +527,7 @@ mod tests { assert!( cata(spanned_typed_expr, &|(_, expr_type), expr| { match expr { - ExprF::Variable { name } => { + ExprF::Variable(Variable { name, .. }) => { if name == "i" { expr_type == NoirType::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) From 805f67311288bd98501031dff3caf80ee0fbf6bd Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 22/86] fix(annotations): correct bit-shifting parsing - Was erroneously reusing the comparison operators parser (copy-paste mishap) --- compiler/formal_verification/src/parse.rs | 25 ++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index bd0c265b1cc..33fb470cc9c 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -258,17 +258,15 @@ pub(crate) fn parse_shift_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> if let Ok((input, (op, expr_right))) = pair( context( "comparison", - delimited(multispace, alt((tag("<="), tag(">="), tag("<"), tag(">"))), multispace), + delimited(multispace, alt((tag("<<"), tag(">>"))), multispace), ), parse_additive_expr, ) .parse(input) { let op_kind = match op { - "<" => BinaryOp::Lt, - "<=" => BinaryOp::Le, - ">" => BinaryOp::Gt, - ">=" => BinaryOp::Ge, + "<<" => BinaryOp::ShiftLeft, + ">>" => BinaryOp::ShiftRight, _ => unreachable!(), }; expr_left = OffsetExpr { @@ -820,4 +818,21 @@ pub mod tests { assert!(matches!(attribute, Attribute::Ghost)); } + + #[test] + fn test_bitshift() { + let annotation = "ensures(result == x >> y)"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + } } From 89ed5f411d2fc6d664f4c1b57968d425b30c474c Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 23/86] fix(annotations): correctly propagate type through predicates - See test for use-case --- compiler/formal_verification/src/typing.rs | 103 ++++++++++++++++----- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 243af7d7dc5..ac30185448f 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -130,7 +130,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result { // NOTE: parsing should not yield `id`s debug_assert_eq!(*id, None); - let (variable_ident, variable_id, variable_type): (&str, Option, Option) = - quantifier_bound_variables - .iter() - .find_map(|bound_variable| { - // TODO: `id` not `None` (when we have a way to generate new `id`s) - (bound_variable == name).then(|| (name.as_str(), None, None)) - }) - .or_else(|| { - state.function.parameters.iter().find_map(|(id, _, par_name, t, _)| { - (par_name == name).then(|| (name.as_str(), Some(id.0), Some(t.clone()))) - }) + let (variable_ident, variable_id, variable_type): ( + &str, + Option, + Option, + ) = quantifier_bound_variables + .iter() + .find_map(|bound_variable| { + // TODO: `id` not `None` (when we have a way to generate new `id`s) + (bound_variable == name).then(|| (name.as_str(), None, None)) + }) + .or_else(|| { + state.function.parameters.iter().find_map(|(id, _, par_name, t, _)| { + (par_name == name) + .then(|| (name.as_str(), Some(id.0), Some(t.clone()))) }) - .or_else(|| { - (name == "result").then(|| { - (FUNC_RETURN_VAR_NAME, None, Some(state.function.return_type.clone())) - }) + }) + .or_else(|| { + (name == "result").then(|| { + ( + FUNC_RETURN_VAR_NAME, + None, + Some(state.function.return_type.clone()), + ) }) - .ok_or(TypeInferenceError::TypeError { - got: None, - wanted: None, - message: Some(format!("Undefined variable {}", name)), - })?; + }) + .ok_or(TypeInferenceError::TypeError { + got: None, + wanted: None, + message: Some(format!("Undefined variable {}", name)), + })?; ( - ExprF::Variable(Variable { name: variable_ident.to_string(), id: variable_id }), + ExprF::Variable(Variable { + name: variable_ident.to_string(), + id: variable_id, + }), variable_type, ) } @@ -217,7 +228,35 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result (exprf, None), + (None, None) => { + if is_arith { + (exprf, None) + } else { + // NOTE: predicate, always bool, + // assume subterms are `u32` (like `Noir` does) + let default_literal_type = NoirType::Integer( + Signedness::Unsigned, + IntegerBitSize::ThirtyTwo, + ); + let expr_left_inner = propagate_concrete_type( + expr_left.clone(), + default_literal_type.clone(), + )?; + let expr_right_inner = propagate_concrete_type( + expr_right.clone(), + default_literal_type.clone(), + )?; + + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: expr_left_inner, + expr_right: expr_right_inner, + }, + Some(NoirType::Bool), + ) + } + } (None, Some(t2)) => { let expr_left_inner = propagate_concrete_type(expr_left.clone(), t2.clone())?; @@ -588,6 +627,24 @@ mod tests { assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } + #[test] + fn test_tuple_access_combos() { + let attribute = "ensures(exists(|i| (0 <= i) & (i < 20) & xs[i] > 100))"; + let state = empty_state(); + let attribute = parse_attribute( + attribute, + Location { span: Span::inclusive(0, attribute.len() as u32), file: Default::default() }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + dbg!(&spanned_typed_expr); + assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); + } + #[test] fn test_monomorphization_request() { let attribute = "ensures(f(result))"; From c3fce65b72a84fc55f0dd40438931795f4a37d70 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 24/86] fix(annotations): fix parsing of spaces around parenthesis --- compiler/formal_verification/src/parse.rs | 24 ++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 33fb470cc9c..f72c791dcb0 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -256,10 +256,7 @@ pub(crate) fn parse_shift_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> // Comparison operators are not associative (e.g., `a < b < c` is invalid), // so we just look for one optional occurrence. if let Ok((input, (op, expr_right))) = pair( - context( - "comparison", - delimited(multispace, alt((tag("<<"), tag(">>"))), multispace), - ), + context("comparison", delimited(multispace, alt((tag("<<"), tag(">>"))), multispace)), parse_additive_expr, ) .parse(input) @@ -427,7 +424,16 @@ pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { } pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - delimited(multispace, delimited(tag("("), parse_expression, tag(")")), multispace).parse(input) + delimited( + multispace, + delimited( + delimited(multispace, tag("("), multispace), + parse_expression, + delimited(multispace, tag(")"), multispace), + ), + multispace, + ) + .parse(input) } pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { @@ -772,6 +778,14 @@ pub mod tests { assert_eq!(expr.0, ""); } + #[test] + fn test_parenthesis() { + let s = "( (( 1 + 2 ) ) * 3 )"; + let expr = parse(s).unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + #[test] fn test_quantifier() { let expr = parse("exists(|x| 1 + x)").unwrap(); From d6c979aff033ad15b64ccf18a6e437cb9301ac31 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Tue, 29 Jul 2025 13:53:26 +0300 Subject: [PATCH 25/86] feat(fv-bridge): Typed attributes to VIR Implemented the module which converts typed attribute expressions to VIR. In `fv_bridge` we are now using the new functions from `formal_verification`. Therefore we have a completed pipeline of parsing, type inferring and conversion to VIR. Almost all tests which currently don't pass are tests which use Noir structures which we don't support yet. --- Cargo.lock | 3 + compiler/formal_verification/src/ast.rs | 11 +- compiler/fv_bridge/Cargo.toml | 3 + compiler/fv_bridge/src/lib.rs | 21 +- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 338 ++++++++++++++++++ .../src/vir/vir_gen/expr_to_vir/expr.rs | 4 +- .../src/vir/vir_gen/expr_to_vir/types.rs | 2 +- 7 files changed, 366 insertions(+), 16 deletions(-) create mode 100644 compiler/fv_bridge/src/typed_attrs_to_vir.rs diff --git a/Cargo.lock b/Cargo.lock index 3a48f422812..5c7ba98edd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2125,6 +2125,7 @@ dependencies = [ name = "fv_bridge" version = "1.0.0-beta.7" dependencies = [ + "acvm", "fm", "formal_verification", "iter-extended", @@ -2132,6 +2133,8 @@ dependencies = [ "noirc_errors", "noirc_evaluator", "noirc_frontend", + "num-bigint 0.4.6", + "num-traits", "serde", "vir", ] diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 5d5adbce8f4..9010548ba5a 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use noirc_errors::Location; use noirc_frontend::monomorphization::ast::Type as NoirType; @@ -242,3 +242,12 @@ pub fn try_cata_recoverable( algebra(expr.ann, children_results) } + +impl Display for Quantifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", match self { + Quantifier::Forall => "forall", + Quantifier::Exists => "exists", + } ) + } +} diff --git a/compiler/fv_bridge/Cargo.toml b/compiler/fv_bridge/Cargo.toml index 2a4d00b8be7..51d4b87012c 100644 --- a/compiler/fv_bridge/Cargo.toml +++ b/compiler/fv_bridge/Cargo.toml @@ -20,6 +20,9 @@ noirc_driver.workspace = true fm.workspace = true iter-extended.workspace = true formal_verification.workspace = true +acvm.workspace = true +num-bigint.workspace = true +num-traits.workspace = true [dev-dependencies] iter-extended.workspace = true diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index e4ed4cf73bb..74226f65ebb 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -6,7 +6,7 @@ use formal_verification::typing::type_infer; use formal_verification::{State, parse_attribute}; use iter_extended::vecmap; use noirc_driver::{CompilationResult, CompileError, CompileOptions, check_crate}; -use noirc_errors::{CustomDiagnostic, Location}; +use noirc_errors::CustomDiagnostic; use noirc_evaluator::vir::vir_gen::Attribute; use noirc_evaluator::{ errors::{RuntimeError, SsaReport}, @@ -30,6 +30,10 @@ use noirc_frontend::{ }; use vir::ast::Krate; +use crate::typed_attrs_to_vir::convert_typed_attribute_to_vir_attribute; + +pub mod typed_attrs_to_vir; + pub fn compile_and_build_vir_krate( context: &mut Context, crate_id: CrateId, @@ -223,7 +227,7 @@ fn modified_monomorphize( &globals, &monomorphizer.finished_functions, ) - .map_err(|_| panic!() as MonomorphOrResolverError)?; + .map_err(|x| panic!("{:#?}", x) as MonomorphOrResolverError)?; // Step 2: Type-infer the parsed attribute expression. let typed_attribute = match parsed_attribute { @@ -231,19 +235,19 @@ fn modified_monomorphize( formal_verification::Attribute::Ensures(expr) => { // TODO(totel): Handle MonomorphRequest error type let typed_expr = type_infer(state.clone(), expr) - .map_err(|_| panic!() as MonomorphOrResolverError)?; + .map_err(|x| panic!("{:#?}", x) as MonomorphOrResolverError)?; TypedAttribute::Ensures(typed_expr) } formal_verification::Attribute::Requires(expr) => { // TODO(totel): Handle MonomorphRequest error type let typed_expr = type_infer(state.clone(), expr) - .map_err(|_| panic!() as MonomorphOrResolverError)?; + .map_err(|x| panic!("{:#?}", x) as MonomorphOrResolverError)?; TypedAttribute::Requires(typed_expr) } }; // Step 3: Convert the typed attribute into its final representation. - Ok(convert_typed_attribute_to_vir_attribute(typed_attribute, state.clone())) + Ok(convert_typed_attribute_to_vir_attribute(typed_attribute, &state)) }) .collect::, _>>()?; @@ -275,10 +279,3 @@ pub struct KrateAndWarnings { pub warnings: Vec, pub parse_annotations_errors: Vec, } - -fn convert_typed_attribute_to_vir_attribute( - typed_attribute: TypedAttribute, - state: State, -) -> Attribute { - todo!() -} diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs new file mode 100644 index 00000000000..a14297bab7e --- /dev/null +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -0,0 +1,338 @@ +use std::sync::Arc; + +use formal_verification::{ + State, + ast::{BinaryOp, ExprF, Quantifier, SpannedTypedExpr, Variable, cata}, +}; +use noirc_evaluator::vir::vir_gen::{ + Attribute, build_span, build_span_no_id, + expr_to_vir::{ + expr::{function_name_to_vir_fun, numeric_const_to_vir_exprx, wrap_with_field_modulo}, + types::{ + ast_const_to_vir_type_const, ast_type_to_vir_type, get_bit_not_bitwidth, is_type_field, + }, + }, +}; +use noirc_frontend::monomorphization::FUNC_RETURN_VAR_NAME; +use vir::ast::{ + AirQuant, BinaryOp as VirBinaryOp, BitwiseOp, Dt, FieldOpr, FunX, ImplPath, InequalityOp, + IntRange, PathX, Primitive, Quant, Typ, UnaryOpr, VarBinder, VarBinderX, VarIdent, + VarIdentDisambiguate, VariantCheck, +}; +use vir::{ + ast::{ + ArithOp, AutospecUsage, CallTarget, CallTargetKind, Constant, Expr, ExprX, Mode, + SpannedTyped, TypX, + }, + ast_util::{bitwidth_from_type, is_integer_type_signed}, +}; + +use crate::TypedAttribute; + +use acvm::{AcirField, FieldElement}; +use noirc_frontend::signed_field::SignedField; +use num_bigint::{BigInt, ToBigInt}; // Replace with the actual path to SignedField + +pub fn signed_field_from_bigint_wrapping(value: BigInt) -> SignedField { + let modulus = FieldElement::modulus().to_bigint().unwrap(); + + // Wrap value to the positive modulus range + let wrapped = ((&value % &modulus) + &modulus) % &modulus; + + // Convert the reduced BigInt into FieldElement + let wrapped_biguint = wrapped.to_biguint().unwrap(); + let bytes = wrapped_biguint.to_bytes_be(); + let field = FieldElement::from_be_bytes_reduce(&bytes); + // Fields are always positive + let is_negative = false; + SignedField::new(field, is_negative) +} + +pub(crate) fn convert_typed_attribute_to_vir_attribute( + typed_attribute: TypedAttribute, + state: &State, +) -> Attribute { + match typed_attribute { + TypedAttribute::Ghost => Attribute::Ghost, + TypedAttribute::Requires(ann_expr) => { + Attribute::Requires(ann_expr_to_vir_expr(ann_expr, state)) + } + TypedAttribute::Ensures(ann_expr) => { + Attribute::Ensures(ann_expr_to_vir_expr(ann_expr, state)) + } + } +} + +pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> Expr { + let mode = Mode::Spec; + cata(ann_expr, &|(loc, typ), expr| { + // Helper to construct a standard SpannedTyped expression. + // It captures `loc` from the outer scope. + let make_expr = |exprx: ExprX, vir_type: Typ, span_msg: String| -> Expr { + SpannedTyped::new(&build_span_no_id(span_msg, Some(loc)), &vir_type, exprx) + }; + match expr { + ExprF::Literal { value } => { + let (exprx, span_msg) = match value { + formal_verification::ast::Literal::Bool(val) => { + (ExprX::Const(Constant::Bool(val)), format!("Const boolean {}", val)) + } + formal_verification::ast::Literal::Int(big_int) => ( + numeric_const_to_vir_exprx( + &signed_field_from_bigint_wrapping(big_int.clone()), + &typ, + ), + format!("Int literal {}", big_int), + ), + }; + make_expr(exprx, ast_type_to_vir_type(&typ), span_msg) + } + ExprF::Variable(Variable { name, id }) => { + let is_global = state.global_constants.iter().any(|(_, (gn, _, _))| *gn == name); + let span_msg = format!("Var {}", name); + + // Handle the simpler global case first and return early. + if is_global { + let exprx = ExprX::ConstVar( + Arc::new(FunX { + path: Arc::new(PathX { + krate: None, + segments: Arc::new(vec![Arc::new(name)]), + }), + }), + AutospecUsage::Final, + ); + + // Globals don't have a specific noir DefId, so use build_span_no_id. + return SpannedTyped::new( + &build_span_no_id(span_msg, Some(loc)), + &ast_type_to_vir_type(&typ), + exprx, + ); + } + + // --- Logic below is now only for non-global (local) variables --- + + let disambiguate = if name == FUNC_RETURN_VAR_NAME { + VarIdentDisambiguate::AirLocal + } else { + let rustc_id = id + .expect("Non-return variable must have an ID") + .try_into() + .expect("Failed to convert ast id to usize"); + VarIdentDisambiguate::RustcId(rustc_id) + }; + + let exprx = ExprX::Var(VarIdent(Arc::new(name), disambiguate)); + + // Idiomatically create the span based on whether an ID exists. + let span = if let Some(def_id) = id { + build_span(def_id, span_msg, Some(loc)) + } else { + build_span_no_id(span_msg, Some(loc)) + }; + + SpannedTyped::new(&span, &ast_type_to_vir_type(&typ), exprx) + } + ExprF::FnCall { name, args } => { + // TODO(totel): Special handling for `old` from the Noir `fv_std` + let exprx = ExprX::Call( + CallTarget::Fun( + CallTargetKind::Static, + function_name_to_vir_fun(name.clone()), + Arc::new(vec![]), + Arc::new(vec![]), + AutospecUsage::Final, + ), + Arc::new(args), + ); + make_expr(exprx, ast_type_to_vir_type(&typ), format!("Function call {}", name)) + } + ExprF::Quantified { quantifier, variables, expr } => { + let quantifier_type = match quantifier { + Quantifier::Forall => Quant { quant: AirQuant::Forall }, + Quantifier::Exists => Quant { quant: AirQuant::Exists }, + }; + let make_binder = |ident: &Variable| -> VarBinder { + let rustc_id = ident + .id + .expect("Quantifier index should have a local id") + .try_into() + .expect("Failed to convert u32 id to usize"); + Arc::new(VarBinderX { + name: VarIdent( + Arc::new(ident.name.clone()), + VarIdentDisambiguate::RustcId(rustc_id), + ), + a: Arc::new(TypX::Int(IntRange::Int)), + }) + }; + let quantifier_vir_indices = Arc::new(variables.iter().map(make_binder).collect()); + + assert!( + matches!(expr.typ.as_ref(), TypX::Bool), + "All quantifier bodies must be of type bool" + ); + + let exprx = ExprX::Quant(quantifier_type, quantifier_vir_indices, expr); + make_expr(exprx, Arc::new(TypX::Bool), format!("Quantifier {}", quantifier)) + } + ExprF::Parenthesised { expr } => expr, + ExprF::UnaryOp { op, expr } => { + let (exprx, span_msg) = match op { + formal_verification::ast::UnaryOp::Not => { + if matches!(typ, noirc_frontend::monomorphization::ast::Type::Bool) { + ( + ExprX::Unary(vir::ast::UnaryOp::Not, expr), + "Logical Not op".to_string(), + ) + } else { + ( + ExprX::Unary( + vir::ast::UnaryOp::BitNot(get_bit_not_bitwidth(&typ)), + expr, + ), + "BitNot expr".to_string(), + ) + } + } + }; + make_expr(exprx, ast_type_to_vir_type(&typ), span_msg) + } + ExprF::BinaryOp { op, expr_left, expr_right } => { + let lhs_type = expr_left.typ.clone(); + let is_lhs_bool = matches!(lhs_type.as_ref(), TypX::Bool); + let bool_or_bitwise = |bool_op, bitwise_op| { + if is_lhs_bool { bool_op } else { VirBinaryOp::Bitwise(bitwise_op, mode) } + }; + + let vir_binary_op = match op { + BinaryOp::Mul => VirBinaryOp::Arith(ArithOp::Mul, mode), + BinaryOp::Div => VirBinaryOp::Arith(ArithOp::EuclideanDiv, mode), + BinaryOp::Mod => VirBinaryOp::Arith(ArithOp::EuclideanMod, mode), + BinaryOp::Add => VirBinaryOp::Arith(ArithOp::Add, mode), + BinaryOp::Sub => VirBinaryOp::Arith(ArithOp::Sub, mode), + BinaryOp::ShiftLeft => VirBinaryOp::Bitwise( + BitwiseOp::Shl( + bitwidth_from_type(&lhs_type) + .expect("Bitwise operation with non int type"), + is_integer_type_signed(&lhs_type), + ), + mode, + ), + BinaryOp::ShiftRight => VirBinaryOp::Bitwise( + BitwiseOp::Shr( + bitwidth_from_type(&lhs_type) + .expect("Bitwise operation with non int type"), + ), + mode, + ), + BinaryOp::Eq => VirBinaryOp::Eq(mode), + BinaryOp::Neq => VirBinaryOp::Ne, + BinaryOp::Lt => VirBinaryOp::Inequality(InequalityOp::Lt), + BinaryOp::Le => VirBinaryOp::Inequality(InequalityOp::Le), + BinaryOp::Gt => VirBinaryOp::Inequality(InequalityOp::Gt), + BinaryOp::Ge => VirBinaryOp::Inequality(InequalityOp::Ge), + BinaryOp::Implies => VirBinaryOp::Implies, + BinaryOp::And => bool_or_bitwise(VirBinaryOp::And, BitwiseOp::BitAnd), + BinaryOp::Or => bool_or_bitwise(VirBinaryOp::Or, BitwiseOp::BitOr), + BinaryOp::Xor => bool_or_bitwise(VirBinaryOp::Xor, BitwiseOp::BitXor), + }; + + let binary_op_type = get_binary_op_type(expr_left.typ.clone(), &op); + let exprx = ExprX::Binary(vir_binary_op, expr_left, expr_right); + + let vir_binary_expr = + make_expr(exprx, binary_op_type, "Binary op expression".to_string()); + + if is_type_field(&typ) && op.is_arithmetic() { + // Wrap expression with `% Field::modulus()` if the expression type is Field + wrap_with_field_modulo(vir_binary_expr, mode) + } else { + vir_binary_expr + } + } + ExprF::TupleAccess { expr, index } => { + let TypX::Datatype(Dt::Tuple(tuple_len), _, _) = expr.typ.as_ref() else { + unreachable!("Unexpected type for tuple access: {:#?}", expr); + }; + + let exprx = ExprX::UnaryOpr( + UnaryOpr::Field(FieldOpr { + datatype: Dt::Tuple(*tuple_len), + variant: Arc::new(format!("tuple%{}", tuple_len)), + field: Arc::new(index.to_string()), + get_variant: false, + check: VariantCheck::None, + }), + expr, + ); + + make_expr(exprx, ast_type_to_vir_type(&typ), format!("Tuple access at {}", index)) + } + ExprF::Index { expr, index } => { + let element_type = ast_type_to_vir_type(&typ); + + // Ensure we're indexing into a proper array with a const length + let array_type = match expr.typ.as_ref() { + TypX::Primitive(Primitive::Array, inner) => inner, + _ => unreachable!("Arrays must be of type Primitive"), + }; + + let array_len = match array_type[1].as_ref() { + TypX::ConstInt(len) => len, + _ => unreachable!("Arrays must have a constant length"), + }; + + let array_len_type = ast_const_to_vir_type_const( + array_len.try_into().expect("Failed to convert u32 to usize"), + ); + + // Build types for the vstd::array::spec_index function + let vstd_krate = Some(Arc::new("vstd".to_string())); + let element_and_len = Arc::new(vec![element_type.clone(), array_len_type.clone()]); + let array_typ = + Arc::new(TypX::Primitive(Primitive::Array, element_and_len.clone())); + let typs_for_call = Arc::new(vec![array_typ.clone(), element_type.clone()]); + + // Define the call target and trait impl paths + let make_path = |segments: Vec<&str>| { + Arc::new(PathX { + krate: vstd_krate.clone(), + segments: Arc::new( + segments.into_iter().map(|s| Arc::new(s.to_string())).collect(), + ), + }) + }; + + let call_target = CallTarget::Fun( + CallTargetKind::DynamicResolved { + resolved: Arc::new(FunX { + path: make_path(vec!["array", "impl&%2", "spec_index"]), + }), + typs: array_type.clone(), + impl_paths: Arc::new(vec![]), + is_trait_default: false, + }, + Arc::new(FunX { + path: make_path(vec!["array", "ArrayAdditionalSpecFns", "spec_index"]), + }), + typs_for_call.clone(), + Arc::new(vec![ + ImplPath::TraitImplPath(make_path(vec!["array", "impl&%0"])), + ImplPath::TraitImplPath(make_path(vec!["array", "impl&%2"])), + ]), + AutospecUsage::IfMarked, + ); + + let vir_exprx = ExprX::Call(call_target, Arc::new(vec![expr.clone(), index])); + + make_expr(vir_exprx, element_type, "Array indexing expression".to_string()) + } + } + }) +} + +fn get_binary_op_type(lhs_type: Typ, binary_op: &BinaryOp) -> Typ { + if !binary_op.is_arithmetic() { Arc::new(TypX::Bool) } else { lhs_type } +} diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/expr.rs b/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/expr.rs index 88174086603..e02b1806bb3 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/expr.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/expr.rs @@ -229,7 +229,7 @@ fn ast_literal_to_vir_expr( expr } -fn numeric_const_to_vir_exprx(signed_field: &SignedField, ast_type: &Type) -> ExprX { +pub fn numeric_const_to_vir_exprx(signed_field: &SignedField, ast_type: &Type) -> ExprX { // If we have a negative Field const we want to wrap it around the finite field modulus let (const_big_uint, big_int_sign): (BigUint, _) = { match ast_type { @@ -1121,7 +1121,7 @@ pub fn ast_definition_to_id(definition: &Definition) -> Option { /// For the Noir Field type we have to wrap all arithmetic instructions /// with a Euclidean modulo `p` operation where `p` is the modulus of /// the Noir Field. -fn wrap_with_field_modulo(dividend: Expr, mode: Mode) -> Expr { +pub fn wrap_with_field_modulo(dividend: Expr, mode: Mode) -> Expr { let expr_span = dividend.span.clone(); let expr_type = dividend.typ.clone(); diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/types.rs b/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/types.rs index 11ba9930ec7..13794f1f6ef 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/types.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/types.rs @@ -71,7 +71,7 @@ fn get_integer_type_bitwidth(integer_type: &Type) -> IntegerTypeBitwidth { /// Similar to `get_integer_type_bitwidth` but we have a different logic /// for the types Field and Signed integer. /// Will `panic` if the type is not an integer. -pub(crate) fn get_bit_not_bitwidth(integer_type: &Type) -> Option { +pub fn get_bit_not_bitwidth(integer_type: &Type) -> Option { match integer_type { Type::Field | Type::Integer(Signedness::Signed, _) => None, Type::Integer(Signedness::Unsigned, _) => Some(get_integer_type_bitwidth(integer_type)), From 34d1dbdb7e0460491a1ab965ecae1142e53d4e32 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 26/86] feat(annotations): More informative errors when parsing - `opt`/`cut` early where adequate in the parsers - Also make type inferrence more restrictive - bit-shifting now only takes `u8` on the RHS and any unsigned integer on the LHS - Also add tests for failing parses of invalid annotations --- compiler/formal_verification/src/lib.rs | 44 +- compiler/formal_verification/src/parse.rs | 509 +++++++++++++----- .../formal_verification/src/parse/errors.rs | 140 +++++ compiler/formal_verification/src/typing.rs | 73 ++- compiler/fv_bridge/src/lib.rs | 2 +- 5 files changed, 561 insertions(+), 207 deletions(-) create mode 100644 compiler/formal_verification/src/parse/errors.rs diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index aada4a149ce..dd33252b76f 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -6,7 +6,7 @@ use vir::messages::Span as VirSpan; use crate::{ ast::{OffsetExpr, SpannedExpr, cata}, - parse::{Error as ParseError, build_location, parse_expression, parse_identifier}, + parse::{errors::Error as ParseError, build_location, parse_expression, parse_identifier}, }; // NOTE: all types inside are not prefixed, to be used as `ast::OffsetExpr` @@ -41,45 +41,3 @@ fn span_expr(annotation_location: Location, full_length: u32, expr: OffsetExpr) expr: Box::new(exprf), }) } - -pub fn parse_attribute<'a>( - annotation: &'a str, - mut location: Location, - function: &'a mast::Function, - global_constants: &'a BTreeMap, - functions: &'a BTreeMap, -) -> Result { - // NOTE: #['...] - // ^^^^^^^ - received `Location` - // ^^^ - relevant stuff - // TODO: don't do this here - location = Location { - span: Span::inclusive(location.span.start() + 3, location.span.end() - 1), - ..location - }; - - let input = annotation; - let (input, attribute_type) = parse_identifier(input).finish()?; //.map_err(|e| e.parser_errors)?; - - let parse_expr = || -> Result { - let (rest, expr) = parse_expression(input).finish()?; //.map_err(|e| e.parser_errors)?; - assert_eq!(rest, ""); - Ok(span_expr(location, annotation.len() as u32, expr)) - }; - - Ok(match attribute_type { - "ghost" => Attribute::Ghost, - "ensures" => Attribute::Ensures(parse_expr()?), - "requires" => Attribute::Requires(parse_expr()?), - _ => { - // return Err(vec![ResolverError::VariableNotDeclared { - // name: attribute_type.to_string(), - // location, - // }]); - return Err(ParseError { - parser_errors: vec![], - contexts: vec![], - }); - } - }) -} diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index f72c791dcb0..c474569334f 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -1,57 +1,30 @@ use noirc_errors::{Location, Span}; +use noirc_frontend::monomorphization::ast as mast; use nom::{ - Err, IResult, Parser, + Err as NomErr, IResult, Parser, branch::alt, bytes::complete::{tag, take_while, take_while1}, character::complete::{digit1 as digit, multispace0 as multispace}, - combinator::{cut, fail, map, opt, recognize, value}, - error::{ContextError, ErrorKind, ParseError, context}, - multi::{many0, separated_list0}, + combinator::{cut, map, opt, recognize, value}, + error::context, + multi::{many0, separated_list0, separated_list1}, sequence::{delimited, pair, preceded, terminated}, }; use num_bigint::{BigInt, BigUint, Sign}; -use std::fmt::Debug; +use std::{collections::BTreeMap, fmt::Debug}; -use crate::ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}; +pub mod errors; +use errors::{Error, ParserErrorKind, build_error, expect, map_nom_err}; -#[derive(Debug)] -pub enum ParserError { - // TODO: real errors - Oops, -} - -#[derive(Debug)] -pub struct Error { - pub parser_errors: Vec, - pub contexts: Vec, -} +use crate::{ + Attribute, + ast::{BinaryOp, ExprF, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, + span_expr, +}; pub type Input<'a> = &'a str; pub type PResult<'a, T> = IResult, T, Error>; -impl<'a> ParseError> for Error { - fn from_error_kind(input: Input<'a>, kind: ErrorKind) -> Self { - Self { - parser_errors: vec![ParserError::Oops], - contexts: vec![], - // location: build_location(input.len(), input.len()), - } - } - - fn append(input: Input<'a>, kind: ErrorKind, mut other: Self) -> Self { - // TODO: smart stuff - other.parser_errors.push(ParserError::Oops); - other - } -} - -impl<'a> ContextError> for Error { - fn add_context(input: Input<'a>, ctx: &'static str, mut other: Self) -> Self { - other.contexts.push(ctx.to_string()); - other - } -} - // https://github.com/rust-bakery/nom/blob/main/doc/error_management.md pub(crate) fn build_location( @@ -85,6 +58,89 @@ pub(crate) fn build_offset_from_exprs(left: &OffsetExpr, right: &OffsetExpr) -> * Main parser combinators, in order of precedence, like in upstream Noir * **************************************************************************/ +pub fn parse_attribute<'a>( + annotation: &'a str, + mut location: Location, + _function: &'a mast::Function, + _global_constants: &'a BTreeMap, + _functions: &'a BTreeMap, +) -> Result { + // NOTE: #['...] + // ^^^^^^^ - received `Location` + // ^^^ - relevant stuff + // TODO: don't do this here + location = Location { + span: Span::inclusive(location.span.start() + 3, location.span.end() - 1), + ..location + }; + + let (i, ident) = match expect("attribute name", parse_identifier)(annotation) { + Ok(result) => result, + Err(nom_err) => { + return match nom_err { + NomErr::Error(e) | NomErr::Failure(e) => Err(e), + NomErr::Incomplete(_) => Err(build_error( + annotation, + ParserErrorKind::Message("Incomplete input".to_string()), + )), + }; + } + }; + + match ident { + "ghost" => { + if !i.is_empty() { + return Err(build_error( + i, + ParserErrorKind::Message(format!( + "Unexpected input after 'ghost' attribute: '{}'", + i + )), + )); + } + Ok(Attribute::Ghost) + } + "ensures" | "requires" => { + let mut expr_parser = delimited( + preceded(multispace, expect("'('", tag("("))), + delimited(multispace, parse_expression, multispace), + cut(expect("')'", tag(")"))), + ); + + match expr_parser.parse(i) { + Ok((rest, expr)) => { + if !rest.is_empty() { + return Err(build_error( + rest, + ParserErrorKind::Message(format!( + "Unexpected trailing input: '{}'", + rest + )), + )); + } + let spanned_expr = span_expr(location, annotation.len() as u32, expr); + if ident == "ensures" { + Ok(Attribute::Ensures(spanned_expr)) + } else { + Ok(Attribute::Requires(spanned_expr)) + } + } + Err(nom_err) => match nom_err { + NomErr::Error(e) | NomErr::Failure(e) => Err(e), + NomErr::Incomplete(_) => Err(build_error( + i, + ParserErrorKind::Message("Incomplete input".to_string()), + )), + }, + } + } + unknown => { + let err_kind = ParserErrorKind::UnknownAttribute(unknown.to_string()); + Err(build_error(annotation, err_kind)) + } + } +} + // TODO: array indexing - ast_index_to_vir_expr // TODO: tuple indexing - ast_tuple_access_to_vir_expr @@ -101,7 +157,10 @@ pub(crate) fn parse_implication_expr<'a>(input: Input<'a>) -> PResult<'a, Offset parse_equality_expr, context( "implication", - many0(pair(delimited(multispace, tag("==>"), multispace), parse_equality_expr)), + many0(pair( + delimited(multispace, expect("'==>'", tag("==>")), multispace), + parse_equality_expr, + )), ), ) .parse(input)?; @@ -127,7 +186,11 @@ pub(crate) fn parse_equality_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp context( "equality", many0(pair( - delimited(multispace, alt((tag("=="), tag("!="))), multispace), + delimited( + multispace, + expect("'==' or '!='".to_string(), alt((tag("=="), tag("!=")))), + multispace, + ), parse_or_expr, )), ), @@ -153,7 +216,13 @@ pub(crate) fn parse_equality_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp pub(crate) fn parse_or_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (input, (first, remainder)) = pair( parse_and_expr, - context("or", many0(pair(delimited(multispace, tag("|"), multispace), parse_and_expr))), + context( + "or", + many0(pair( + delimited(multispace, expect("'|'".to_string(), tag("|")), multispace), + parse_and_expr, + )), + ), ) .parse(input)?; @@ -175,7 +244,13 @@ pub(crate) fn parse_or_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { pub(crate) fn parse_and_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (input, (first, remainder)) = pair( parse_xor_expr, - context("and", many0(pair(delimited(multispace, tag("&"), multispace), parse_xor_expr))), + context( + "and", + many0(pair( + delimited(multispace, expect("'&'".to_string(), tag("&")), multispace), + parse_xor_expr, + )), + ), ) .parse(input)?; @@ -199,7 +274,10 @@ pub(crate) fn parse_xor_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { parse_comparison_expr, context( "xor", - many0(pair(delimited(multispace, tag("^"), multispace), parse_comparison_expr)), + many0(pair( + delimited(multispace, expect("'^'".to_string(), tag("^")), multispace), + parse_comparison_expr, + )), ), ) .parse(input)?; @@ -219,18 +297,23 @@ pub(crate) fn parse_xor_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { )) } pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, mut expr_left) = parse_shift_expr(input)?; + let (i, mut expr_left) = parse_shift_expr(input)?; // Comparison operators are not associative (e.g., `a < b < c` is invalid), // so we just look for one optional occurrence. - if let Ok((input, (op, expr_right))) = pair( - context( - "comparison", - delimited(multispace, alt((tag("<="), tag(">="), tag("<"), tag(">"))), multispace), + if let (i, Some((op, expr_right))) = opt(pair( + delimited( + multispace, + expect( + "'<=', '>=', '<' or '>'".to_string(), + alt((tag("<="), tag(">="), tag("<"), tag(">"))), + ), + multispace, ), - parse_shift_expr, - ) - .parse(input) + // NOTE: If an operator was found, the RHS is now MANDATORY. `cut` enforces this. + cut(parse_shift_expr), + )) + .parse(i)? { let op_kind = match op { "<" => BinaryOp::Lt, @@ -243,23 +326,21 @@ pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE ann: build_offset_from_exprs(&expr_left, &expr_right), expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), }; - - return Ok((input, expr_left)); + return Ok((i, expr_left)); } - Ok((input, expr_left)) + Ok((i, expr_left)) } pub(crate) fn parse_shift_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, mut expr_left) = parse_additive_expr(input)?; + let (i, mut expr_left) = parse_additive_expr(input)?; - // Comparison operators are not associative (e.g., `a < b < c` is invalid), - // so we just look for one optional occurrence. - if let Ok((input, (op, expr_right))) = pair( - context("comparison", delimited(multispace, alt((tag("<<"), tag(">>"))), multispace)), - parse_additive_expr, - ) - .parse(input) + // Apply the same `opt`/`cut` pattern here + if let (i, Some((op, expr_right))) = opt(pair( + delimited(multispace, expect("'<<' or '>>'", alt((tag("<<"), tag(">>")))), multispace), + cut(parse_additive_expr), + )) + .parse(i)? { let op_kind = match op { "<<" => BinaryOp::ShiftLeft, @@ -270,40 +351,39 @@ pub(crate) fn parse_shift_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> ann: build_offset_from_exprs(&expr_left, &expr_right), expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), }; - - return Ok((input, expr_left)); + return Ok((i, expr_left)); } - Ok((input, expr_left)) + Ok((i, expr_left)) } pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, (first, remainder)) = pair( - parse_multiplicative_expr, - context( - "additive", - many0(pair( - delimited(multispace, alt((tag("+"), tag("-"))), multispace), - parse_multiplicative_expr, - )), - ), - ) - .parse(input)?; - - Ok(( - input, - remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { + let (mut i, mut expr_left) = parse_multiplicative_expr(input)?; + + loop { + // Use `opt` to look for an operator. If none, we're done. + let (next_i, remainder) = opt(pair( + delimited(multispace, expect("'+' or '-'", alt((tag("+"), tag("-")))), multispace), + // If the operator IS found, `cut` makes the RHS mandatory. + cut(parse_multiplicative_expr), + )) + .parse(i)?; + + if let Some((op, expr_right)) = remainder { let op_kind = match op { "+" => BinaryOp::Add, "-" => BinaryOp::Sub, _ => unreachable!(), }; - OffsetExpr { + expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), - } - }), - )) + }; + i = next_i; + } else { + return Ok((i, expr_left)); + } + } } pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { @@ -312,7 +392,11 @@ pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, Off context( "multiplicative", many0(pair( - delimited(multispace, alt((tag("*"), tag("/"), tag("%"))), multispace), + delimited( + multispace, + expect("'*', '/' or '%'".to_string(), alt((tag("*"), tag("/"), tag("%")))), + multispace, + ), parse_prefix_expr, )), ), @@ -340,17 +424,23 @@ pub(crate) fn parse_prefix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> alt(( context( "prefix", - map(preceded(terminated(tag("!"), multispace), parse_prefix_expr), |expr| OffsetExpr { - ann: expr.ann, - expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), - }), + map( + preceded( + terminated(expect("'!'".to_string(), tag("!")), multispace), + parse_prefix_expr, + ), + |expr| OffsetExpr { + ann: expr.ann, + expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), + }, + ), ), parse_postfix_expr, )) .parse(input) } -enum Postfix { +pub(crate) enum Postfix { ArrayIndex(OffsetExpr), TupleMember(BigInt), } @@ -377,7 +467,7 @@ pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr expr: Box::new(ExprF::TupleAccess { expr: current_expr, index: index.try_into().map_err(|_| { - nom::Err::Error(Error { parser_errors: vec![], contexts: vec![] }) + NomErr::Error(Error { parser_errors: vec![], contexts: vec![] }) })?, }), }, @@ -387,7 +477,7 @@ pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr Ok((input, final_expr)) } -fn parse_any_suffix<'a>(input: Input<'a>) -> PResult<'a, Postfix> { +pub(crate) fn parse_any_suffix<'a>(input: Input<'a>) -> PResult<'a, Postfix> { alt(( map(parse_index_suffix, Postfix::ArrayIndex), map(parse_member_suffix, Postfix::TupleMember), @@ -395,18 +485,47 @@ fn parse_any_suffix<'a>(input: Input<'a>) -> PResult<'a, Postfix> { .parse(input) } -/// Parses an index suffix `[expr]` and returns just the inner expression. -fn parse_index_suffix<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { +pub(crate) fn parse_index_suffix<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { preceded( multispace, - delimited(tag("["), cut(delimited(multispace, parse_expression, multispace)), tag("]")), + delimited( + expect("'[".to_string(), tag("[")), + cut(delimited(multispace, parse_expression, multispace)), + expect("']'".to_string(), tag("]")), + ), ) .parse(input) } -/// Parses a member access `.field` and returns just the field identifier. -fn parse_member_suffix<'a>(input: Input<'a>) -> PResult<'a, BigInt> { - preceded(pair(multispace, tag(".")), cut(parse_int)).parse(input) +pub(crate) fn parse_member_suffix<'a>(input: Input<'a>) -> PResult<'a, BigInt> { + preceded(pair(multispace, expect("'.' for tuple access".to_string(), tag("."))), cut(parse_int)) + .parse(input) +} + +// Add this helper function to your parser file. +fn trace<'a, P, O: Debug>( + name: &'static str, + mut parser: P, +) -> impl FnMut(Input<'a>) -> IResult, O, Error> +where + P: Parser, Output = O, Error = Error>, +{ + move |input: Input<'a>| { + println!("[trace] Enter '{}' on input: {:?}", name, input); + let result = parser.parse(input); + match &result { + Ok((remaining, output)) => { + println!( + "[trace] Success on '{}'! Output: {:?}, Remaining: {:?}", + name, output, remaining + ); + } + Err(e) => { + println!("[trace] Fail on '{}'! Error: {:?}", name, e); + } + } + result + } } pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { @@ -427,9 +546,9 @@ pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, Offs delimited( multispace, delimited( - delimited(multispace, tag("("), multispace), + expect("'('".to_string(), tag("(")), parse_expression, - delimited(multispace, tag(")"), multispace), + expect("')'".to_string(), tag(")")), ), multispace, ) @@ -438,27 +557,24 @@ pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, Offs pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let prev_offset = input.len(); - let (input, quantifier_kind) = parse_identifier(input)?; - - let quantifier = match quantifier_kind { - "forall" => Quantifier::Forall, - "exists" => Quantifier::Exists, - _ => return Err(nom::Err::Error(Error { parser_errors: vec![], contexts: vec![] })), - }; + let (input, quantifier) = context("quantifier keyword", parse_quantifier_kind).parse(input)?; let (input, _) = multispace(input)?; - // TODO: better `space` management let (input, (variables, expr)) = delimited( - tag("("), + expect("'('".to_string(), tag("(")), cut(pair( delimited( - delimited(multispace, tag("|"), multispace), - cut(separated_list0(delimited(multispace, tag(","), multispace), parse_identifier)), - delimited(multispace, tag("|"), multispace), + expect("'|'".to_string(), tag("|")), + // NOTE: unlike functions, quantifiers need to have at least one bound variable + cut(separated_list1( + delimited(multispace, expect("','".to_string(), tag(",")), multispace), + parse_identifier, + )), + expect("'|'".to_string(), tag("|")), ), delimited(multispace, parse_expression, multispace), )), - tag(")"), + expect("')'".to_string(), tag(")")), ) .parse(input)?; let after_offset = input.len(); @@ -480,14 +596,30 @@ pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE )) } +fn parse_quantifier_kind<'a>(input: Input<'a>) -> PResult<'a, Quantifier> { + let (i, ident) = parse_identifier(input)?; + match ident { + "forall" => Ok((i, Quantifier::Forall)), + "exists" => Ok((i, Quantifier::Exists)), + // NOTE: If the identifier is not a valid quantifier, fail with a specific error + _ => Err(NomErr::Error(build_error( + input, + ParserErrorKind::InvalidQuantifier(ident.to_string()), + ))), + } +} + pub(crate) fn parse_fn_call_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let prev_offset = input.len(); let (input, name) = context("fn_call name", parse_identifier).parse(input)?; let (input, params) = delimited( - tag("("), - cut(separated_list0(delimited(multispace, tag(","), multispace), parse_expression)), - tag(")"), + expect("'('".to_string(), tag("(")), + cut(separated_list0( + delimited(multispace, expect("','".to_string(), tag(",")), multispace), + parse_expression, + )), + expect("')'", tag(")")), ) .parse(input)?; let after_offset = input.len(); @@ -518,12 +650,14 @@ pub(crate) fn parse_literal_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr } pub(crate) fn parse_bool<'a>(input: Input<'a>) -> PResult<'a, bool> { - alt((map(tag("true"), |_| true), map(tag("false"), |_| false))).parse(input) + expect("boolean 'true' or 'false'", alt((value(true, tag("true")), value(false, tag("false"))))) + .parse(input) } pub(crate) fn parse_int<'a>(input: Input<'a>) -> PResult<'a, BigInt> { let (input, sign) = parse_sign(input)?; - let (input, digits) = digit(input)?; + + let (input, digits) = expect("an integer".to_string(), digit)(input)?; let biguint = digits .chars() @@ -542,35 +676,32 @@ pub(crate) fn parse_int<'a>(input: Input<'a>) -> PResult<'a, BigInt> { } pub(crate) fn parse_sign<'a>(input: Input<'a>) -> PResult<'a, bool> { - let (input, opt_sign) = opt(alt(( - // - value(false, tag(&b"-"[..])), - value(true, tag(&b"+"[..])), - ))) + let (input, opt_sign) = opt(map_nom_err( + alt((value(false, tag("-")), value(true, tag("+")))), + |_| ParserErrorKind::Message("Should not fail".to_string()), // NOTE: `opt` makes this effectively infallible + )) .parse(input)?; let sign = opt_sign.unwrap_or(true); - Ok((input, sign)) } +const FORBIDDEN_IDENTIFIERS: &[&str] = &["forall", "exists"]; + // TODO: parse module references `fv_std::SOMETHING` pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let prev_offset = input.len(); - let (input, ident) = parse_identifier(input).map_err(|_| { - // Err::Error(Error { - // resolver_errors: vec![ResolverError::ParserError(Box::new( - // NoirParserError::with_reason( - // // TODO: ne - // ParserErrorReason::DocCommentDoesNotDocumentAnything, - // Location::dummy(), - // ), - // ))], - // location: Location::dummy(), - // }) - Err::Error(Error { parser_errors: vec![ParserError::Oops], contexts: vec![] }) - })?; + + let (input, ident) = parse_identifier(input)?; + let after_offset = input.len(); + if FORBIDDEN_IDENTIFIERS.contains(&ident) { + return Err(NomErr::Error(build_error( + input, + ParserErrorKind::InvalidIdentifier(ident.to_string()), + ))); + } + Ok(( input, build_expr( @@ -590,15 +721,10 @@ pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { c.is_ascii_alphanumeric() || c == '_' } - let mut parser = recognize(pair( - // - take_while1(is_valid_start), - take_while(is_valid_char), - )); - - let (input, name) = parser.parse(input)?; + let identifier_parser = recognize(pair(take_while1(is_valid_start), take_while(is_valid_char))); - Ok((input, name)) + // Wrap the internal parser to catch the error from `take_while1` + expect("an identifier".to_string(), identifier_parser)(input) } #[cfg(test)] @@ -612,8 +738,7 @@ pub mod tests { use crate::{ Attribute, State, - ast::{AnnExpr, Literal, RawExpr, cata, strip_ann}, - parse_attribute, + ast::{Literal, cata, strip_ann}, }; use super::*; @@ -849,4 +974,90 @@ pub mod tests { ) .unwrap(); } + + #[test] + fn test_parse_failure_identifier() { + let annotation = "ensures(5 > 'invalid)"; + let state = empty_state(); + let result = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ); + + let err = result.unwrap_err(); + + let first_error = err.parser_errors.get(0).expect("Should have one parser error"); + assert!(matches!(&first_error.kind, ParserErrorKind::Expected { expected, found } + if expected == "an identifier" && found.starts_with("'invalid") + )); + } + + #[test] + fn test_parse_failure_attribute() { + let annotation = "banica(true)"; + let state = empty_state(); + let result = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ); + + let err = result.unwrap_err(); + + let first_error = err.parser_errors.get(0).expect("Should have one parser error"); + assert!(matches!(&first_error.kind, ParserErrorKind::UnknownAttribute(attr) + if attr == "banica" + )); + } + + #[test] + fn test_parse_failure_salvo() { + let annotations = vec![ + // Valid annotation, invalid quantifier + "requires(exists(x > 5))", + "requires(forall(|| x > 5))", + "requires(forall(||))", + "requires(forall)", + "requires(forall(|i x > 5))", + "requires(exists |j| x < 4)", + "requires(exists())", + + // Invalid annotation + "ensures(result > 5", + "ensures result > 5)", + "ensures result > 5", + "requires", + "ensures", + "ensures()", + "ensures(result > 4)x", + "requires x == 4)", + ]; + for annotation in annotations { + let state = empty_state(); + let result = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ); + + let err = result.unwrap_err(); + dbg!(annotation, err); + } + } } diff --git a/compiler/formal_verification/src/parse/errors.rs b/compiler/formal_verification/src/parse/errors.rs new file mode 100644 index 00000000000..41e23047397 --- /dev/null +++ b/compiler/formal_verification/src/parse/errors.rs @@ -0,0 +1,140 @@ +use noirc_errors::Span; +use nom::{ + Err as NomErr, IResult, Parser, + error::{ContextError, ErrorKind, ParseError}, +}; +use std::fmt::Debug; + +use super::Input; + +// An individual, specific error that occurred. +#[derive(Debug, Clone)] +pub struct ParserError { + pub span: Span, + pub kind: ParserErrorKind, +} + +// The different kinds of specific errors. +#[derive(Debug, Clone)] +pub enum ParserErrorKind { + Expected { + // e.g., "Expected ')'" + expected: String, + // e.g., "found 'foo'" + found: String, + }, + // e.g., "forall" + InvalidQuantifier(String), + // e.g., "reassures(...)" + UnknownAttribute(String), + // e.g., "123_abc" + InvalidIntegerLiteral, + // e.g., "user.5" where the member access must be an integer literal + InvalidTupleIndex, + // When an identifier doesn't follow the rules (e.g., starts with a number) + InvalidIdentifier(String), + // A generic message for when other errors don't fit + Message(String), +} + +// The error type that nom's functions will return +#[derive(Debug, Clone)] +pub struct Error { + pub parser_errors: Vec, + pub contexts: Vec, +} + +// Helper to get a Span from a nom Input +pub fn input_to_span(i: Input) -> Span { + let offset = i.as_ptr() as usize - i.as_bytes().as_ptr() as usize; + Span::single_char(offset as u32) +} + +/// Builds and returns our custom Error struct directly. +pub fn build_error(input: Input, kind: ParserErrorKind) -> Error { + Error { + parser_errors: vec![ParserError { span: input_to_span(input), kind }], + contexts: vec![], + } +} + +/// From an input slice, get the next token for use in an error message. +pub fn get_found_token(input: Input) -> String { + match input.split_whitespace().next() { + Some("") | None => "end of input".to_string(), + Some(token) => token.to_string(), + } +} + +/// A specialized version of `map_nom_err` for the common "Expected" error. +pub fn expect<'a, P, O>( + // TODO: maybe `Into` or similar? + expected_msg: impl AsRef, + parser: P, +) -> impl FnMut(Input<'a>) -> IResult, O, Error> +where + P: Parser, Output = O, Error = Error>, +{ + map_nom_err(parser, move |fail_input| ParserErrorKind::Expected { + expected: expected_msg.as_ref().to_string(), + found: get_found_token(fail_input), + }) +} + +/// A combinator that wraps a parser and maps any `NomErr::Error` to a custom error kind. +/// +/// It takes a closure `F` that generates the error kind, allowing dynamic error +/// messages based on the input at the failure point. +pub fn map_nom_err<'a, P, O, F>( + mut parser: P, + error_fn: F, +) -> impl FnMut(Input<'a>) -> IResult, O, Error> +where + P: Parser, Output = O, Error = Error>, + F: Fn(Input<'a>) -> ParserErrorKind, +{ + move |input: Input<'a>| { + parser.parse(input).map_err(|e: NomErr| { + if let NomErr::Error(_) = e { + // Generate the error dynamically using the input at the point of failure. + NomErr::Error(build_error(input, error_fn(input))) + } else { + e + } + }) + } +} + +impl<'a> ParseError> for Error { + /// This function is called by nom's primitives when they fail. + /// It should create a generic error that can be enriched later by other combinators. + fn from_error_kind(input: Input<'a>, kind: ErrorKind) -> Self { + // unreachable!( + // "We should wrap all errors and never have to convert from the built-in `nom` ones, still got a {:?} while parsing {:?} tho", + // kind, input + // ); + let err = ParserError { + span: input_to_span(input), + // Create a generic message from the nom ErrorKind. + kind: ParserErrorKind::Message(format!("nom primitive failed: {:?}", kind)), + }; + Error { parser_errors: vec![err], contexts: vec![] } + } + + fn append(input: Input<'a>, kind: ErrorKind, mut other: Self) -> Self { + // // This is called when `alt` fails, for example. We can add to the error stack. + // let err = ParserError { + // span: input_to_span(input), + // kind: ParserErrorKind::Message(format!("nom append error: {:?}", kind)), + // }; + // other.parser_errors.push(err); + other + } +} + +impl<'a> ContextError> for Error { + fn add_context(input: Input<'a>, ctx: &'static str, mut other: Self) -> Self { + other.contexts.push(ctx.to_string()); + other + } +} diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index ac30185448f..e2b6151456f 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -124,6 +124,12 @@ pub fn propagate_concrete_type( } pub fn type_infer(state: State, expr: SpannedExpr) -> Result { + // NOTE: predicate, always bool, + // assume subterms are `u32` (like `Noir` does) + let default_literal_type = NoirType::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); + + let is_numeric = |t: &NoirType| matches!(t, NoirType::Integer(_, _) | NoirType::Field); + let sote: SpannedOptionallyTypedExpr = try_contextual_cata( expr, vec![], @@ -209,13 +215,62 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result (exprf.clone(), expr.ann.1.clone()), ExprF::BinaryOp { op, expr_left, expr_right } => { match op { + BinaryOp::ShiftLeft | BinaryOp::ShiftRight => { + let mut expr_left = expr_left.clone(); + let mut expr_right = expr_right.clone(); + + let shift_amount_type = + NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Eight); + + match expr_right.ann.1 { + // Fine shift amount, only `u8` is allowed in `Noir` + Some(ref t) if *t == shift_amount_type => {}, + // Integer literal, try type inferring to `u8` + None => { + expr_right = + propagate_concrete_type(expr_right, shift_amount_type)?; + } + // Not fine shift amount + Some(_) => { + return Err(TypeInferenceError::TypeError { + got: expr_right.ann.1, + wanted: Some(shift_amount_type), + message: Some(format!("Can only bit shift using `u8`")), + }); + } + } + + match expr_left.ann.1 { + // Fine shiftee + Some(NoirType::Integer(Signedness::Unsigned, _)) => {} + // Integer literal, try type inferring to u32 + None => { + expr_left = propagate_concrete_type(expr_left, default_literal_type.clone())?; + } + // Not fine shiftee + Some(_) => { + return Err(TypeInferenceError::TypeError { + got: expr_left.ann.1, + wanted: None, + message: Some(format!("Can only bit shift unsigned integers")), + }); + } + } + + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left, + expr_right, + }, + None, + ) + } BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod | BinaryOp::Add | BinaryOp::Sub - | BinaryOp::ShiftLeft - | BinaryOp::ShiftRight | BinaryOp::Eq | BinaryOp::Neq | BinaryOp::Lt @@ -232,12 +287,6 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result {} // Integer literal, try type inferring to `u32` None => { - index = propagate_concrete_type( - index, - NoirType::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo), - )?; + index = propagate_concrete_type(index, default_literal_type.clone())?; } // Not fine index Some(_) => { @@ -415,8 +461,7 @@ mod tests { use crate::{ Attribute, ast::{Literal, Variable}, - parse::tests::*, - parse_attribute, + parse::{parse_attribute, tests::*}, }; #[test] diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 74226f65ebb..497e5d3ba43 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashMap}; use fm::FileId; use formal_verification::ast::SpannedTypedExpr; use formal_verification::typing::type_infer; -use formal_verification::{State, parse_attribute}; +use formal_verification::{State, parse::parse_attribute}; use iter_extended::vecmap; use noirc_driver::{CompilationResult, CompileError, CompileOptions, check_crate}; use noirc_errors::CustomDiagnostic; From 87adca5d3c4d161144d48cf226edb6ebe34cea6e Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 27/86] chore(annotations): remove old comments --- compiler/formal_verification/src/parse.rs | 5 ----- compiler/formal_verification/src/typing.rs | 2 -- 2 files changed, 7 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index c474569334f..0e9762b3923 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -335,7 +335,6 @@ pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE pub(crate) fn parse_shift_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (i, mut expr_left) = parse_additive_expr(input)?; - // Apply the same `opt`/`cut` pattern here if let (i, Some((op, expr_right))) = opt(pair( delimited(multispace, expect("'<<' or '>>'", alt((tag("<<"), tag(">>")))), multispace), cut(parse_additive_expr), @@ -361,10 +360,8 @@ pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExp let (mut i, mut expr_left) = parse_multiplicative_expr(input)?; loop { - // Use `opt` to look for an operator. If none, we're done. let (next_i, remainder) = opt(pair( delimited(multispace, expect("'+' or '-'", alt((tag("+"), tag("-")))), multispace), - // If the operator IS found, `cut` makes the RHS mandatory. cut(parse_multiplicative_expr), )) .parse(i)?; @@ -502,7 +499,6 @@ pub(crate) fn parse_member_suffix<'a>(input: Input<'a>) -> PResult<'a, BigInt> { .parse(input) } -// Add this helper function to your parser file. fn trace<'a, P, O: Debug>( name: &'static str, mut parser: P, @@ -723,7 +719,6 @@ pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { let identifier_parser = recognize(pair(take_while1(is_valid_start), take_while(is_valid_char))); - // Wrap the internal parser to catch the error from `take_while1` expect("an identifier".to_string(), identifier_parser)(input) } diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index e2b6151456f..406f12a515f 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -113,8 +113,6 @@ pub fn propagate_concrete_type( } } } - // TODO: handle type inferring pure boolean operators - // `(1 < 2) & x > 5` _ => {} } } From 927cee29a2d624ff4ee190e9ac7584b2005e74d1 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 28/86] fix(annotations): Even stricter type inferrence - If one of their two arguments is an integer literal (or quantified), arithmetic operators now require their other argument to also be a numeric type (integer or field) - Switch around `got` and `wanted` types of the `TypeInferrenceError::TypeError` from `bi_can_fit_in` - Add test for that (`1 + true` leads to a `TypeInferrenceError`) - Add test for bitshift's requirement for its right operator to be `u8` --- compiler/formal_verification/src/typing.rs | 96 +++++++++++++++++++--- 1 file changed, 83 insertions(+), 13 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 406f12a515f..1ac5a3d4300 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -103,8 +103,8 @@ pub fn propagate_concrete_type( FitsIn::Yes => {} FitsIn::No { need } => { return Err(TypeInferenceError::TypeError { - got: need.clone(), - wanted: Some(t.clone()), + got: Some(t.clone()), + wanted: need.clone(), message: Some(format!( "Integer literal {} cannot fit in {}, needs at least {:?} or larger", bi, t, need, @@ -126,7 +126,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result {} // Integer literal, try type inferring to u32 None => { - expr_left = propagate_concrete_type(expr_left, default_literal_type.clone())?; + expr_left = propagate_concrete_type( + expr_left, + default_literal_type.clone(), + )?; } // Not fine shiftee Some(_) => { return Err(TypeInferenceError::TypeError { got: expr_left.ann.1, wanted: None, - message: Some(format!("Can only bit shift unsigned integers")), + message: Some(format!( + "Can only bit shift unsigned integers" + )), }); } } - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left, - expr_right, - }, - None, - ) + (ExprF::BinaryOp { op: op.clone(), expr_left, expr_right }, None) } BinaryOp::Mul | BinaryOp::Div @@ -305,6 +303,17 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { + // NOTE: `1 & true` + if is_arith && !is_numeric(t2) { + return Err(TypeInferenceError::TypeError { + got: Some(t2.clone()), + wanted: None, + message: Some(format!( + "Cannot mix untyped integers and non-numeric arguments for arithmetic+boolean operators" + )), + }); + } + let expr_left_inner = propagate_concrete_type(expr_left.clone(), t2.clone())?; @@ -318,6 +327,17 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { + // NOTE: `true & 1` + if is_arith && !is_numeric(t1) { + return Err(TypeInferenceError::TypeError { + got: Some(t1.clone()), + wanted: None, + message: Some(format!( + "Cannot mix untyped integers and non-numeric arguments for arithmetic+boolean operators" + )), + }); + } + let expr_right_inner = propagate_concrete_type(expr_right.clone(), t1.clone())?; @@ -652,6 +672,56 @@ mod tests { assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } + #[test] + fn test_operators_mixed_types() { + let attribute = "ensures(1 + true)"; + let state = empty_state(); + let attribute = parse_attribute( + attribute, + Location { span: Span::inclusive(0, attribute.len() as u32), file: Default::default() }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let type_inference_error = type_infer(state, spanned_expr).unwrap_err(); + let TypeInferenceError::TypeError { got, wanted, message } = type_inference_error else { + panic!() + }; + dbg!(&got, &wanted, &message); + + // NOTE: untyped integer literal (same for quantifier variables) force the other argument + // to also be numeric + assert_eq!(got, Some(NoirType::Bool)); + assert_eq!(wanted, None); + assert!(message.unwrap().contains("mix untyped integers and non-numeric")); + } + + #[test] + fn test_bitshift() { + let attribute = "ensures(1 << 256)"; + let state = empty_state(); + let attribute = parse_attribute( + attribute, + Location { span: Span::inclusive(0, attribute.len() as u32), file: Default::default() }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let type_inference_error = type_infer(state, spanned_expr).unwrap_err(); + let TypeInferenceError::TypeError { got, wanted, message } = type_inference_error else { + panic!() + }; + dbg!(&got, &wanted, &message); + assert_eq!(got, Some(NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Eight))); + // NOTE: minimal size that fits `256` + assert_eq!(wanted, Some(NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Sixteen))); + assert_eq!(message, Some("Integer literal 256 cannot fit in u8, needs at least Some(Integer(Unsigned, Sixteen)) or larger".to_string())); + } + #[test] fn test_tuple_access() { let attribute = "ensures(user.0 ==> true)"; From 8d1a2c73047ba40392bfb1784d04495479ac9e37 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 29/86] feat(annotations): add cast expressions - Parsing (only `Field`, `bool` and `{u,i}{1,8,16,32,64,128}`) of type literals - Do not parse whitespace around parenthesis (eats required space for cast expressions) - Add tests (parsing and type-checking) for casting TODO: - Conversion to VIR --- compiler/formal_verification/src/ast.rs | 31 +++--- compiler/formal_verification/src/parse.rs | 105 +++++++++++++++++-- compiler/formal_verification/src/typing.rs | 85 ++++++++++++--- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 4 + 4 files changed, 190 insertions(+), 35 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 9010548ba5a..39957cf7880 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; pub type Identifier = String; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ExprF { BinaryOp { op: BinaryOp, expr_left: R, expr_right: R }, UnaryOp { op: UnaryOp, expr: R }, @@ -16,11 +16,12 @@ pub enum ExprF { FnCall { name: Identifier, args: Vec }, Index { expr: R, index: R }, TupleAccess { expr: R, index: u32 }, + Cast { expr: R, target: NoirType }, Literal { value: Literal }, Variable(Variable), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct AnnExpr { pub ann: A, pub expr: Box>>, @@ -31,7 +32,7 @@ pub type SpannedTypedExpr = AnnExpr<(Location, NoirType)>; pub type SpannedExpr = AnnExpr; pub type OffsetExpr = AnnExpr<(u32, u32)>; -#[derive(Clone, derive_more::Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, derive_more::Debug, PartialEq, Eq)] #[debug("{_0:?}")] pub struct RawExpr(pub Box>); @@ -39,25 +40,25 @@ pub fn strip_ann(expr: AnnExpr) -> RawExpr { cata(expr, &|_, expr| RawExpr(Box::new(expr))) } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Literal { Bool(bool), Int(BigInt), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Quantifier { Forall, Exists, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum UnaryOp { // Arithmetic and Boolean Not, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum BinaryOp { // pure Arithmetic (data -> data) Mul, @@ -129,7 +130,7 @@ impl BinaryOp { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Variable { pub name: Identifier, pub id: Option, @@ -155,8 +156,11 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { ExprF::Index { expr: indexee, index } => { ExprF::Index { expr: cata_fn(indexee), index: cata_fn(index) } } - ExprF::TupleAccess { expr, index: member } => { - ExprF::TupleAccess { expr: cata_fn(expr), index: member } + ExprF::TupleAccess { expr, index } => { + ExprF::TupleAccess { expr: cata_fn(expr), index } + } + ExprF::Cast { expr, target } => { + ExprF::Cast { expr: cata_fn(expr), target } } ExprF::Literal { value } => ExprF::Literal { value }, ExprF::Variable(Variable { name, id }) => ExprF::Variable(Variable { name, id }), @@ -180,8 +184,11 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res ExprF::Index { expr: indexee, index } => { ExprF::Index { expr: cata_fn(indexee)?, index: cata_fn(index)? } } - ExprF::TupleAccess { expr, index: member } => { - ExprF::TupleAccess { expr: cata_fn(expr)?, index: member } + ExprF::TupleAccess { expr, index } => { + ExprF::TupleAccess { expr: cata_fn(expr)?, index } + } + ExprF::Cast { expr, target } => { + ExprF::Cast { expr: cata_fn(expr)?, target } } ExprF::Literal { value } => ExprF::Literal { value }, ExprF::Variable(Variable { name, id }) => ExprF::Variable(Variable { name, id }), diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 0e9762b3923..8d9e7459eb3 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -1,10 +1,14 @@ use noirc_errors::{Location, Span}; -use noirc_frontend::monomorphization::ast as mast; +use noirc_frontend::{ + ast::IntegerBitSize, + monomorphization::ast::{self as mast, Type as NoirType}, + shared::Signedness, +}; use nom::{ Err as NomErr, IResult, Parser, branch::alt, bytes::complete::{tag, take_while, take_while1}, - character::complete::{digit1 as digit, multispace0 as multispace}, + character::complete::{digit1 as digit, multispace0 as multispace, multispace1}, combinator::{cut, map, opt, recognize, value}, error::context, multi::{many0, separated_list0, separated_list1}, @@ -18,7 +22,7 @@ use errors::{Error, ParserErrorKind, build_error, expect, map_nom_err}; use crate::{ Attribute, - ast::{BinaryOp, ExprF, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, + ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, span_expr, }; @@ -440,6 +444,13 @@ pub(crate) fn parse_prefix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> pub(crate) enum Postfix { ArrayIndex(OffsetExpr), TupleMember(BigInt), + Cast(CastTargetType), +} + +pub(crate) enum CastTargetType { + Field, + Integer(Signedness, IntegerBitSize), + Bool, } pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { @@ -468,6 +479,19 @@ pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr })?, }), }, + Postfix::Cast(ident) => OffsetExpr { + ann: new_ann, + expr: Box::new(ExprF::Cast { + expr: current_expr, + target: match ident { + CastTargetType::Field => NoirType::Field, + CastTargetType::Integer(signedness, integer_bit_size) => { + NoirType::Integer(signedness, integer_bit_size) + } + CastTargetType::Bool => NoirType::Bool, + }, + }), + }, }) })?; @@ -478,6 +502,7 @@ pub(crate) fn parse_any_suffix<'a>(input: Input<'a>) -> PResult<'a, Postfix> { alt(( map(parse_index_suffix, Postfix::ArrayIndex), map(parse_member_suffix, Postfix::TupleMember), + map(parse_cast_suffix, Postfix::Cast), )) .parse(input) } @@ -499,6 +524,49 @@ pub(crate) fn parse_member_suffix<'a>(input: Input<'a>) -> PResult<'a, BigInt> { .parse(input) } +pub(crate) fn parse_cast_suffix<'a>(input: Input<'a>) -> PResult<'a, CastTargetType> { + let (input, type_ident) = + preceded(delimited(multispace1, tag("as"), multispace1), cut(parse_identifier)) + .parse(input)?; + + if type_ident == "Field" { + return Ok((input, CastTargetType::Field)); + } + + if type_ident == "bool" { + return Ok((input, CastTargetType::Bool)); + } + + if let Some((signedness @ ("i" | "u"), size)) = type_ident.split_at_checked(1) { + return Ok(( + input, + CastTargetType::Integer( + match signedness { + "i" => Signedness::Signed, + "u" => Signedness::Unsigned, + _ => unreachable!(), + }, + match size { + "1" => IntegerBitSize::One, + "8" => IntegerBitSize::Eight, + "16" => IntegerBitSize::Sixteen, + "32" => IntegerBitSize::ThirtyTwo, + "64" => IntegerBitSize::SixtyFour, + "128" => IntegerBitSize::HundredTwentyEight, + _ => { + return Err(NomErr::Error(build_error( + size, + ParserErrorKind::InvalidIntegerLiteral, + ))); + } + }, + ), + )); + } + + Err(NomErr::Error(build_error(type_ident, ParserErrorKind::InvalidIntegerLiteral))) +} + fn trace<'a, P, O: Debug>( name: &'static str, mut parser: P, @@ -540,13 +608,9 @@ pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { delimited( - multispace, - delimited( - expect("'('".to_string(), tag("(")), - parse_expression, - expect("')'".to_string(), tag(")")), - ), - multispace, + expect("'('".to_string(), tag("(")), + parse_expression, + expect("')'".to_string(), tag(")")), ) .parse(input) } @@ -970,6 +1034,26 @@ pub mod tests { .unwrap(); } + #[test] + fn test_cast() { + let annotation = "ensures((15 as i16 - 3 > 2) & ((result as Field - 6) as u64 == 1 + a as u64 >> kek as u8))"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(expr) = attribute else { panic!() }; + dbg!(strip_ann(expr)); + } + #[test] fn test_parse_failure_identifier() { let annotation = "ensures(5 > 'invalid)"; @@ -1027,7 +1111,6 @@ pub mod tests { "requires(forall(|i x > 5))", "requires(exists |j| x < 4)", "requires(exists())", - // Invalid annotation "ensures(result > 5", "ensures result > 5)", diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 1ac5a3d4300..cc3be145743 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -92,12 +92,18 @@ pub fn propagate_concrete_type( }; try_cata(e, &|(location, _type), expr| { - debug_assert!(_type == None); - // NOTE: only check limits for integer types - // (assume that `NoirType::Field` can hold anything) - if let Some((hole_sign, hole_size)) = limits { - match expr { - ExprF::Literal { value: Literal::Int(ref bi) } => { + match expr { + ExprF::Literal { value: Literal::Int(ref bi) } => { + debug_assert!( + _type == None, + "Trying to smash type {:?} into {:?} which already has type {:?}", + t, + bi, + _type + ); + // NOTE: only check limits for integer types + // (assume that `NoirType::Field` can hold anything) + if let Some((hole_sign, hole_size)) = limits { let fits = bi_can_fit_in(bi, hole_size, hole_sign); match fits { FitsIn::Yes => {} @@ -113,8 +119,8 @@ pub fn propagate_concrete_type( } } } - _ => {} } + _ => {} } Ok(SpannedOptionallyTypedExpr { expr: Box::new(expr), ann: (location, Some(t.clone())) }) @@ -220,9 +226,9 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result {}, + Some(t) if *t == shift_amount_type => {} // Integer literal, try type inferring to `u8` None => { expr_right = @@ -238,7 +244,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result {} // Integer literal, try type inferring to u32 @@ -260,7 +266,9 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result { + let mut expr = expr.clone(); + + // Non-booleans cannot cast to bool + if matches!(target, NoirType::Bool) && !matches!(expr.ann.1, Some(NoirType::Bool)) { + return Err(TypeInferenceError::TypeError { + got: None, + wanted: Some(NoirType::Bool), + message: Some(format!("Only booleans can we cast to bool",)), + }); + } + + // Non-numberics cannot cast to numeric types + if is_numeric(target) && let Some(ref t) = expr.ann.1 && !is_numeric(t) { + return Err(TypeInferenceError::TypeError { + got: Some(t.clone()), + wanted: None, + message: Some(format!("Only numeric expressions can we cast to numeric types",)), + }); + } + + // Try to type infer integer literals as the target type + if matches!(expr.ann.1, None) { + expr = propagate_concrete_type(expr, target.clone())?; + } + + (ExprF::Cast { expr, target: target.clone() }, Some(target.clone())) + } }; Ok(SpannedOptionallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) @@ -457,7 +493,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Ok(SpannedTypedExpr { ann: (location, t), expr: Box::new(exprf) }), - None => Err(()), + None => Err(format!("Expr {:?} still has no type", exprf)), }) .expect("Typing should have either succeeded or have resulted in an expected error"); @@ -581,6 +617,7 @@ mod tests { ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, ExprF::Index { expr, index } => expr && index, ExprF::TupleAccess { expr, .. } => expr, + ExprF::Cast { expr, .. } => expr, // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, @@ -619,6 +656,7 @@ mod tests { ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, ExprF::Index { expr, index } => expr && index, ExprF::TupleAccess { expr, .. } => expr, + ExprF::Cast { expr, .. } => expr, // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, @@ -645,6 +683,7 @@ mod tests { ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, ExprF::Index { expr, index } => expr && index, ExprF::TupleAccess { expr, .. } => expr, + ExprF::Cast { expr, .. } => expr, // Non-recursive variants don't carry information ExprF::Literal { .. } => true, @@ -758,6 +797,28 @@ mod tests { assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } + #[test] + fn test_cast() { + let annotation = "ensures((15 as i16 - 3 > 2) & ((result as Field - 6) as u64 == 1 + a as u64 >> kek as u8))"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr)); + // assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); + } + #[test] fn test_monomorphization_request() { let attribute = "ensures(f(result))"; diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index a14297bab7e..c08818189e8 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -329,6 +329,10 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> make_expr(vir_exprx, element_type, "Array indexing expression".to_string()) } + ExprF::Cast { expr, target } => { + // TODO(totel): conversion of cast expressions + todo!() + }, } }) } From a934df1d54a382b34696ac7ff2eb0f63a3f89aca Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 30/86] feat(annotations): add `location` field to `TypeError` --- compiler/formal_verification/src/typing.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index cc3be145743..85f6ed557f9 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -5,6 +5,7 @@ use crate::{ UnaryOp, Variable, cata, strip_ann, try_cata, try_contextual_cata, }, }; +use noirc_errors::Location; use noirc_frontend::{ ast::IntegerBitSize, monomorphization::{FUNC_RETURN_VAR_NAME, ast::Type as NoirType}, @@ -16,7 +17,7 @@ use num_traits::{One, Zero}; #[derive(Debug, Clone)] pub enum TypeInferenceError { MonomorphizationRequest(MonomorphizationRequest), - TypeError { got: Option, wanted: Option, message: Option }, + TypeError { got: Option, wanted: Option, location: Location, message: Option }, } #[derive(Debug, PartialEq, Eq)] @@ -115,6 +116,7 @@ pub fn propagate_concrete_type( "Integer literal {} cannot fit in {}, needs at least {:?} or larger", bi, t, need, )), + location, }); } } @@ -187,6 +189,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result { + return Err(TypeInferenceError::TypeError { got: expr_right.ann.1, wanted: Some(shift_amount_type), message: Some(format!("Can only bit shift using `u8`")), + location, }); } } @@ -262,6 +267,7 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result Result Result Result Result Result Result Result Result Result Date: Thu, 31 Jul 2025 16:30:49 +0300 Subject: [PATCH 31/86] feat(errors): Error propagation in `fv_bridge` We now convert the errors from the module `formal_verification` into the Noir error types so we can report them using the Noir compiler reporter. Fixes: - Allow bitshifting with signed integers - Allow casting from bool to integer --- .../formal_verification/src/parse/errors.rs | 23 +- compiler/formal_verification/src/typing.rs | 282 ++++++++++-------- compiler/fv_bridge/src/errors.rs | 60 ++++ compiler/fv_bridge/src/lib.rs | 62 ++-- 4 files changed, 276 insertions(+), 151 deletions(-) create mode 100644 compiler/fv_bridge/src/errors.rs diff --git a/compiler/formal_verification/src/parse/errors.rs b/compiler/formal_verification/src/parse/errors.rs index 41e23047397..c591a920333 100644 --- a/compiler/formal_verification/src/parse/errors.rs +++ b/compiler/formal_verification/src/parse/errors.rs @@ -1,4 +1,4 @@ -use noirc_errors::Span; +use noirc_errors::{CustomDiagnostic, Location, Span}; use nom::{ Err as NomErr, IResult, Parser, error::{ContextError, ErrorKind, ParseError}, @@ -138,3 +138,24 @@ impl<'a> ContextError> for Error { other } } + +impl From for CustomDiagnostic { + fn from(value: ParserError) -> Self { + // TODO(totel): Get proper location + let location = Location::dummy(); + + let primary_message = match value.kind { + ParserErrorKind::Expected { expected, found } => { + format!("Expected {} but found {}", expected, found) + } + ParserErrorKind::InvalidQuantifier(q) => format!("Invalid quantifier {}", q), + ParserErrorKind::UnknownAttribute(attr) => format!("Unknown attribute {}", attr), + ParserErrorKind::InvalidIntegerLiteral => "Invalid integer literal".to_string(), + ParserErrorKind::InvalidTupleIndex => "Invalid tuple index".to_string(), + ParserErrorKind::InvalidIdentifier(identifier) => format!("Invalid identifier {}", identifier), + ParserErrorKind::Message(msg) => msg, + }; + + CustomDiagnostic::simple_error(primary_message, String::new(), location) + } +} diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 85f6ed557f9..2aee05f68a0 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -2,12 +2,13 @@ use crate::{ MonomorphizationRequest, State, ast::{ BinaryOp, ExprF, Literal, SpannedExpr, SpannedOptionallyTypedExpr, SpannedTypedExpr, - UnaryOp, Variable, cata, strip_ann, try_cata, try_contextual_cata, + Variable, strip_ann, try_cata, try_contextual_cata, }, }; use noirc_errors::Location; use noirc_frontend::{ ast::IntegerBitSize, + hir::{resolution::errors::ResolverError, type_check::TypeCheckError}, monomorphization::{FUNC_RETURN_VAR_NAME, ast::Type as NoirType}, shared::Signedness, }; @@ -17,7 +18,17 @@ use num_traits::{One, Zero}; #[derive(Debug, Clone)] pub enum TypeInferenceError { MonomorphizationRequest(MonomorphizationRequest), - TypeError { got: Option, wanted: Option, location: Location, message: Option }, + // NOTE: We are converting IntegerLiteralDoesNotFit to TypeCheckError later + // We cannot do it during construction because we need a function + // located in a module which depends on us. + IntegerLiteralDoesNotFit { + literal: BigInt, + literal_type: NoirType, + fit_into: Option, + location: Location, + message: String, + }, + NoirTypeError(TypeCheckError), } #[derive(Debug, PartialEq, Eq)] @@ -109,13 +120,14 @@ pub fn propagate_concrete_type( match fits { FitsIn::Yes => {} FitsIn::No { need } => { - return Err(TypeInferenceError::TypeError { - got: Some(t.clone()), - wanted: need.clone(), - message: Some(format!( + return Err(TypeInferenceError::IntegerLiteralDoesNotFit { + literal: bi.clone(), + literal_type: t.clone(), + fit_into: need.clone(), + message: format!( "Integer literal {} cannot fit in {}, needs at least {:?} or larger", bi, t, need, - )), + ), location, }); } @@ -185,12 +197,9 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result { - - return Err(TypeInferenceError::TypeError { - got: expr_right.ann.1, - wanted: Some(shift_amount_type), - message: Some(format!("Can only bit shift using `u8`")), - location, - }); + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::InvalidShiftSize { location }, + )); } } match &expr_left.ann.1 { // Fine shiftee - Some(NoirType::Integer(Signedness::Unsigned, _)) => {} + Some(NoirType::Integer(_, _)) => {} // Integer literal, try type inferring to u32 None => { expr_left = propagate_concrete_type( @@ -260,15 +265,14 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { - return Err(TypeInferenceError::TypeError { - got: expr_left.ann.1, - wanted: None, - message: Some(format!( - "Can only bit shift unsigned integers" - )), - location, - }); + Some(expr_left_typ) => { + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expr_typ: expr_left_typ.to_string(), + expected_typ: String::from("Numeric type"), + expr_location: location, + }, + )); } } @@ -319,14 +323,13 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { // NOTE: `1 & true` if is_arith && !is_numeric(t2) { - return Err(TypeInferenceError::TypeError { - got: Some(t2.clone()), - wanted: None, - message: Some(format!( - "Cannot mix untyped integers and non-numeric arguments for arithmetic+boolean operators" - )), - location, - }); + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: "Numeric type".to_string(), + expr_typ: t2.to_string(), + expr_location: location, + }, + )); } let expr_left_inner = @@ -344,14 +347,13 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { // NOTE: `true & 1` if is_arith && !is_numeric(t1) { - return Err(TypeInferenceError::TypeError { - got: Some(t1.clone()), - wanted: None, - message: Some(format!( - "Cannot mix untyped integers and non-numeric arguments for arithmetic+boolean operators" - )), - location, - }); + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: "Numeric type".to_string(), + expr_typ: t1.to_string(), + expr_location: location, + }, + )); } let expr_right_inner = @@ -368,15 +370,13 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { if t1 != t2 { - return Err(TypeInferenceError::TypeError { - got: Some(t2.clone()), - wanted: Some(t1.clone()), - message: Some(format!( - "Different types of arguments to {} operation", - if is_arith { "arithmetic" } else { "predicate" } - )), - location, - }); + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: t2.to_string(), + expr_typ: t1.to_string(), + expr_location: location, + }, + )); } ( @@ -394,24 +394,32 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result { if expr_left.ann.1 != Some(NoirType::Bool) { - return Err(TypeInferenceError::TypeError { - got: expr_left.ann.1.clone(), - wanted: Some(NoirType::Bool), - message: Some( - "Boolean operations work on boolean arguments".to_string(), - ), - location, - }); + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expr_typ: expr_left + .ann + .1 + .as_ref() + .map(|t| t.to_string()) + .unwrap_or(String::new()), + expected_typ: NoirType::Bool.to_string(), + expr_location: location, + }, + )); } if expr_right.ann.1 != Some(NoirType::Bool) { - return Err(TypeInferenceError::TypeError { - got: expr_right.ann.1.clone(), - wanted: Some(NoirType::Bool), - message: Some( - "Boolean operations work on boolean arguments".to_string(), - ), - location, - }); + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expr_typ: expr_right + .ann + .1 + .as_ref() + .map(|t| t.to_string()) + .unwrap_or(String::new()), + expected_typ: NoirType::Bool.to_string(), + expr_location: location, + }, + )); } (exprf, Some(NoirType::Bool)) @@ -422,12 +430,18 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result { - return Err(TypeInferenceError::TypeError { - got: index.ann.1.clone(), - wanted: Some(NoirType::Integer( - Signedness::Unsigned, - IntegerBitSize::ThirtyTwo, - )), - message: Some(format!("Can only index using unsigned integers")), - location, - }); + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expr_typ: index + .ann + .1 + .as_ref() + .map(|t| t.to_string()) + .unwrap_or(String::new()), + expected_typ: String::from("Unsigned integer type"), + expr_location: location, + }, + )); } } @@ -456,17 +473,16 @@ pub fn type_infer(state: State, expr: SpannedExpr) -> Result Result), + TypeError(TypeCheckError), + ParserErrors(Vec), +} + +pub(crate) enum CompilationErrorBundle { + CompileError(CompileError), + ResolverErrors(Vec), + TypeError(TypeCheckError), + ParserErrors(Vec), +} + +impl From for MonomorphizationErrorBundle { + fn from(value: TypeInferenceError) -> Self { + match value { + TypeInferenceError::MonomorphizationRequest(monomorphization_request) => { + panic!("Monomorphization request can not be converted to an error") + } + TypeInferenceError::IntegerLiteralDoesNotFit { + literal, + literal_type, + fit_into, + location, + message, + } => MonomorphizationErrorBundle::TypeError( + TypeCheckError::IntegerLiteralDoesNotFitItsType { + expr: signed_field_from_bigint_wrapping(literal), + ty: noirc_frontend::Type::Unit, // We present the range which is enough + range: { + match fit_into { + Some(Type::Integer(_, bit_size)) => bit_size.to_string(), + _ => "Unknown range".to_string(), + } + }, + location, + }, + ), + TypeInferenceError::NoirTypeError(type_check_error) => { + MonomorphizationErrorBundle::TypeError(type_check_error) + } + } + } +} diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 497e5d3ba43..b0e9da7c7ed 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -12,7 +12,6 @@ use noirc_evaluator::{ errors::{RuntimeError, SsaReport}, vir::{create_verus_vir_with_ready_annotations, vir_gen::BuildingKrateError}, }; -use noirc_frontend::hir::resolution::errors::ResolverError; use noirc_frontend::{ debug::DebugInstrumenter, graph::CrateId, @@ -30,8 +29,10 @@ use noirc_frontend::{ }; use vir::ast::Krate; +use crate::errors::{CompilationErrorBundle, MonomorphizationErrorBundle}; use crate::typed_attrs_to_vir::convert_typed_attribute_to_vir_attribute; +mod errors; pub mod typed_attrs_to_vir; pub fn compile_and_build_vir_krate( @@ -59,10 +60,16 @@ fn modified_compile_main( let compiled_program = modified_compile_no_check(context, options, main).map_err(|error| match error { - CompileOrResolverError::CompileError(compile_error) => vec![compile_error.into()], - CompileOrResolverError::ResolverErrors(resolver_errors) => { + CompilationErrorBundle::CompileError(compile_error) => vec![compile_error.into()], + CompilationErrorBundle::ResolverErrors(resolver_errors) => { resolver_errors.iter().map(Into::into).collect() } + CompilationErrorBundle::TypeError(type_check_error) => { + vec![CustomDiagnostic::from(&type_check_error)] + } + CompilationErrorBundle::ParserErrors(parser_errors) => { + parser_errors.into_iter().map(CustomDiagnostic::from).collect() + } })?; let compilation_warnings = vecmap(compiled_program.warnings.clone(), CustomDiagnostic::from); @@ -76,17 +83,12 @@ fn modified_compile_main( Ok((compiled_program.krate, warnings)) } -pub enum CompileOrResolverError { - CompileError(CompileError), - ResolverErrors(Vec), -} - // Something like the method `compile_no_check()` fn modified_compile_no_check( context: &mut Context, options: &CompileOptions, main_function: node_interner::FuncId, -) -> Result { +) -> Result { let force_unconstrained = options.force_brillig || options.minimal_ssa; let (program, fv_annotations) = modified_monomorphize( @@ -96,13 +98,19 @@ fn modified_compile_no_check( force_unconstrained, ) .map_err(|e| match e { - MonomorphOrResolverError::MonomorphizationError(monomorphization_error) => { - CompileOrResolverError::CompileError(CompileError::MonomorphizationError( + MonomorphizationErrorBundle::MonomorphizationError(monomorphization_error) => { + CompilationErrorBundle::CompileError(CompileError::MonomorphizationError( monomorphization_error, )) } - MonomorphOrResolverError::ResolverErrors(resolver_errors) => { - CompileOrResolverError::ResolverErrors(resolver_errors) + MonomorphizationErrorBundle::ResolverErrors(resolver_errors) => { + CompilationErrorBundle::ResolverErrors(resolver_errors) + } + MonomorphizationErrorBundle::TypeError(type_check_error) => { + CompilationErrorBundle::TypeError(type_check_error) + } + MonomorphizationErrorBundle::ParserErrors(parser_errors) => { + CompilationErrorBundle::ParserErrors(parser_errors) } })?; @@ -119,18 +127,13 @@ fn modified_compile_no_check( }) }) .map_err(|runtime_error| { - CompileOrResolverError::CompileError(CompileError::RuntimeError(runtime_error)) + CompilationErrorBundle::CompileError(CompileError::RuntimeError(runtime_error)) })?, warnings: vec![], parse_annotations_errors: vec![], // TODO(totel): Get the errors from `modified_monomorphize()` }) } -enum MonomorphOrResolverError { - MonomorphizationError(MonomorphizationError), - ResolverErrors(Vec), -} - enum TypedAttribute { Ghost, Requires(SpannedTypedExpr), @@ -142,14 +145,14 @@ fn modified_monomorphize( interner: &mut NodeInterner, debug_instrumenter: &DebugInstrumenter, force_unconstrained: bool, -) -> Result<(Program, Vec<(FuncId, Vec)>), MonomorphOrResolverError> { +) -> Result<(Program, Vec<(FuncId, Vec)>), MonomorphizationErrorBundle> { let debug_type_tracker = DebugTypeTracker::build_from_debug_instrumenter(debug_instrumenter); // TODO(totel): Monomorphizer is a `pub(crate)` struct let mut monomorphizer = Monomorphizer::new(interner, debug_type_tracker); monomorphizer.in_unconstrained_function = force_unconstrained; let function_sig = monomorphizer .compile_main(main) - .map_err(MonomorphOrResolverError::MonomorphizationError)?; + .map_err(MonomorphizationErrorBundle::MonomorphizationError)?; let mut new_ids_to_old_ids: HashMap = HashMap::new(); new_ids_to_old_ids.insert(Program::main_id(), main); @@ -164,11 +167,11 @@ fn modified_monomorphize( let interner = &monomorphizer.interner; let impl_bindings = perform_impl_bindings(interner, trait_method, next_fn_id, location) .map_err(MonomorphizationError::InterpreterError) - .map_err(MonomorphOrResolverError::MonomorphizationError)?; + .map_err(MonomorphizationErrorBundle::MonomorphizationError)?; monomorphizer .function(next_fn_id, new_id, location) - .map_err(MonomorphOrResolverError::MonomorphizationError)?; + .map_err(MonomorphizationErrorBundle::MonomorphizationError)?; new_ids_to_old_ids.insert(new_id, next_fn_id); undo_instantiation_bindings(impl_bindings); undo_instantiation_bindings(bindings); @@ -227,7 +230,7 @@ fn modified_monomorphize( &globals, &monomorphizer.finished_functions, ) - .map_err(|x| panic!("{:#?}", x) as MonomorphOrResolverError)?; + .map_err(|e| MonomorphizationErrorBundle::ParserErrors(e.parser_errors))?; // Step 2: Type-infer the parsed attribute expression. let typed_attribute = match parsed_attribute { @@ -235,23 +238,26 @@ fn modified_monomorphize( formal_verification::Attribute::Ensures(expr) => { // TODO(totel): Handle MonomorphRequest error type let typed_expr = type_infer(state.clone(), expr) - .map_err(|x| panic!("{:#?}", x) as MonomorphOrResolverError)?; + .map_err(|e| MonomorphizationErrorBundle::from(e))?; TypedAttribute::Ensures(typed_expr) } formal_verification::Attribute::Requires(expr) => { // TODO(totel): Handle MonomorphRequest error type let typed_expr = type_infer(state.clone(), expr) - .map_err(|x| panic!("{:#?}", x) as MonomorphOrResolverError)?; + .map_err(|e| MonomorphizationErrorBundle::from(e))?; TypedAttribute::Requires(typed_expr) } }; // Step 3: Convert the typed attribute into its final representation. - Ok(convert_typed_attribute_to_vir_attribute(typed_attribute, &state)) + Ok::<_, MonomorphizationErrorBundle>(convert_typed_attribute_to_vir_attribute( + typed_attribute, + &state, + )) }) .collect::, _>>()?; - Ok((new_func_id, attributes)) + Ok::<_, MonomorphizationErrorBundle>((new_func_id, attributes)) }) .collect::, _>>()?; From b100d4b901f9fbb047cbc27ef018357214cb7a60 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 32/86] refactor(annotations): clean up - Use `.map(|_| _)` on `tag`s instead of a match on their output - Prepare prefix parsing for the addition of more prefix operators (dereferencing) - Add `context`s to the new parsers, who did not have any - Do early `cut`s smarter, for all "repeatable" operators - Add a "negative peek" to `==` to avoid falsely parsing `==>` as `==`, (needed because of the new eager `cut`s) - Parse whitespace between parenthesis - Rename back `i` to `input` (consistency) - Remove some TODO redundant comments - Resolve a few `unused` diagnostics - Remove old unused `trace` helper function for parser debugging --- compiler/formal_verification/src/parse.rs | 569 ++++++++++----------- compiler/formal_verification/src/typing.rs | 24 +- 2 files changed, 307 insertions(+), 286 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 8d9e7459eb3..ecde070b9ea 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -9,20 +9,20 @@ use nom::{ branch::alt, bytes::complete::{tag, take_while, take_while1}, character::complete::{digit1 as digit, multispace0 as multispace, multispace1}, - combinator::{cut, map, opt, recognize, value}, + combinator::{cut, map, not, opt, recognize, value}, error::context, multi::{many0, separated_list0, separated_list1}, sequence::{delimited, pair, preceded, terminated}, }; use num_bigint::{BigInt, BigUint, Sign}; -use std::{collections::BTreeMap, fmt::Debug}; +use std::collections::BTreeMap; pub mod errors; use errors::{Error, ParserErrorKind, build_error, expect, map_nom_err}; use crate::{ Attribute, - ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, + ast::{BinaryOp, ExprF, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, span_expr, }; @@ -78,7 +78,7 @@ pub fn parse_attribute<'a>( ..location }; - let (i, ident) = match expect("attribute name", parse_identifier)(annotation) { + let (input, ident) = match expect("attribute name", parse_identifier)(annotation) { Ok(result) => result, Err(nom_err) => { return match nom_err { @@ -93,12 +93,12 @@ pub fn parse_attribute<'a>( match ident { "ghost" => { - if !i.is_empty() { + if !input.is_empty() { return Err(build_error( - i, + input, ParserErrorKind::Message(format!( "Unexpected input after 'ghost' attribute: '{}'", - i + input )), )); } @@ -111,7 +111,7 @@ pub fn parse_attribute<'a>( cut(expect("')'", tag(")"))), ); - match expr_parser.parse(i) { + match expr_parser.parse(input) { Ok((rest, expr)) => { if !rest.is_empty() { return Err(build_error( @@ -132,7 +132,7 @@ pub fn parse_attribute<'a>( Err(nom_err) => match nom_err { NomErr::Error(e) | NomErr::Failure(e) => Err(e), NomErr::Incomplete(_) => Err(build_error( - i, + input, ParserErrorKind::Message("Incomplete input".to_string()), )), }, @@ -145,9 +145,6 @@ pub fn parse_attribute<'a>( } } -// TODO: array indexing - ast_index_to_vir_expr -// TODO: tuple indexing - ast_tuple_access_to_vir_expr - pub(crate) fn parse_expression<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { alt(( // @@ -157,286 +154,297 @@ pub(crate) fn parse_expression<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> } pub(crate) fn parse_implication_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, (first, remainder)) = pair( - parse_equality_expr, - context( + let (mut input, mut expr_left) = parse_equality_expr(input)?; + loop { + let (next_input, remainder) = opt(context( "implication", - many0(pair( - delimited(multispace, expect("'==>'", tag("==>")), multispace), - parse_equality_expr, - )), - ), - ) - .parse(input)?; + pair( + delimited( + multispace, + expect("'==>'", tag("==>").map(|_| BinaryOp::Implies)), + multispace, + ), + cut(parse_equality_expr), + ), + )) + .parse(input)?; - Ok(( - input, - remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { - let op_kind = match op { - "==>" => BinaryOp::Implies, - _ => unreachable!(), - }; - OffsetExpr { + if let Some((op, expr_right)) = remainder { + expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), - } - }), - )) + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), + }; + input = next_input; + } else { + return Ok((input, expr_left)); + } + } } pub(crate) fn parse_equality_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, (first, remainder)) = pair( - parse_or_expr, - context( - "equality", - many0(pair( - delimited( - multispace, - expect("'==' or '!='".to_string(), alt((tag("=="), tag("!=")))), - multispace, + let (mut input, mut expr_left) = parse_or_expr(input)?; + + loop { + let (next_input, remainder) = opt( + // Re-introduce `context` here to wrap the operator and RHS parser + context( + "equality expression", // The context name for error messages + pair( + delimited( + multispace, + expect( + "'==' or '!='", + alt(( + // NOTE: We need to check that the immediate next character after + // the `==` is not `>`, otherwise, we'll `cut` into trying to + // parse it a normal expression while it should actually be + // parsed as implication (`==>`) + terminated(tag("=="), not(tag(">"))).map(|_| BinaryOp::Eq), + tag("!=").map(|_| BinaryOp::Neq), + )), + ), + multispace, + ), + cut(parse_or_expr), ), - parse_or_expr, - )), - ), - ) - .parse(input)?; + ), + ) + .parse(input)?; - Ok(( - input, - remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { - let op_kind = match op { - "==" => BinaryOp::Eq, - "!=" => BinaryOp::Neq, - _ => unreachable!(), - }; - OffsetExpr { + if let Some((op, expr_right)) = remainder { + expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), - } - }), - )) + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), + }; + input = next_input; + } else { + return Ok((input, expr_left)); + } + } } pub(crate) fn parse_or_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, (first, remainder)) = pair( - parse_and_expr, - context( + let (mut input, mut expr_left) = parse_and_expr(input)?; + loop { + let (next_input, remainder) = opt(context( "or", - many0(pair( - delimited(multispace, expect("'|'".to_string(), tag("|")), multispace), - parse_and_expr, - )), - ), - ) - .parse(input)?; + pair( + delimited(multispace, expect("'|'", tag("|").map(|_| BinaryOp::Or)), multispace), + cut(parse_and_expr), + ), + )) + .parse(input)?; - Ok(( - input, - remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { - let op_kind = match op { - "|" => BinaryOp::Or, - _ => unreachable!(), - }; - OffsetExpr { + if let Some((op, expr_right)) = remainder { + expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), - } - }), - )) + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), + }; + input = next_input; + } else { + return Ok((input, expr_left)); + } + } } pub(crate) fn parse_and_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, (first, remainder)) = pair( - parse_xor_expr, - context( + let (mut input, mut expr_left) = parse_xor_expr(input)?; + loop { + let (next_input, remainder) = opt(context( "and", - many0(pair( - delimited(multispace, expect("'&'".to_string(), tag("&")), multispace), - parse_xor_expr, - )), - ), - ) - .parse(input)?; + pair( + delimited(multispace, expect("'&'", tag("&").map(|_| BinaryOp::And)), multispace), + cut(parse_xor_expr), + ), + )) + .parse(input)?; - Ok(( - input, - remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { - let op_kind = match op { - "&" => BinaryOp::And, - _ => unreachable!(), - }; - OffsetExpr { + if let Some((op, expr_right)) = remainder { + expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), - } - }), - )) + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), + }; + input = next_input; + } else { + return Ok((input, expr_left)); + } + } } pub(crate) fn parse_xor_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, (first, remainder)) = pair( - parse_comparison_expr, - context( + let (mut input, mut expr_left) = parse_comparison_expr(input)?; + loop { + let (next_input, remainder) = opt(context( "xor", - many0(pair( - delimited(multispace, expect("'^'".to_string(), tag("^")), multispace), - parse_comparison_expr, - )), - ), - ) - .parse(input)?; + pair( + delimited(multispace, expect("'|'", tag("|").map(|_| BinaryOp::Or)), multispace), + cut(parse_comparison_expr), + ), + )) + .parse(input)?; - Ok(( - input, - remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { - let op_kind = match op { - "^" => BinaryOp::Xor, - _ => unreachable!(), - }; - OffsetExpr { + if let Some((op, expr_right)) = remainder { + expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), - } - }), - )) + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), + }; + input = next_input; + } else { + return Ok((input, expr_left)); + } + } } pub(crate) fn parse_comparison_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (i, mut expr_left) = parse_shift_expr(input)?; + let (input, mut expr_left) = parse_shift_expr(input)?; // Comparison operators are not associative (e.g., `a < b < c` is invalid), // so we just look for one optional occurrence. - if let (i, Some((op, expr_right))) = opt(pair( + if let (input, Some((op, expr_right))) = opt(pair( delimited( multispace, expect( "'<=', '>=', '<' or '>'".to_string(), - alt((tag("<="), tag(">="), tag("<"), tag(">"))), + alt(( + tag("<=").map(|_| BinaryOp::Le), + tag(">=").map(|_| BinaryOp::Ge), + tag("<").map(|_| BinaryOp::Lt), + tag(">").map(|_| BinaryOp::Gt), + )), ), multispace, ), // NOTE: If an operator was found, the RHS is now MANDATORY. `cut` enforces this. cut(parse_shift_expr), )) - .parse(i)? + .parse(input)? { - let op_kind = match op { - "<" => BinaryOp::Lt, - "<=" => BinaryOp::Le, - ">" => BinaryOp::Gt, - ">=" => BinaryOp::Ge, - _ => unreachable!(), - }; expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), }; - return Ok((i, expr_left)); + return Ok((input, expr_left)); } - Ok((i, expr_left)) + Ok((input, expr_left)) } pub(crate) fn parse_shift_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (i, mut expr_left) = parse_additive_expr(input)?; + let (input, mut expr_left) = parse_additive_expr(input)?; - if let (i, Some((op, expr_right))) = opt(pair( - delimited(multispace, expect("'<<' or '>>'", alt((tag("<<"), tag(">>")))), multispace), + if let (input, Some((op, expr_right))) = opt(pair( + delimited( + multispace, + expect( + "'<<' or '>>'", + alt(( + tag("<<").map(|_| BinaryOp::ShiftLeft), + tag(">>").map(|_| BinaryOp::ShiftRight), + )), + ), + multispace, + ), cut(parse_additive_expr), )) - .parse(i)? + .parse(input)? { - let op_kind = match op { - "<<" => BinaryOp::ShiftLeft, - ">>" => BinaryOp::ShiftRight, - _ => unreachable!(), - }; expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), }; - return Ok((i, expr_left)); + return Ok((input, expr_left)); } - Ok((i, expr_left)) + Ok((input, expr_left)) } pub(crate) fn parse_additive_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (mut i, mut expr_left) = parse_multiplicative_expr(input)?; + let (mut input, mut expr_left) = parse_multiplicative_expr(input)?; loop { - let (next_i, remainder) = opt(pair( - delimited(multispace, expect("'+' or '-'", alt((tag("+"), tag("-")))), multispace), + let (next_input, remainder) = opt(pair( + delimited( + multispace, + expect( + "'+' or '-'", + alt((tag("+").map(|_| BinaryOp::Add), tag("-").map(|_| BinaryOp::Sub))), + ), + multispace, + ), cut(parse_multiplicative_expr), )) - .parse(i)?; + .parse(input)?; if let Some((op, expr_right)) = remainder { - let op_kind = match op { - "+" => BinaryOp::Add, - "-" => BinaryOp::Sub, - _ => unreachable!(), - }; expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), }; - i = next_i; + input = next_input; } else { - return Ok((i, expr_left)); + return Ok((input, expr_left)); } } } pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, (first, remainder)) = pair( - parse_prefix_expr, - context( - "multiplicative", - many0(pair( - delimited( - multispace, - expect("'*', '/' or '%'".to_string(), alt((tag("*"), tag("/"), tag("%")))), - multispace, + let (mut input, mut expr_left) = parse_prefix_expr(input)?; + + loop { + let (next_input, remainder) = opt(pair( + delimited( + multispace, + expect( + "'*', '/', or '%'", + alt(( + tag("*").map(|_| BinaryOp::Mul), + tag("/").map(|_| BinaryOp::Div), + tag("%").map(|_| BinaryOp::Mod), + )), ), - parse_prefix_expr, - )), - ), - ) - .parse(input)?; + multispace, + ), + cut(parse_prefix_expr), + )) + .parse(input)?; - Ok(( - input, - remainder.into_iter().fold(first, |expr_left, (op, expr_right)| { - let op_kind = match op { - "*" => BinaryOp::Mul, - "/" => BinaryOp::Div, - "%" => BinaryOp::Mod, - _ => unreachable!(), - }; - OffsetExpr { + if let Some((op, expr_right)) = remainder { + expr_left = OffsetExpr { ann: build_offset_from_exprs(&expr_left, &expr_right), - expr: Box::new(ExprF::BinaryOp { op: op_kind, expr_left, expr_right }), - } - }), - )) + expr: Box::new(ExprF::BinaryOp { op, expr_left, expr_right }), + }; + input = next_input; + } else { + return Ok((input, expr_left)); + } + } +} + +pub(crate) enum Prefix { + Not, + // Dereference, } pub(crate) fn parse_prefix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let (input, prefixes) = context("prefix", many0(parse_any_prefix)).parse(input)?; + + let (input, base_expr) = parse_postfix_expr(input)?; + + let final_expr = prefixes.into_iter().rev().fold(base_expr, |inner_expr, prefix| { + // TODO: real span + let ann = inner_expr.ann; + let expr_f = match prefix { + Prefix::Not => ExprF::UnaryOp { op: UnaryOp::Not, expr: inner_expr }, + }; + OffsetExpr { ann, expr: Box::new(expr_f) } + }); + + Ok((input, final_expr)) +} + +pub(crate) fn parse_any_prefix<'a>(input: Input<'a>) -> PResult<'a, Prefix> { alt(( - context( - "prefix", - map( - preceded( - terminated(expect("'!'".to_string(), tag("!")), multispace), - parse_prefix_expr, - ), - |expr| OffsetExpr { - ann: expr.ann, - expr: Box::new(ExprF::UnaryOp { op: UnaryOp::Not, expr }), - }, - ), - ), - parse_postfix_expr, + // + context("not", terminated(expect("'!'", tag("!")), multispace).map(|_| Prefix::Not)), )) .parse(input) } @@ -454,55 +462,58 @@ pub(crate) enum CastTargetType { } pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - let (input, base_expr) = parse_atom_expr(input)?; - - let (input, suffixes) = many0(parse_any_suffix).parse(input)?; + let (mut input, mut expr_base) = parse_atom_expr(input)?; - let final_expr = suffixes.into_iter().try_fold(base_expr, |current_expr, suffix| { - // TODO: real span - let new_ann = current_expr.ann; - - Ok(match suffix { - Postfix::ArrayIndex(index_expr) => { - let ann = build_offset_from_exprs(¤t_expr, &index_expr); - OffsetExpr { - ann, - expr: Box::new(ExprF::Index { expr: current_expr, index: index_expr }), + loop { + // `cut` ensures that if we see a `.` or `[` but the rest is invalid, we get a hard error. + let (next_input, suffix) = cut(opt(parse_any_suffix)).parse(input)?; + + if let Some(s) = suffix { + expr_base = match s { + Postfix::ArrayIndex(index_expr) => { + let ann = build_offset_from_exprs(&expr_base, &index_expr); + OffsetExpr { + ann, + expr: Box::new(ExprF::Index { expr: expr_base, index: index_expr }), + } } - } - Postfix::TupleMember(index) => OffsetExpr { - ann: new_ann, - expr: Box::new(ExprF::TupleAccess { - expr: current_expr, - index: index.try_into().map_err(|_| { - NomErr::Error(Error { parser_errors: vec![], contexts: vec![] }) - })?, - }), - }, - Postfix::Cast(ident) => OffsetExpr { - ann: new_ann, - expr: Box::new(ExprF::Cast { - expr: current_expr, - target: match ident { - CastTargetType::Field => NoirType::Field, - CastTargetType::Integer(signedness, integer_bit_size) => { - NoirType::Integer(signedness, integer_bit_size) - } - CastTargetType::Bool => NoirType::Bool, - }, - }), - }, - }) - })?; - - Ok((input, final_expr)) + Postfix::TupleMember(index) => { + let index_u32 = index.try_into().map_err(|_| { + NomErr::Error(build_error(input, ParserErrorKind::InvalidTupleIndex)) + })?; + let ann = (expr_base.ann.0, (input.len() - next_input.len()) as u32); // Approximate span + OffsetExpr { + ann, + expr: Box::new(ExprF::TupleAccess { expr: expr_base, index: index_u32 }), + } + } + Postfix::Cast(target_type) => { + let ann = (expr_base.ann.0, (input.len() - next_input.len()) as u32); // Approximate span + OffsetExpr { + ann, + expr: Box::new(ExprF::Cast { + expr: expr_base, + target: match target_type { + CastTargetType::Field => NoirType::Field, + CastTargetType::Integer(s, b) => NoirType::Integer(s, b), + CastTargetType::Bool => NoirType::Bool, + }, + }), + } + } + }; + input = next_input; + } else { + return Ok((input, expr_base)); + } + } } pub(crate) fn parse_any_suffix<'a>(input: Input<'a>) -> PResult<'a, Postfix> { alt(( - map(parse_index_suffix, Postfix::ArrayIndex), - map(parse_member_suffix, Postfix::TupleMember), - map(parse_cast_suffix, Postfix::Cast), + context("index", map(parse_index_suffix, Postfix::ArrayIndex)), + context("member", map(parse_member_suffix, Postfix::TupleMember)), + context("cast", map(parse_cast_suffix, Postfix::Cast)), )) .parse(input) } @@ -525,9 +536,11 @@ pub(crate) fn parse_member_suffix<'a>(input: Input<'a>) -> PResult<'a, BigInt> { } pub(crate) fn parse_cast_suffix<'a>(input: Input<'a>) -> PResult<'a, CastTargetType> { - let (input, type_ident) = - preceded(delimited(multispace1, tag("as"), multispace1), cut(parse_identifier)) - .parse(input)?; + let (input, type_ident) = preceded( + expect("'as'", delimited(multispace1, tag("as"), multispace1)), + cut(parse_identifier), + ) + .parse(input)?; if type_ident == "Field" { return Ok((input, CastTargetType::Field)); @@ -567,30 +580,6 @@ pub(crate) fn parse_cast_suffix<'a>(input: Input<'a>) -> PResult<'a, CastTargetT Err(NomErr::Error(build_error(type_ident, ParserErrorKind::InvalidIntegerLiteral))) } -fn trace<'a, P, O: Debug>( - name: &'static str, - mut parser: P, -) -> impl FnMut(Input<'a>) -> IResult, O, Error> -where - P: Parser, Output = O, Error = Error>, -{ - move |input: Input<'a>| { - println!("[trace] Enter '{}' on input: {:?}", name, input); - let result = parser.parse(input); - match &result { - Ok((remaining, output)) => { - println!( - "[trace] Success on '{}'! Output: {:?}, Remaining: {:?}", - name, output, remaining - ); - } - Err(e) => { - println!("[trace] Fail on '{}'! Error: {:?}", name, e); - } - } - result - } -} pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { alt(( @@ -609,7 +598,7 @@ pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { delimited( expect("'('".to_string(), tag("(")), - parse_expression, + delimited(multispace, parse_expression, multispace), expect("')'".to_string(), tag(")")), ) .parse(input) @@ -624,13 +613,13 @@ pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE expect("'('".to_string(), tag("(")), cut(pair( delimited( - expect("'|'".to_string(), tag("|")), + expect("'|'".to_string(), pair(tag("|"), multispace)), // NOTE: unlike functions, quantifiers need to have at least one bound variable cut(separated_list1( delimited(multispace, expect("','".to_string(), tag(",")), multispace), parse_identifier, )), - expect("'|'".to_string(), tag("|")), + expect("'|'".to_string(), pair(multispace, tag("|"))), ), delimited(multispace, parse_expression, multispace), )), @@ -657,10 +646,10 @@ pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE } fn parse_quantifier_kind<'a>(input: Input<'a>) -> PResult<'a, Quantifier> { - let (i, ident) = parse_identifier(input)?; + let (input, ident) = parse_identifier(input)?; match ident { - "forall" => Ok((i, Quantifier::Forall)), - "exists" => Ok((i, Quantifier::Exists)), + "forall" => Ok((input, Quantifier::Forall)), + "exists" => Ok((input, Quantifier::Exists)), // NOTE: If the identifier is not a valid quantifier, fail with a specific error _ => Err(NomErr::Error(build_error( input, @@ -920,8 +909,8 @@ pub mod tests { let (input, expr) = parse(identche).unwrap(); assert_eq!(input, ""); // assert!(matches!(*expr.1.typ, TypX::Bool)); - let ExprF::Variable(Variable { name: i, .. }) = *expr.expr else { panic!() }; - assert_eq!(&i, identche); + let ExprF::Variable(Variable { name: input, .. }) = *expr.expr else { panic!() }; + assert_eq!(&input, identche); } #[test] @@ -991,6 +980,13 @@ pub mod tests { assert_eq!(expr.0, ""); } + #[test] + fn test_prefix() { + let expr = parse("(!1 + !2 * !x)").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + #[test] fn test_postfix() { let expr = parse("5 + ala.126[nica].012[1[3]][2].15[da] / 5").unwrap(); @@ -1032,6 +1028,9 @@ pub mod tests { state.functions, ) .unwrap(); + + let Attribute::Ensures(expr) = attribute else { panic!() }; + dbg!(strip_ann(expr)); } #[test] diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 2aee05f68a0..624de80fd2b 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -853,7 +853,29 @@ mod tests { #[test] fn test_cast() { - let annotation = "ensures((15 as i16 - 3 > 2) & ((result as Field - 6) as u64 == 1 + a as u64 >> kek as u8))"; + let annotation = "ensures((15 as i16 - 3 > 2) & ((result as Field - 6) as u64 == 1 + a as u64 >> 4 as u8))"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr)); + // assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); + } + + #[test] + fn test_double_cast() { + let annotation = "ensures(a == (a as i16) as i32)"; let state = empty_state(); let attribute = parse_attribute( annotation, From 8677995bf108d3dc484af8c15a1f3c1d8a1bb255 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 1 Aug 2025 12:03:07 +0300 Subject: [PATCH 33/86] chore(typing): Fetch min available local id We are now passing this value to `type_infer` which in return will attach ids to quantifier indices. Expanded the `State` structure and removed its `Clone` trait. --- compiler/formal_verification/src/lib.rs | 3 ++- compiler/formal_verification/src/parse.rs | 3 ++- compiler/formal_verification/src/typing.rs | 22 +++++++++++----------- compiler/fv_bridge/src/lib.rs | 9 +++++++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index dd33252b76f..58b15c9613b 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -14,11 +14,12 @@ pub mod ast; pub mod parse; pub mod typing; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct State<'a> { pub function: &'a mast::Function, pub global_constants: &'a BTreeMap, pub functions: &'a BTreeMap, + pub min_local_id: &'a mut u32, } #[derive(Debug, Clone)] diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index ecde070b9ea..c2030032424 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -823,7 +823,7 @@ pub mod tests { Visibility::Public, ), ( - LocalId(3), + LocalId(4), false, "user".to_string(), NoirType::Tuple(vec![NoirType::Bool, NoirType::Unit]), @@ -863,6 +863,7 @@ pub mod tests { .into_iter() .collect(), )), + min_local_id: Box::leak(Box::new(5)), } } diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 624de80fd2b..2e3a71423db 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -141,7 +141,7 @@ pub fn propagate_concrete_type( }) } -pub fn type_infer(state: State, expr: SpannedExpr) -> Result { +pub fn type_infer(state: &State, expr: SpannedExpr) -> Result { // NOTE: predicate, always bool, // assume subterms are `u32` (like `Noir` does) let default_literal_type = NoirType::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); @@ -642,7 +642,7 @@ mod tests { ) .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); assert!( cata(spanned_typed_expr, &|(_, expr_type), expr| { match expr { @@ -680,7 +680,7 @@ mod tests { ) .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); assert!( cata(spanned_typed_expr.clone(), &|(_, expr_type), expr| { match expr { @@ -746,7 +746,7 @@ mod tests { ) .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); dbg!(&spanned_typed_expr); assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } @@ -764,7 +764,7 @@ mod tests { ) .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let type_inference_error = type_infer(state, spanned_expr).unwrap_err(); + let type_inference_error = type_infer(&state, spanned_expr).unwrap_err(); let TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { expected_typ, expr_typ, @@ -794,7 +794,7 @@ mod tests { ) .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let type_inference_error = type_infer(state, spanned_expr).unwrap_err(); + let type_inference_error = type_infer(&state, spanned_expr).unwrap_err(); let TypeInferenceError::IntegerLiteralDoesNotFit { literal: _, literal_type, @@ -828,7 +828,7 @@ mod tests { ) .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); dbg!(&spanned_typed_expr); assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } @@ -846,7 +846,7 @@ mod tests { ) .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); dbg!(&spanned_typed_expr); assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } @@ -868,7 +868,7 @@ mod tests { .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); dbg!(&strip_ann(spanned_typed_expr)); // assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } @@ -890,7 +890,7 @@ mod tests { .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let spanned_typed_expr = type_infer(state, spanned_expr).unwrap(); + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); dbg!(&strip_ann(spanned_typed_expr)); // assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } @@ -908,7 +908,7 @@ mod tests { ) .unwrap(); let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; - let type_inference_error = type_infer(state, spanned_expr).unwrap_err(); + let type_inference_error = type_infer(&state, spanned_expr).unwrap_err(); let TypeInferenceError::MonomorphizationRequest(MonomorphizationRequest { function_identifier, param_types, diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index b0e9da7c7ed..a0c3922b873 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -12,6 +12,7 @@ use noirc_evaluator::{ errors::{RuntimeError, SsaReport}, vir::{create_verus_vir_with_ready_annotations, vir_gen::BuildingKrateError}, }; +use noirc_frontend::monomorphization::ast::LocalId; use noirc_frontend::{ debug::DebugInstrumenter, graph::CrateId, @@ -193,6 +194,9 @@ fn modified_monomorphize( let globals = monomorphizer.finished_globals.into_iter().collect::>(); + let mut min_available_id: u32 = + monomorphizer.locals.values().map(|LocalId(id)| *id).max().unwrap_or_default() + 1; + let fv_annotations: Vec<(FuncId, Vec)> = monomorphizer .finished_functions .iter() @@ -206,6 +210,7 @@ fn modified_monomorphize( function, global_constants: &globals, functions: &monomorphizer.finished_functions, + min_local_id: &mut min_available_id }; let attributes = monomorphizer @@ -237,13 +242,13 @@ fn modified_monomorphize( formal_verification::Attribute::Ghost => TypedAttribute::Ghost, formal_verification::Attribute::Ensures(expr) => { // TODO(totel): Handle MonomorphRequest error type - let typed_expr = type_infer(state.clone(), expr) + let typed_expr = type_infer(&state, expr) .map_err(|e| MonomorphizationErrorBundle::from(e))?; TypedAttribute::Ensures(typed_expr) } formal_verification::Attribute::Requires(expr) => { // TODO(totel): Handle MonomorphRequest error type - let typed_expr = type_infer(state.clone(), expr) + let typed_expr = type_infer(&state, expr) .map_err(|e| MonomorphizationErrorBundle::from(e))?; TypedAttribute::Requires(typed_expr) } From dc65a398dfc8259d6a60562e3d340d90feaafd2c Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 34/86] fix(annotations): parse `^` correctly (again) - Broken during the last refactor, due to a copy-paste mishap, oops --- compiler/formal_verification/src/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index c2030032424..f9ce38c0d11 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -277,7 +277,7 @@ pub(crate) fn parse_xor_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let (next_input, remainder) = opt(context( "xor", pair( - delimited(multispace, expect("'|'", tag("|").map(|_| BinaryOp::Or)), multispace), + delimited(multispace, expect("'^'", tag("^").map(|_| BinaryOp::Xor)), multispace), cut(parse_comparison_expr), ), )) From 4065cc60b1ac03ea3979f135ddde660b4efad9c2 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 1 Aug 2025 17:08:58 +0300 Subject: [PATCH 35/86] fix(tag_attr): Keep whitespace tokens when parsing --- compiler/noirc_frontend/src/lexer/lexer.rs | 4 ++++ compiler/noirc_frontend/src/parser/parser.rs | 13 +++++++++++++ .../noirc_frontend/src/parser/parser/attributes.rs | 8 ++++++++ 3 files changed, 25 insertions(+) diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index cab99e5514d..1dcb2af5840 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -73,6 +73,10 @@ impl<'a> Lexer<'a> { self } + pub fn set_skip_whitespaces_flag(&mut self, flag: bool) { + self.skip_whitespaces = flag; + } + /// Iterates the cursor and returns the char at the new cursor position fn next_char(&mut self) -> Option { let (position, ch) = self.chars.next()?; diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index e97f197afe0..1a1d4708830 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -475,6 +475,19 @@ impl<'a> Parser<'a> { self.at(Token::Keyword(keyword)) } + fn at_whitespace(&self) -> bool { + matches!(self.token.token(), Token::Whitespace(_)) + } + + fn set_lexer_skip_whitespaces_flag(&mut self, flag: bool) { + match &mut self.tokens { + TokenStream::Lexer(lexer) => { + lexer.set_skip_whitespaces_flag(flag); + } + _ => {} + }; + } + fn next_is(&self, token: Token) -> bool { self.next_token.token() == &token } diff --git a/compiler/noirc_frontend/src/parser/parser/attributes.rs b/compiler/noirc_frontend/src/parser/parser/attributes.rs index 2166aa128fb..233140a0527 100644 --- a/compiler/noirc_frontend/src/parser/parser/attributes.rs +++ b/compiler/noirc_frontend/src/parser/parser/attributes.rs @@ -7,6 +7,7 @@ use crate::lexer::fv_attributes::{ }; use crate::parser::ParserErrorReason; use crate::parser::labels::ParsingRuleLabel; +use crate::parser::parser::TokenStream; use crate::token::{ Attribute, FunctionAttribute, FunctionAttributeKind, FuzzingScope, MetaAttribute, MetaAttributeName, SecondaryAttribute, SecondaryAttributeKind, TestScope, Token, @@ -114,6 +115,8 @@ impl Parser<'_> { let mut brackets_count = 1; // 1 because of the starting `#[` + self.set_lexer_skip_whitespaces_flag(false); + while !self.at_eof() { if self.at(Token::LeftBracket) { brackets_count += 1; @@ -129,6 +132,11 @@ impl Parser<'_> { self.bump(); } + self.set_lexer_skip_whitespaces_flag(true); + while self.at_whitespace() { + self.bump(); + } + let location = self.location_since(start_location); let kind = SecondaryAttributeKind::Tag(contents); let attr = SecondaryAttribute { kind, location }; From e440c212477b6468dd347ead252e78dcbfd6cbe5 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 36/86] feat(annotations): propagate `Location`s through parser errors - Make new `ParserErrorWithLocation` type - Change the return type of `parse_attribute` to return a `Vec` of the new `ParserErrorWithLocation` instead of the old `ParserError` --- compiler/formal_verification/src/parse.rs | 52 ++++++++++++------- .../formal_verification/src/parse/errors.rs | 31 +++++------ compiler/fv_bridge/src/errors.rs | 6 +-- compiler/fv_bridge/src/lib.rs | 2 +- 4 files changed, 54 insertions(+), 37 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index f9ce38c0d11..4e7593e1710 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -18,11 +18,13 @@ use num_bigint::{BigInt, BigUint, Sign}; use std::collections::BTreeMap; pub mod errors; -use errors::{Error, ParserErrorKind, build_error, expect, map_nom_err}; +use errors::{ + Error, ParserError, ParserErrorKind, ParserErrorWithLocation, build_error, expect, map_nom_err, +}; use crate::{ Attribute, - ast::{BinaryOp, ExprF, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, + ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, span_expr, }; @@ -68,7 +70,7 @@ pub fn parse_attribute<'a>( _function: &'a mast::Function, _global_constants: &'a BTreeMap, _functions: &'a BTreeMap, -) -> Result { +) -> Result, Vec)> { // NOTE: #['...] // ^^^^^^^ - received `Location` // ^^^ - relevant stuff @@ -78,29 +80,43 @@ pub fn parse_attribute<'a>( ..location }; + let locate_parser_error = |parser_error: ParserError| ParserErrorWithLocation { + location: build_location( + location, + annotation.len() as u32, + parser_error.offset, + parser_error.offset + 1, + ), + kind: parser_error.kind.clone(), + }; + + let convert_nom_error = |nom_err: Error| -> (Vec, Vec) { + (nom_err.parser_errors.into_iter().map(locate_parser_error).collect(), nom_err.contexts) + }; + let (input, ident) = match expect("attribute name", parse_identifier)(annotation) { Ok(result) => result, Err(nom_err) => { - return match nom_err { - NomErr::Error(e) | NomErr::Failure(e) => Err(e), - NomErr::Incomplete(_) => Err(build_error( + return Err(convert_nom_error(match nom_err { + NomErr::Error(e) | NomErr::Failure(e) => e, + NomErr::Incomplete(_) => build_error( annotation, ParserErrorKind::Message("Incomplete input".to_string()), - )), - }; + ), + })); } }; match ident { "ghost" => { if !input.is_empty() { - return Err(build_error( + return Err(convert_nom_error(build_error( input, ParserErrorKind::Message(format!( "Unexpected input after 'ghost' attribute: '{}'", input )), - )); + ))); } Ok(Attribute::Ghost) } @@ -114,13 +130,13 @@ pub fn parse_attribute<'a>( match expr_parser.parse(input) { Ok((rest, expr)) => { if !rest.is_empty() { - return Err(build_error( + return Err(convert_nom_error(build_error( rest, ParserErrorKind::Message(format!( "Unexpected trailing input: '{}'", rest )), - )); + ))); } let spanned_expr = span_expr(location, annotation.len() as u32, expr); if ident == "ensures" { @@ -130,17 +146,17 @@ pub fn parse_attribute<'a>( } } Err(nom_err) => match nom_err { - NomErr::Error(e) | NomErr::Failure(e) => Err(e), - NomErr::Incomplete(_) => Err(build_error( + NomErr::Error(e) | NomErr::Failure(e) => Err(convert_nom_error(e)), + NomErr::Incomplete(_) => Err(convert_nom_error(build_error( input, ParserErrorKind::Message("Incomplete input".to_string()), - )), + ))), }, } } unknown => { let err_kind = ParserErrorKind::UnknownAttribute(unknown.to_string()); - Err(build_error(annotation, err_kind)) + Err(convert_nom_error(build_error(annotation, err_kind))) } } } @@ -1071,7 +1087,7 @@ pub mod tests { let err = result.unwrap_err(); - let first_error = err.parser_errors.get(0).expect("Should have one parser error"); + let first_error = err.0.get(0).expect("Should have one parser error"); assert!(matches!(&first_error.kind, ParserErrorKind::Expected { expected, found } if expected == "an identifier" && found.starts_with("'invalid") )); @@ -1094,7 +1110,7 @@ pub mod tests { let err = result.unwrap_err(); - let first_error = err.parser_errors.get(0).expect("Should have one parser error"); + let first_error = err.0.get(0).expect("Should have one parser error"); assert!(matches!(&first_error.kind, ParserErrorKind::UnknownAttribute(attr) if attr == "banica" )); diff --git a/compiler/formal_verification/src/parse/errors.rs b/compiler/formal_verification/src/parse/errors.rs index c591a920333..3f3ed24feec 100644 --- a/compiler/formal_verification/src/parse/errors.rs +++ b/compiler/formal_verification/src/parse/errors.rs @@ -7,10 +7,16 @@ use std::fmt::Debug; use super::Input; -// An individual, specific error that occurred. #[derive(Debug, Clone)] pub struct ParserError { - pub span: Span, + /// Offset from the end of the annotation + pub offset: u32, + pub kind: ParserErrorKind, +} + +#[derive(Debug, Clone)] +pub struct ParserErrorWithLocation { + pub location: Location, pub kind: ParserErrorKind, } @@ -44,16 +50,14 @@ pub struct Error { pub contexts: Vec, } -// Helper to get a Span from a nom Input -pub fn input_to_span(i: Input) -> Span { - let offset = i.as_ptr() as usize - i.as_bytes().as_ptr() as usize; - Span::single_char(offset as u32) +pub fn input_to_offset(i: Input) -> u32 { + i.len() as u32 } /// Builds and returns our custom Error struct directly. pub fn build_error(input: Input, kind: ParserErrorKind) -> Error { Error { - parser_errors: vec![ParserError { span: input_to_span(input), kind }], + parser_errors: vec![ParserError { offset: input_to_offset(input), kind }], contexts: vec![], } } @@ -77,7 +81,7 @@ where { map_nom_err(parser, move |fail_input| ParserErrorKind::Expected { expected: expected_msg.as_ref().to_string(), - found: get_found_token(fail_input), + found: fail_input.to_string(), // get_found_token(fail_input), }) } @@ -114,7 +118,7 @@ impl<'a> ParseError> for Error { // kind, input // ); let err = ParserError { - span: input_to_span(input), + offset: input_to_offset(input), // Create a generic message from the nom ErrorKind. kind: ParserErrorKind::Message(format!("nom primitive failed: {:?}", kind)), }; @@ -139,11 +143,8 @@ impl<'a> ContextError> for Error { } } -impl From for CustomDiagnostic { - fn from(value: ParserError) -> Self { - // TODO(totel): Get proper location - let location = Location::dummy(); - +impl From for CustomDiagnostic { + fn from(value: ParserErrorWithLocation) -> Self { let primary_message = match value.kind { ParserErrorKind::Expected { expected, found } => { format!("Expected {} but found {}", expected, found) @@ -156,6 +157,6 @@ impl From for CustomDiagnostic { ParserErrorKind::Message(msg) => msg, }; - CustomDiagnostic::simple_error(primary_message, String::new(), location) + CustomDiagnostic::simple_error(primary_message, String::new(), value.location) } } diff --git a/compiler/fv_bridge/src/errors.rs b/compiler/fv_bridge/src/errors.rs index 4f8a75597ee..858d6b13af3 100644 --- a/compiler/fv_bridge/src/errors.rs +++ b/compiler/fv_bridge/src/errors.rs @@ -1,6 +1,6 @@ use fm::FileId; use formal_verification::{ - parse::errors::{self, ParserError, ParserErrorKind}, + parse::errors::{self, ParserError, ParserErrorKind, ParserErrorWithLocation}, typing::TypeInferenceError, }; use noirc_driver::CompileError; @@ -17,14 +17,14 @@ pub(crate) enum MonomorphizationErrorBundle { MonomorphizationError(MonomorphizationError), ResolverErrors(Vec), TypeError(TypeCheckError), - ParserErrors(Vec), + ParserErrors(Vec), } pub(crate) enum CompilationErrorBundle { CompileError(CompileError), ResolverErrors(Vec), TypeError(TypeCheckError), - ParserErrors(Vec), + ParserErrors(Vec), } impl From for MonomorphizationErrorBundle { diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index a0c3922b873..4352bbc9d46 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -235,7 +235,7 @@ fn modified_monomorphize( &globals, &monomorphizer.finished_functions, ) - .map_err(|e| MonomorphizationErrorBundle::ParserErrors(e.parser_errors))?; + .map_err(|e| MonomorphizationErrorBundle::ParserErrors(e.0))?; // Step 2: Type-infer the parsed attribute expression. let typed_attribute = match parsed_attribute { From 71cfa1ec9d22d4da2261e3ab4022748f4e1e3ee4 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 1 Aug 2025 17:22:23 +0300 Subject: [PATCH 37/86] chore(annotations): Reverse order of annotation parsing --- compiler/fv_bridge/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 4352bbc9d46..c3ba98289bd 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -200,6 +200,7 @@ fn modified_monomorphize( let fv_annotations: Vec<(FuncId, Vec)> = monomorphizer .finished_functions .iter() + .rev() // Find the original function ID for each new monomorphized function. .filter_map(|(new_func_id, function)| { new_ids_to_old_ids.get(new_func_id).map(|old_id| (*new_func_id, *old_id, function)) From 4f80d89eedb7d79daf354d2c6a68c6d1a05093e9 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 38/86] feat(annotations): add support for paths in variables - Currently also plaguing the variables bound by quantifiers, the API should be cleaned up - Clean some up unused imports TODO: - Adequate type inference (no module paths in the Mon. AST) --- compiler/formal_verification/src/ast.rs | 38 ++++++++-------- compiler/formal_verification/src/lib.rs | 6 +-- compiler/formal_verification/src/parse.rs | 46 +++++++++++++++----- compiler/formal_verification/src/typing.rs | 38 +++++++++++++--- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 2 +- 5 files changed, 90 insertions(+), 40 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 39957cf7880..82a0bf5dce6 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -3,7 +3,6 @@ use std::fmt::{Debug, Display}; use noirc_errors::Location; use noirc_frontend::monomorphization::ast::Type as NoirType; use num_bigint::BigInt; -use serde::{Deserialize, Serialize}; pub type Identifier = String; @@ -132,6 +131,7 @@ impl BinaryOp { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Variable { + pub path: Vec, pub name: Identifier, pub id: Option, } @@ -156,14 +156,12 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { ExprF::Index { expr: indexee, index } => { ExprF::Index { expr: cata_fn(indexee), index: cata_fn(index) } } - ExprF::TupleAccess { expr, index } => { - ExprF::TupleAccess { expr: cata_fn(expr), index } - } - ExprF::Cast { expr, target } => { - ExprF::Cast { expr: cata_fn(expr), target } - } + ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr), index }, + ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr), target }, ExprF::Literal { value } => ExprF::Literal { value }, - ExprF::Variable(Variable { name, id }) => ExprF::Variable(Variable { name, id }), + ExprF::Variable(Variable { path, name, id }) => { + ExprF::Variable(Variable { path, name, id }) + } } } @@ -184,14 +182,12 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res ExprF::Index { expr: indexee, index } => { ExprF::Index { expr: cata_fn(indexee)?, index: cata_fn(index)? } } - ExprF::TupleAccess { expr, index } => { - ExprF::TupleAccess { expr: cata_fn(expr)?, index } - } - ExprF::Cast { expr, target } => { - ExprF::Cast { expr: cata_fn(expr)?, target } - } + ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr)?, index }, + ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr)?, target }, ExprF::Literal { value } => ExprF::Literal { value }, - ExprF::Variable(Variable { name, id }) => ExprF::Variable(Variable { name, id }), + ExprF::Variable(Variable { path, name, id }) => { + ExprF::Variable(Variable { path, name, id }) + } }) } @@ -252,9 +248,13 @@ pub fn try_cata_recoverable( impl Display for Quantifier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", match self { - Quantifier::Forall => "forall", - Quantifier::Exists => "exists", - } ) + write!( + f, + "{}", + match self { + Quantifier::Forall => "forall", + Quantifier::Exists => "exists", + } + ) } } diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 58b15c9613b..66a2aebbff5 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -1,12 +1,10 @@ -use noirc_errors::{Location, Span}; +use noirc_errors::Location; use noirc_frontend::monomorphization::ast as mast; -use nom::Finish; use std::{collections::BTreeMap, fmt::Debug}; -use vir::messages::Span as VirSpan; use crate::{ ast::{OffsetExpr, SpannedExpr, cata}, - parse::{errors::Error as ParseError, build_location, parse_expression, parse_identifier}, + parse::build_location, }; // NOTE: all types inside are not prefixed, to be used as `ast::OffsetExpr` diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 4e7593e1710..ea868bb1511 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -24,7 +24,7 @@ use errors::{ use crate::{ Attribute, - ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, + ast::{BinaryOp, ExprF, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, span_expr, }; @@ -653,7 +653,7 @@ pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetE quantifier, variables: variables .into_iter() - .map(|name| Variable { name: name.to_string(), id: None }) + .map(|name| Variable { path: vec![], name: name.to_string(), id: None }) .collect(), expr, }, @@ -752,27 +752,44 @@ pub(crate) fn parse_sign<'a>(input: Input<'a>) -> PResult<'a, bool> { const FORBIDDEN_IDENTIFIERS: &[&str] = &["forall", "exists"]; -// TODO: parse module references `fv_std::SOMETHING` pub(crate) fn parse_var_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let prev_offset = input.len(); - let (input, ident) = parse_identifier(input)?; + // Wrapper of `parse_identifier` that denies some identifiers + let path_segment_parser = |input: Input<'a>| -> PResult<'a, &'a str> { + let (input, ident) = parse_identifier(input)?; + + if FORBIDDEN_IDENTIFIERS.contains(&ident) { + return Err(NomErr::Error(build_error( + input, + ParserErrorKind::Message(format!( + "The keyword `{}` cannot be used as an identifier", + ident + )), + ))); + } + + Ok((input, ident)) + }; + + let (input, all_path_segments) = + separated_list1(expect("'::'", tag("::")), path_segment_parser).parse(input)?; let after_offset = input.len(); - if FORBIDDEN_IDENTIFIERS.contains(&ident) { - return Err(NomErr::Error(build_error( - input, - ParserErrorKind::InvalidIdentifier(ident.to_string()), - ))); - } + let (&name, path) = + all_path_segments.split_last().expect("`separated_list1` should return a non-empty `Vec`"); Ok(( input, build_expr( prev_offset, after_offset, - ExprF::Variable(Variable { name: ident.to_string(), id: None }), + ExprF::Variable(Variable { + path: path.into_iter().map(|&s| s.to_string()).collect(), + name: name.to_string(), + id: None, + }), ), )) } @@ -960,6 +977,13 @@ pub mod tests { ); } + #[test] + fn test_identifier_with_path() { + let expr = parse("f(alo::da::kek + !2 ^ bani::c::a.0)").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + #[test] fn test_sum() { let identche = "1 + 2 * 3"; diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 2e3a71423db..960df78a59e 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -141,7 +141,10 @@ pub fn propagate_concrete_type( }) } -pub fn type_infer(state: &State, expr: SpannedExpr) -> Result { +pub fn type_infer( + state: &State, + expr: SpannedExpr, +) -> Result { // NOTE: predicate, always bool, // assume subterms are `u32` (like `Noir` does) let default_literal_type = NoirType::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); @@ -153,8 +156,14 @@ pub fn type_infer(state: &State, expr: SpannedExpr) -> Result Result { + ExprF::Variable(Variable { path, name, id }) => { + // TODO: we do not support type inferrence of variables with paths + debug_assert_eq!(path.len(), 0); // NOTE: parsing should not yield `id`s debug_assert_eq!(*id, None); let (variable_ident, variable_id, variable_type): ( @@ -203,6 +214,7 @@ pub fn type_infer(state: &State, expr: SpannedExpr) -> Result Result (exprf, Some(NoirType::Bool)), + ExprF::Quantified { variables, .. } => { + variables.iter().map(|Variable { path, .. }| { + if !path.is_empty() { + Err(TypeInferenceError::NoirTypeError( + // TODO(totel): better error? + TypeCheckError::ParameterCountMismatch { + expected: 0, + found: path.len(), + location, + }, + )) + } else { + Ok(()) + } + }).collect::, _>>()?; + (exprf, Some(NoirType::Bool)) + } ExprF::Parenthesised { expr } => (exprf.clone(), expr.ann.1.clone()), ExprF::UnaryOp { op: _, expr } => (exprf.clone(), expr.ann.1.clone()), ExprF::BinaryOp { op, expr_left, expr_right } => { diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index c08818189e8..2ec178f5434 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -87,7 +87,7 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> }; make_expr(exprx, ast_type_to_vir_type(&typ), span_msg) } - ExprF::Variable(Variable { name, id }) => { + ExprF::Variable(Variable { name, id, path: _ }) => { let is_global = state.global_constants.iter().any(|(_, (gn, _, _))| *gn == name); let span_msg = format!("Var {}", name); From b2deb7eff273c321b4d25f1f322532dcf6947cc3 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 39/86] refactor(annotations): Error type specification location for `fv_bridge` Go from using `Ok::<_, MonomorphizationErrorBundle>` as the return value, to using `Result<_, MonomorphizationErrorBundle>` as the return type --- compiler/fv_bridge/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index c3ba98289bd..83684cfa6a7 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -205,7 +205,7 @@ fn modified_monomorphize( .filter_map(|(new_func_id, function)| { new_ids_to_old_ids.get(new_func_id).map(|old_id| (*new_func_id, *old_id, function)) }) - .map(|(new_func_id, old_id, function)| { + .map(|(new_func_id, old_id, function)| -> Result<_, MonomorphizationErrorBundle> { // Create the state once per function to avoid repeated lookups inside a loop. let state = State { function, @@ -227,7 +227,7 @@ fn modified_monomorphize( None } }) - .map(|(annotation_body, location)| { + .map(|(annotation_body, location)| -> Result<_, MonomorphizationErrorBundle> { // Step 1: Parse the attribute string. let parsed_attribute = parse_attribute( annotation_body, @@ -256,14 +256,14 @@ fn modified_monomorphize( }; // Step 3: Convert the typed attribute into its final representation. - Ok::<_, MonomorphizationErrorBundle>(convert_typed_attribute_to_vir_attribute( + Ok(convert_typed_attribute_to_vir_attribute( typed_attribute, &state, )) }) .collect::, _>>()?; - Ok::<_, MonomorphizationErrorBundle>((new_func_id, attributes)) + Ok((new_func_id, attributes)) }) .collect::, _>>()?; From a4ea5986648f9ef62ce6427b509ed21fa797290e Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 40/86] chore(annotations): format --- compiler/formal_verification/src/typing.rs | 31 ++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 960df78a59e..a2dd16a0480 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -239,20 +239,23 @@ pub fn type_infer( (exprf, Some(return_type.clone())) } ExprF::Quantified { variables, .. } => { - variables.iter().map(|Variable { path, .. }| { - if !path.is_empty() { - Err(TypeInferenceError::NoirTypeError( - // TODO(totel): better error? - TypeCheckError::ParameterCountMismatch { - expected: 0, - found: path.len(), - location, - }, - )) - } else { - Ok(()) - } - }).collect::, _>>()?; + variables + .iter() + .map(|Variable { path, .. }| { + if !path.is_empty() { + Err(TypeInferenceError::NoirTypeError( + // TODO(totel): better error? + TypeCheckError::ParameterCountMismatch { + expected: 0, + found: path.len(), + location, + }, + )) + } else { + Ok(()) + } + }) + .collect::, _>>()?; (exprf, Some(NoirType::Bool)) } ExprF::Parenthesised { expr } => (exprf.clone(), expr.ann.1.clone()), From 6c9d172688d880e12e9c28a0f1e0416d3adb18b1 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 41/86] feat(annotations): parse and type-infer derefences - Add test for it - Small diff due to the recent refactor - Add `todo!()` for the conversion to VIR --- compiler/formal_verification/src/ast.rs | 3 ++ compiler/formal_verification/src/parse.rs | 17 +++++++- compiler/formal_verification/src/typing.rs | 41 +++++++++++++++++++- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 6 ++- 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 82a0bf5dce6..49d8e1e3485 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -53,6 +53,9 @@ pub enum Quantifier { #[derive(Clone, Debug, PartialEq, Eq)] pub enum UnaryOp { + // neither + Dereference, + // Arithmetic and Boolean Not, } diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index ea868bb1511..2a34514de03 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -436,8 +436,8 @@ pub(crate) fn parse_multiplicative_expr<'a>(input: Input<'a>) -> PResult<'a, Off } pub(crate) enum Prefix { + Dereference, Not, - // Dereference, } pub(crate) fn parse_prefix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { @@ -449,6 +449,7 @@ pub(crate) fn parse_prefix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> // TODO: real span let ann = inner_expr.ann; let expr_f = match prefix { + Prefix::Dereference => ExprF::UnaryOp { op: UnaryOp::Dereference, expr: inner_expr }, Prefix::Not => ExprF::UnaryOp { op: UnaryOp::Not, expr: inner_expr }, }; OffsetExpr { ann, expr: Box::new(expr_f) } @@ -460,6 +461,10 @@ pub(crate) fn parse_prefix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> pub(crate) fn parse_any_prefix<'a>(input: Input<'a>) -> PResult<'a, Prefix> { alt(( // + context( + "dereference", + terminated(expect("'*'", tag("*")), multispace).map(|_| Prefix::Dereference), + ), context("not", terminated(expect("'!'", tag("!")), multispace).map(|_| Prefix::Not)), )) .parse(input) @@ -855,6 +860,16 @@ pub mod tests { NoirType::Array(3, Box::new(NoirType::Field)), Visibility::Public, ), + ( + LocalId(3), + false, + "rxs".to_string(), + NoirType::Reference( + Box::new(NoirType::Array(3, Box::new(NoirType::Field))), + false, + ), + Visibility::Public, + ), ( LocalId(4), false, diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index a2dd16a0480..308bd5347c4 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -2,7 +2,7 @@ use crate::{ MonomorphizationRequest, State, ast::{ BinaryOp, ExprF, Literal, SpannedExpr, SpannedOptionallyTypedExpr, SpannedTypedExpr, - Variable, strip_ann, try_cata, try_contextual_cata, + UnaryOp, Variable, strip_ann, try_cata, try_contextual_cata, }, }; use noirc_errors::Location; @@ -259,7 +259,26 @@ pub fn type_infer( (exprf, Some(NoirType::Bool)) } ExprF::Parenthesised { expr } => (exprf.clone(), expr.ann.1.clone()), - ExprF::UnaryOp { op: _, expr } => (exprf.clone(), expr.ann.1.clone()), + ExprF::UnaryOp { op, expr } => { + let t = match op { + UnaryOp::Dereference => { + match &expr.ann.1 { + Some(NoirType::Reference(t, _)) => Some(*t.clone()), + // TODO(totel): better error? + Some(_) | None => { + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::UnconstrainedReferenceToConstrained { + location, + }, + )); + } + } + } + UnaryOp::Not => expr.ann.1.clone(), + }; + + (exprf.clone(), t) + } ExprF::BinaryOp { op, expr_left, expr_right } => { match op { BinaryOp::ShiftLeft | BinaryOp::ShiftRight => { @@ -782,6 +801,24 @@ mod tests { assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } + #[test] + fn test_dereference_index() { + let attribute = "ensures((*rxs)[1 + 1] > 5)"; + let state = empty_state(); + let attribute = parse_attribute( + attribute, + Location { span: Span::inclusive(0, attribute.len() as u32), file: Default::default() }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&spanned_typed_expr); + assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); + } + #[test] fn test_operators_mixed_types() { let attribute = "ensures(1 + true)"; diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index 2ec178f5434..360a6fb9614 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -65,7 +65,7 @@ pub(crate) fn convert_typed_attribute_to_vir_attribute( pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> Expr { let mode = Mode::Spec; - cata(ann_expr, &|(loc, typ), expr| { + cata(ann_expr, &|(loc, typ), expr| -> Arc> { // Helper to construct a standard SpannedTyped expression. // It captures `loc` from the outer scope. let make_expr = |exprx: ExprX, vir_type: Typ, span_msg: String| -> Expr { @@ -196,6 +196,10 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> ) } } + formal_verification::ast::UnaryOp::Dereference => { + // TODO(totel): conversion of cast expressions + todo!() + } }; make_expr(exprx, ast_type_to_vir_type(&typ), span_msg) } From 0f25a8ffc2fac492b39cd854bad85848a1fde6ba Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 42/86] chore(annotations): use field punning again after renames --- compiler/formal_verification/src/ast.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 49d8e1e3485..135a4728269 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -156,8 +156,8 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { ExprF::FnCall { name, args } => { ExprF::FnCall { name, args: args.into_iter().map(cata_fn).collect() } } - ExprF::Index { expr: indexee, index } => { - ExprF::Index { expr: cata_fn(indexee), index: cata_fn(index) } + ExprF::Index { expr, index } => { + ExprF::Index { expr: cata_fn(expr), index: cata_fn(index) } } ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr), index }, ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr), target }, @@ -182,8 +182,8 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res let processed_args = args.into_iter().map(cata_fn).collect::, _>>()?; ExprF::FnCall { name, args: processed_args } } - ExprF::Index { expr: indexee, index } => { - ExprF::Index { expr: cata_fn(indexee)?, index: cata_fn(index)? } + ExprF::Index { expr, index } => { + ExprF::Index { expr: cata_fn(expr)?, index: cata_fn(index)? } } ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr)?, index }, ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr)?, target }, From 19f5f802816f91df42a0c406bd5a98aae79c933e Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 43/86] feat(annotations): add tuple literals - Tests for parsing and type inferrence - Leave some `WARN` and `TODO`s --- compiler/formal_verification/src/ast.rs | 7 +++ compiler/formal_verification/src/parse.rs | 54 +++++++++++++++++--- compiler/formal_verification/src/typing.rs | 52 ++++++++++++++++++- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 4 ++ 4 files changed, 109 insertions(+), 8 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 135a4728269..765167b5250 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -17,6 +17,7 @@ pub enum ExprF { TupleAccess { expr: R, index: u32 }, Cast { expr: R, target: NoirType }, Literal { value: Literal }, + Tuple { exprs: Vec }, Variable(Variable), } @@ -162,6 +163,9 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr), index }, ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr), target }, ExprF::Literal { value } => ExprF::Literal { value }, + ExprF::Tuple { exprs } => { + ExprF::Tuple { exprs: exprs.into_iter().map(cata_fn).collect::>() } + } ExprF::Variable(Variable { path, name, id }) => { ExprF::Variable(Variable { path, name, id }) } @@ -188,6 +192,9 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr)?, index }, ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr)?, target }, ExprF::Literal { value } => ExprF::Literal { value }, + ExprF::Tuple { exprs } => { + ExprF::Tuple { exprs: exprs.into_iter().map(cata_fn).collect::, _>>()? } + } ExprF::Variable(Variable { path, name, id }) => { ExprF::Variable(Variable { path, name, id }) } diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 2a34514de03..9d3178d8901 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -604,7 +604,7 @@ pub(crate) fn parse_cast_suffix<'a>(input: Input<'a>) -> PResult<'a, CastTargetT pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { alt(( - context("parenthesised", parse_parenthesised_expr), + context("parenthesised or tuple", parse_parenthesised_or_tuple_expr), context("quantifier", parse_quantifier_expr), // context("path", parse_path_expr), context("fn_call", parse_fn_call_expr), @@ -616,13 +616,46 @@ pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { .parse(input) } -pub(crate) fn parse_parenthesised_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - delimited( - expect("'('".to_string(), tag("(")), - delimited(multispace, parse_expression, multispace), - expect("')'".to_string(), tag(")")), +pub(crate) fn parse_parenthesised_or_tuple_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + + // NOTE: Both parenthesised and tuple expressions have a starting `(` + let (input, (exprs, trailing_comma)) = delimited( + expect("(", tag("(")), + // NOTE: Inside, we have a list of 0 or more expressions separated by commas... + pair( + separated_list0( + delimited(multispace, expect(",", tag(",")), multispace), + parse_expression, + ), + // NOTE: ...with an optional trailing comma + opt(delimited(multispace, tag(","), multispace)), + ), + cut(expect(")", tag(")"))), ) - .parse(input) + .parse(input)?; + + let after_offset = input.len(); + + let result_expr = if let Some((only, [])) = exprs.split_first() + && trailing_comma.is_none() + { + // NOTE: + // Special case: exactly one expression and NO comma + // This is a parenthesized expression + build_expr(prev_offset, after_offset, ExprF::Parenthesised { expr: only.clone() }) + } else { + // NOTE: + // All other cases are tuples: + // - `()` -> 0 expressions + // - `(1,)` -> 1 expression with a trailing comma + // - `(1, 2)` -> 2 expressions + // TODO: + // empty tuple vs unit? + build_expr(prev_offset, after_offset, ExprF::Tuple { exprs }) + }; + + Ok((input, result_expr)) } pub(crate) fn parse_quantifier_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { @@ -1050,6 +1083,13 @@ pub mod tests { assert_eq!(expr.0, ""); } + #[test] + fn test_tuple() { + let expr = parse("(1, kek, true).2").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + #[test] fn test_ghost() { let annotation = "ghost"; diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 308bd5347c4..9991d0a6ba3 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -89,6 +89,8 @@ pub fn bi_can_fit_in(bi: &BigInt, hole_size: &IntegerBitSize, hole_sign: &Signed return FitsIn::Yes; } +// WARN: will (possibly) incorrectly propagate types in tuple literals +// `(1000, 5, 1000).1 == (5 as u8)` pub fn propagate_concrete_type( e: SpannedOptionallyTypedExpr, t: NoirType, @@ -521,7 +523,12 @@ pub fn type_infer( } ExprF::TupleAccess { expr, index } => { let Some(NoirType::Tuple(types)) = &expr.ann.1 else { - panic!(); + // TODO(totel): better error? + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::ResolverError(ResolverError::SelfReferentialType { + location, + }), + )); }; let type_inner = types.get(*index as usize).ok_or( TypeInferenceError::NoirTypeError(TypeCheckError::TupleIndexOutOfBounds { @@ -574,6 +581,25 @@ pub fn type_infer( (ExprF::Cast { expr, target: target.clone() }, Some(target.clone())) } + ExprF::Tuple { exprs } => { + // TODO: support not-yet-typed expressions in the tuple literals, + // later back-propagating the type inferrence through the projections + // into the tuple + ( + exprf.clone(), + Some(NoirType::Tuple( + exprs + .iter() + .map(|e| e.ann.1.clone()) + .collect::>>() + .ok_or(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeAnnotationsNeededForFieldAccess { + location, + }, + ))?, + )), + ) + } }; Ok(SpannedOptionallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) @@ -708,6 +734,7 @@ mod tests { ExprF::Index { expr, index } => expr && index, ExprF::TupleAccess { expr, .. } => expr, ExprF::Cast { expr, .. } => expr, + ExprF::Tuple { exprs } => exprs.into_iter().all(identity), // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, @@ -747,6 +774,7 @@ mod tests { ExprF::Index { expr, index } => expr && index, ExprF::TupleAccess { expr, .. } => expr, ExprF::Cast { expr, .. } => expr, + ExprF::Tuple { exprs } => exprs.into_iter().all(identity), // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, @@ -774,6 +802,7 @@ mod tests { ExprF::Index { expr, index } => expr && index, ExprF::TupleAccess { expr, .. } => expr, ExprF::Cast { expr, .. } => expr, + ExprF::Tuple { exprs } => exprs.into_iter().all(identity), // Non-recursive variants don't carry information ExprF::Literal { .. } => true, @@ -963,6 +992,27 @@ mod tests { // assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } + #[test] + fn test_tuple() { + let annotation = "ensures(((), kek, true).2)"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr)); + } + #[test] fn test_monomorphization_request() { let attribute = "ensures(f(result))"; diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index 360a6fb9614..4cafac1c28d 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -337,6 +337,10 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> // TODO(totel): conversion of cast expressions todo!() }, + ExprF::Tuple { exprs } => { + // TODO(totel): conversion of tuple expressions + todo!() + }, } }) } From 2e894369fdd5281ee1619579db164c0467040b25 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 44/86] feat(annotations): add array literals - Tests for parsing and type inferrence --- compiler/formal_verification/src/ast.rs | 7 +++ compiler/formal_verification/src/parse.rs | 26 +++++++++- compiler/formal_verification/src/typing.rs | 54 ++++++++++++++++++++ compiler/fv_bridge/src/typed_attrs_to_vir.rs | 4 ++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 765167b5250..e1ab2a7fae7 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -18,6 +18,7 @@ pub enum ExprF { Cast { expr: R, target: NoirType }, Literal { value: Literal }, Tuple { exprs: Vec }, + Array { exprs: Vec }, Variable(Variable), } @@ -166,6 +167,9 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { ExprF::Tuple { exprs } => { ExprF::Tuple { exprs: exprs.into_iter().map(cata_fn).collect::>() } } + ExprF::Array { exprs } => { + ExprF::Array { exprs: exprs.into_iter().map(cata_fn).collect::>() } + } ExprF::Variable(Variable { path, name, id }) => { ExprF::Variable(Variable { path, name, id }) } @@ -195,6 +199,9 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res ExprF::Tuple { exprs } => { ExprF::Tuple { exprs: exprs.into_iter().map(cata_fn).collect::, _>>()? } } + ExprF::Array { exprs } => ExprF::Array { + exprs: exprs.into_iter().map(cata_fn).collect::, _>>()?, + }, ExprF::Variable(Variable { path, name, id }) => { ExprF::Variable(Variable { path, name, id }) } diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 9d3178d8901..9776583d461 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -606,10 +606,10 @@ pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { alt(( context("parenthesised or tuple", parse_parenthesised_or_tuple_expr), context("quantifier", parse_quantifier_expr), - // context("path", parse_path_expr), context("fn_call", parse_fn_call_expr), // context("member", parse_member_expr), // context("method_call", parse_method_call_expr), + context("array", parse_array_expr), context("literal", parse_literal_expr), context("var", parse_var_expr), )) @@ -737,6 +737,23 @@ pub(crate) fn parse_fn_call_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr )) } +pub(crate) fn parse_array_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + + let (input, exprs) = delimited( + expect("[", tag("[")), + separated_list0(delimited(multispace, expect(",", tag(",")), multispace), parse_expression), + cut(expect("]", tag("]"))), + ) + .parse(input)?; + + let after_offset = input.len(); + + let result_expr = build_expr(prev_offset, after_offset, ExprF::Array { exprs }); + + Ok((input, result_expr)) +} + pub(crate) fn parse_literal_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { let prev_offset = input.len(); let (input, exprf) = alt(( @@ -1090,6 +1107,13 @@ pub mod tests { assert_eq!(expr.0, ""); } + #[test] + fn test_array() { + let expr = parse("[1, kek, true][2]").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + #[test] fn test_ghost() { let annotation = "ghost"; diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 9991d0a6ba3..45982970010 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -600,6 +600,36 @@ pub fn type_infer( )), ) } + ExprF::Array { exprs } => { + match exprs.split_first() { + // NOTE: we do not support empty array literals + // (pretty useless and a PITA to type infer) + None => { + // TODO(totel): better error? + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::InvalidTypeForEntryPoint { location }, + )); + } + Some((first, rest)) => { + // NOTE: all expressions in the array have to have the same type + if rest.iter().any(|e| e.ann.1 != first.ann.1) { + // TODO(totel): better error? + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::InvalidTypeForEntryPoint { location }, + )); + } + + ( + exprf.clone(), + first + .ann + .1 + .clone() + .map(|t| NoirType::Array(exprs.len() as u32, Box::new(t))), + ) + } + } + } }; Ok(SpannedOptionallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) @@ -735,6 +765,7 @@ mod tests { ExprF::TupleAccess { expr, .. } => expr, ExprF::Cast { expr, .. } => expr, ExprF::Tuple { exprs } => exprs.into_iter().all(identity), + ExprF::Array { exprs } => exprs.into_iter().all(identity), // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, @@ -775,6 +806,7 @@ mod tests { ExprF::TupleAccess { expr, .. } => expr, ExprF::Cast { expr, .. } => expr, ExprF::Tuple { exprs } => exprs.into_iter().all(identity), + ExprF::Array { exprs } => exprs.into_iter().all(identity), // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, @@ -803,6 +835,7 @@ mod tests { ExprF::TupleAccess { expr, .. } => expr, ExprF::Cast { expr, .. } => expr, ExprF::Tuple { exprs } => exprs.into_iter().all(identity), + ExprF::Array { exprs } => exprs.into_iter().all(identity), // Non-recursive variants don't carry information ExprF::Literal { .. } => true, @@ -1013,6 +1046,27 @@ mod tests { dbg!(&strip_ann(spanned_typed_expr)); } + #[test] + fn test_array() { + let annotation = "ensures([15 as i32, a, 129 as i32][2])"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr)); + } + #[test] fn test_monomorphization_request() { let attribute = "ensures(f(result))"; diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index 4cafac1c28d..09fde4f10fa 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -341,6 +341,10 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> // TODO(totel): conversion of tuple expressions todo!() }, + ExprF::Array { exprs } => { + // TODO(totel): conversion of array expressions + todo!() + }, } }) } From ab58a51255eac5617de3a7279fed441daf43235d Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 45/86] fix(annotations): parse whitespace around parenthesis (again) --- compiler/formal_verification/src/parse.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 9776583d461..ec365ef872f 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -621,7 +621,7 @@ pub(crate) fn parse_parenthesised_or_tuple_expr<'a>(input: Input<'a>) -> PResult // NOTE: Both parenthesised and tuple expressions have a starting `(` let (input, (exprs, trailing_comma)) = delimited( - expect("(", tag("(")), + pair(expect("(", tag("(")), multispace), // NOTE: Inside, we have a list of 0 or more expressions separated by commas... pair( separated_list0( @@ -631,7 +631,7 @@ pub(crate) fn parse_parenthesised_or_tuple_expr<'a>(input: Input<'a>) -> PResult // NOTE: ...with an optional trailing comma opt(delimited(multispace, tag(","), multispace)), ), - cut(expect(")", tag(")"))), + pair(multispace, cut(expect(")", tag(")")))), ) .parse(input)?; From 0190b76730c4b17f6970c500bd91a252eca19756 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 46/86] refactor(annotations): add `OptinalType` type - Used in place of the old `Option`, has more semantic constructor names - Might have more constructors added if needed --- compiler/formal_verification/src/ast.rs | 1 - compiler/formal_verification/src/lib.rs | 3 +- compiler/formal_verification/src/typing.rs | 206 ++++++++++++--------- 3 files changed, 123 insertions(+), 87 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index e1ab2a7fae7..848ef14b786 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -28,7 +28,6 @@ pub struct AnnExpr { pub expr: Box>>, } -pub type SpannedOptionallyTypedExpr = AnnExpr<(Location, Option)>; pub type SpannedTypedExpr = AnnExpr<(Location, NoirType)>; pub type SpannedExpr = AnnExpr; pub type OffsetExpr = AnnExpr<(u32, u32)>; diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 66a2aebbff5..7010267dad5 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -1,5 +1,6 @@ use noirc_errors::Location; use noirc_frontend::monomorphization::ast as mast; +use typing::OptionalType; use std::{collections::BTreeMap, fmt::Debug}; use crate::{ @@ -31,7 +32,7 @@ pub enum Attribute { pub struct MonomorphizationRequest { pub function_identifier: String, // NOTE: `None` for untyped integer literals - pub param_types: Vec>, + pub param_types: Vec, } fn span_expr(annotation_location: Location, full_length: u32, expr: OffsetExpr) -> SpannedExpr { diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 45982970010..ff32b434b13 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -1,8 +1,10 @@ +use std::fmt::Display; + use crate::{ MonomorphizationRequest, State, ast::{ - BinaryOp, ExprF, Literal, SpannedExpr, SpannedOptionallyTypedExpr, SpannedTypedExpr, - UnaryOp, Variable, strip_ann, try_cata, try_contextual_cata, + AnnExpr, BinaryOp, ExprF, Literal, SpannedExpr, SpannedTypedExpr, UnaryOp, Variable, + strip_ann, try_cata, try_contextual_cata, }, }; use noirc_errors::Location; @@ -15,6 +17,25 @@ use noirc_frontend::{ use num_bigint::{BigInt, BigUint}; use num_traits::{One, Zero}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OptionalType { + /// Well-typed + Well(NoirType), + /// Untyped (yet) integer literal or quantified variable + IntegerLiteral, +} + +impl Display for OptionalType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OptionalType::Well(t) => write!(f, "{t}"), + OptionalType::IntegerLiteral => write!(f, "Integer literal"), + } + } +} + +pub type SpannedPartiallyTypedExpr = AnnExpr<(Location, OptionalType)>; + #[derive(Debug, Clone)] pub enum TypeInferenceError { MonomorphizationRequest(MonomorphizationRequest), @@ -92,9 +113,9 @@ pub fn bi_can_fit_in(bi: &BigInt, hole_size: &IntegerBitSize, hole_sign: &Signed // WARN: will (possibly) incorrectly propagate types in tuple literals // `(1000, 5, 1000).1 == (5 as u8)` pub fn propagate_concrete_type( - e: SpannedOptionallyTypedExpr, + e: SpannedPartiallyTypedExpr, t: NoirType, -) -> Result { +) -> Result { let limits = match &t { NoirType::Field => None, NoirType::Integer(hole_sign, hole_size) => Some((hole_sign, hole_size)), @@ -109,7 +130,7 @@ pub fn propagate_concrete_type( match expr { ExprF::Literal { value: Literal::Int(ref bi) } => { debug_assert!( - _type == None, + _type == OptionalType::IntegerLiteral, "Trying to smash type {:?} into {:?} which already has type {:?}", t, bi, @@ -139,7 +160,10 @@ pub fn propagate_concrete_type( _ => {} } - Ok(SpannedOptionallyTypedExpr { expr: Box::new(expr), ann: (location, Some(t.clone())) }) + Ok(SpannedPartiallyTypedExpr { + expr: Box::new(expr), + ann: (location, OptionalType::Well(t.clone())), + }) }) } @@ -153,7 +177,7 @@ pub fn type_infer( let is_numeric = |t: &NoirType| matches!(t, NoirType::Integer(_, _) | NoirType::Field); - let sote: SpannedOptionallyTypedExpr = try_contextual_cata( + let sote: SpannedPartiallyTypedExpr = try_contextual_cata( expr, vec![], &|mut quantifier_bound_variables, e| { @@ -172,12 +196,12 @@ pub fn type_infer( &|location, quantifier_bound_variables, exprf| { let (exprf, exprf_type) = match &exprf { ExprF::Literal { value } => match value { - Literal::Bool(_) => (exprf, Some(NoirType::Bool)), + Literal::Bool(_) => (exprf, OptionalType::Well(NoirType::Bool)), Literal::Int(_) => { - // NOTE: `None` signifies that this has to be inferred up the chain, will gain - // a concrete type when it gets matched (in an arithmetic or predicate) - // operation with a variable with a real (integer) type - (exprf, None) + // NOTE: `OptionalType::IntegerLiteral` signifies that this has to be inferred up the chain, + // will gain a concrete type when it gets matched in an (arithmetic or predicate) operation + // with a variable with a real (integer) type + (exprf, OptionalType::IntegerLiteral) } }, ExprF::Variable(Variable { path, name, id }) => { @@ -188,17 +212,19 @@ pub fn type_infer( let (variable_ident, variable_id, variable_type): ( &str, Option, - Option, + OptionalType, ) = quantifier_bound_variables .iter() .find_map(|bound_variable| { // TODO: `id` not `None` (when we have a way to generate new `id`s) - (bound_variable == name).then(|| (name.as_str(), None, None)) + (bound_variable == name) + .then(|| (name.as_str(), None, OptionalType::IntegerLiteral)) }) .or_else(|| { state.function.parameters.iter().find_map(|(id, _, par_name, t, _)| { - (par_name == name) - .then(|| (name.as_str(), Some(id.0), Some(t.clone()))) + (par_name == name).then(|| { + (name.as_str(), Some(id.0), OptionalType::Well(t.clone())) + }) }) }) .or_else(|| { @@ -206,7 +232,7 @@ pub fn type_infer( ( FUNC_RETURN_VAR_NAME, None, - Some(state.function.return_type.clone()), + OptionalType::Well(state.function.return_type.clone()), ) }) }) @@ -233,12 +259,12 @@ pub fn type_infer( function_identifier: name.clone(), param_types: args .iter() - .map(|arg: &SpannedOptionallyTypedExpr| arg.ann.1.clone()) + .map(|arg: &SpannedPartiallyTypedExpr| arg.ann.1.clone()) .collect(), }, ))?; - (exprf, Some(return_type.clone())) + (exprf, OptionalType::Well(return_type.clone())) } ExprF::Quantified { variables, .. } => { variables @@ -258,16 +284,18 @@ pub fn type_infer( } }) .collect::, _>>()?; - (exprf, Some(NoirType::Bool)) + (exprf, OptionalType::Well(NoirType::Bool)) } ExprF::Parenthesised { expr } => (exprf.clone(), expr.ann.1.clone()), ExprF::UnaryOp { op, expr } => { let t = match op { UnaryOp::Dereference => { match &expr.ann.1 { - Some(NoirType::Reference(t, _)) => Some(*t.clone()), + OptionalType::Well(NoirType::Reference(t, _)) => { + OptionalType::Well(*t.clone()) + } // TODO(totel): better error? - Some(_) | None => { + OptionalType::Well(_) | OptionalType::IntegerLiteral => { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::UnconstrainedReferenceToConstrained { location, @@ -292,14 +320,14 @@ pub fn type_infer( match &expr_right.ann.1 { // Fine shift amount, only `u8` is allowed in `Noir` - Some(t) if *t == shift_amount_type => {} + OptionalType::Well(t) if *t == shift_amount_type => {} // Integer literal, try type inferring to `u8` - None => { + OptionalType::IntegerLiteral => { expr_right = propagate_concrete_type(expr_right, shift_amount_type)?; } // Not fine shift amount - Some(_) => { + OptionalType::Well(_) => { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::InvalidShiftSize { location }, )); @@ -308,16 +336,16 @@ pub fn type_infer( match &expr_left.ann.1 { // Fine shiftee - Some(NoirType::Integer(_, _)) => {} + OptionalType::Well(NoirType::Integer(_, _)) => {} // Integer literal, try type inferring to u32 - None => { + OptionalType::IntegerLiteral => { expr_left = propagate_concrete_type( expr_left, default_literal_type.clone(), )?; } // Not fine shiftee - Some(expr_left_typ) => { + OptionalType::Well(expr_left_typ) => { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { expr_typ: expr_left_typ.to_string(), @@ -349,9 +377,9 @@ pub fn type_infer( let is_arith = op.is_arithmetic(); match (&expr_left.ann.1, &expr_right.ann.1) { - (None, None) => { + (OptionalType::IntegerLiteral, OptionalType::IntegerLiteral) => { if is_arith { - (exprf, None) + (exprf, OptionalType::IntegerLiteral) } else { let expr_left_inner = propagate_concrete_type( expr_left.clone(), @@ -368,11 +396,11 @@ pub fn type_infer( expr_left: expr_left_inner, expr_right: expr_right_inner, }, - Some(NoirType::Bool), + OptionalType::Well(NoirType::Bool), ) } } - (None, Some(t2)) => { + (OptionalType::IntegerLiteral, OptionalType::Well(t2)) => { // NOTE: `1 & true` if is_arith && !is_numeric(t2) { return Err(TypeInferenceError::NoirTypeError( @@ -393,10 +421,14 @@ pub fn type_infer( expr_left: expr_left_inner, expr_right: expr_right.clone(), }, - Some(if is_arith { t2.clone() } else { NoirType::Bool }), + OptionalType::Well(if is_arith { + t2.clone() + } else { + NoirType::Bool + }), ) } - (Some(t1), None) => { + (OptionalType::Well(t1), OptionalType::IntegerLiteral) => { // NOTE: `true & 1` if is_arith && !is_numeric(t1) { return Err(TypeInferenceError::NoirTypeError( @@ -417,10 +449,14 @@ pub fn type_infer( expr_left: expr_left.clone(), expr_right: expr_right_inner, }, - Some(if is_arith { t1.clone() } else { NoirType::Bool }), + OptionalType::Well(if is_arith { + t1.clone() + } else { + NoirType::Bool + }), ) } - (Some(t1), Some(t2)) => { + (OptionalType::Well(t1), OptionalType::Well(t2)) => { if t1 != t2 { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { @@ -437,7 +473,11 @@ pub fn type_infer( expr_left: expr_left.clone(), expr_right: expr_right.clone(), }, - Some(if is_arith { t1.clone() } else { NoirType::Bool }), + OptionalType::Well(if is_arith { + t1.clone() + } else { + NoirType::Bool + }), ) } } @@ -445,51 +485,36 @@ pub fn type_infer( // pure Boolean BinaryOp::Implies => { - if expr_left.ann.1 != Some(NoirType::Bool) { + if expr_left.ann.1 != OptionalType::Well(NoirType::Bool) { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { - expr_typ: expr_left - .ann - .1 - .as_ref() - .map(|t| t.to_string()) - .unwrap_or(String::new()), + expr_typ: expr_left.ann.1.to_string(), expected_typ: NoirType::Bool.to_string(), expr_location: location, }, )); } - if expr_right.ann.1 != Some(NoirType::Bool) { + if expr_right.ann.1 != OptionalType::Well(NoirType::Bool) { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { - expr_typ: expr_right - .ann - .1 - .as_ref() - .map(|t| t.to_string()) - .unwrap_or(String::new()), + expr_typ: expr_right.ann.1.to_string(), expected_typ: NoirType::Bool.to_string(), expr_location: location, }, )); } - (exprf, Some(NoirType::Bool)) + (exprf, OptionalType::Well(NoirType::Bool)) } } } ExprF::Index { expr, index } => { let mut index = index.clone(); - let Some(NoirType::Array(_, type_inner)) = &expr.ann.1 else { + let OptionalType::Well(NoirType::Array(_, type_inner)) = &expr.ann.1 else { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { - expr_typ: expr - .ann - .1 - .as_ref() - .map(|t| t.to_string()) - .unwrap_or(String::new()), + expr_typ: expr.ann.1.to_string(), expected_typ: String::from("Array type"), expr_location: location, }, @@ -497,21 +522,16 @@ pub fn type_infer( }; match &index.ann.1 { // Fine index - Some(NoirType::Integer(Signedness::Unsigned, _)) => {} + OptionalType::Well(NoirType::Integer(Signedness::Unsigned, _)) => {} // Integer literal, try type inferring to `u32` - None => { + OptionalType::IntegerLiteral => { index = propagate_concrete_type(index, default_literal_type.clone())?; } // Not fine index - Some(_) => { + OptionalType::Well(_) => { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { - expr_typ: index - .ann - .1 - .as_ref() - .map(|t| t.to_string()) - .unwrap_or(String::new()), + expr_typ: index.ann.1.to_string(), expected_typ: String::from("Unsigned integer type"), expr_location: location, }, @@ -519,10 +539,13 @@ pub fn type_infer( } } - (ExprF::Index { expr: expr.clone(), index }, Some(*type_inner.clone())) + ( + ExprF::Index { expr: expr.clone(), index }, + OptionalType::Well(*type_inner.clone()), + ) } ExprF::TupleAccess { expr, index } => { - let Some(NoirType::Tuple(types)) = &expr.ann.1 else { + let OptionalType::Well(NoirType::Tuple(types)) = &expr.ann.1 else { // TODO(totel): better error? return Err(TypeInferenceError::NoirTypeError( TypeCheckError::ResolverError(ResolverError::SelfReferentialType { @@ -541,14 +564,14 @@ pub fn type_infer( }), )?; - (exprf.clone(), Some(type_inner.clone())) + (exprf.clone(), OptionalType::Well(type_inner.clone())) } ExprF::Cast { expr, target } => { let mut expr = expr.clone(); // Non-booleans cannot cast to bool if matches!(target, NoirType::Bool) - && !matches!(expr.ann.1, Some(NoirType::Bool)) + && !matches!(expr.ann.1, OptionalType::Well(NoirType::Bool)) { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::CannotCastNumericToBool { @@ -561,7 +584,7 @@ pub fn type_infer( // Non-numerics cannot cast to numeric types if is_numeric(target) - && let Some(ref t) = expr.ann.1 + && let OptionalType::Well(ref t) = expr.ann.1 && !is_numeric(t) && !matches!(t, NoirType::Bool) { @@ -575,11 +598,14 @@ pub fn type_infer( } // Try to type infer integer literals as the target type - if matches!(expr.ann.1, None) { + if matches!(expr.ann.1, OptionalType::IntegerLiteral) { expr = propagate_concrete_type(expr, target.clone())?; } - (ExprF::Cast { expr, target: target.clone() }, Some(target.clone())) + ( + ExprF::Cast { expr, target: target.clone() }, + OptionalType::Well(target.clone()), + ) } ExprF::Tuple { exprs } => { // TODO: support not-yet-typed expressions in the tuple literals, @@ -587,10 +613,14 @@ pub fn type_infer( // into the tuple ( exprf.clone(), - Some(NoirType::Tuple( + OptionalType::Well(NoirType::Tuple( exprs .iter() .map(|e| e.ann.1.clone()) + .map(|ot| match ot { + OptionalType::Well(t) => Some(t), + OptionalType::IntegerLiteral => None, + }) .collect::>>() .ok_or(TypeInferenceError::NoirTypeError( TypeCheckError::TypeAnnotationsNeededForFieldAccess { @@ -621,25 +651,28 @@ pub fn type_infer( ( exprf.clone(), - first - .ann - .1 - .clone() - .map(|t| NoirType::Array(exprs.len() as u32, Box::new(t))), + match first.ann.1 { + OptionalType::Well(ref t) => OptionalType::Well( + NoirType::Array(exprs.len() as u32, Box::new(t.clone())), + ), + OptionalType::IntegerLiteral => OptionalType::IntegerLiteral, + }, ) } } } }; - Ok(SpannedOptionallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) + Ok(SpannedPartiallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) }, )?; let fully_typed_expr: SpannedTypedExpr = try_cata(sote, &|(location, otype), exprf| match otype { - Some(t) => Ok(SpannedTypedExpr { ann: (location, t), expr: Box::new(exprf) }), - None => Err(format!("Expr {:?} still has no type", exprf)), + OptionalType::Well(t) => { + Ok(SpannedTypedExpr { ann: (location, t), expr: Box::new(exprf) }) + } + OptionalType::IntegerLiteral => Err(format!("Expr {:?} still has no type", exprf)), }) .expect("Typing should have either succeeded or have resulted in an expected error"); @@ -1091,7 +1124,10 @@ mod tests { assert_eq!(function_identifier, "f"); assert_eq!( param_types, - vec![Some(NoirType::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo))] + vec![OptionalType::Well(NoirType::Integer( + Signedness::Signed, + IntegerBitSize::ThirtyTwo + ))] ); } } From b1eedc817f886ebb4fe613a9d04cb9190c0c22ad Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 47/86] refactor(annotations): type-inference algorithm for tuples and arrays - Add new constructor for `OptionalType` - `PartialTuple`. It signifies tuple literals, which do not have all of their sub-expressions fully type-inferered. It's used similarly to the `IntegerLiteral` constructor, i.e. gaining a real type later in the type-inference process - There are a few special cases for type-inferring `PartialTuple`s: - Projections right after construction (`(1, 2, 3).1`) The projected field gets carried upwards in the algorithm, the rest are unified to `Field`, if needed (for the other literals/quantifier variables) - Equality checks with other (partial) tuples Here we do by-element unification of the sub-expressions - Refactor `propagate_concrete_type` into `unify_expression_with_type`, which now respects the new ways of carrying the type "holes" upwards - Add `concretize_type` function, which tries to convert a `OptionalType` into a `NoirType` - Add `unify` function, which tries unifying two expressions, basically the old body of the big pattern match for most arithmetic operators - Refactor `BinaryOp` type-inference to not use (partially overlapping) pattern matches, but an `if-else` chain, utilizing some new predicates on the `BinaryOp` type itself - Correctly unify all array literal sub-expressions, mostly useful for cases with many partial tuples inside - Add tests for the new complex tuple and array type-inference - Fix `test_precedence_equality` test, remove all parenthesis before the equality check TODO: - Better errors in many cases - De-duplication of the "type-merging" logic --- compiler/formal_verification/src/ast.rs | 68 +- compiler/formal_verification/src/parse.rs | 7 +- compiler/formal_verification/src/typing.rs | 984 +++++++++++++------ compiler/fv_bridge/src/typed_attrs_to_vir.rs | 6 +- 4 files changed, 729 insertions(+), 336 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 848ef14b786..fd3bbfc408e 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -90,46 +90,34 @@ pub enum BinaryOp { } impl BinaryOp { + /// An operator is "arithmetic" if it performs an arithmetic operation + /// (e.g., +, -, *) and returns a value of the same numeric type as its operands. pub fn is_arithmetic(&self) -> bool { - matches!( - self, - // pure - BinaryOp::Mul - | BinaryOp::Div - | BinaryOp::Mod - | BinaryOp::Add - | BinaryOp::Sub - | BinaryOp::ShiftLeft - | BinaryOp::ShiftRight - // generic - | BinaryOp::And - | BinaryOp::Or - | BinaryOp::Xor - ) + matches!(self, Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod) } - pub fn is_predicate(&self) -> bool { - matches!( - self, - BinaryOp::Eq - | BinaryOp::Neq - | BinaryOp::Lt - | BinaryOp::Le - | BinaryOp::Gt - | BinaryOp::Ge - ) + /// An operator is "bitwise" if it performs a bitwise operation (e.g., &, |, ^). + /// In this language, these can operate on both integers and booleans. + pub fn is_bitwise(&self) -> bool { + matches!(self, Self::And | Self::Or | Self::Xor) } - pub fn is_boolean(&self) -> bool { - matches!( - self, - // pure - BinaryOp::Implies - // generic - | BinaryOp::And - | BinaryOp::Or - | BinaryOp::Xor - ) + /// An operator is a "shift" if it performs a bit shift (e.g., <<, >>). + /// These have unique type rules, requiring a numeric type on the left + /// and a `u8` on the right. + pub fn is_shift(&self) -> bool { + matches!(self, Self::ShiftLeft | Self::ShiftRight) + } + + /// An operator is a "comparison" if it compares two values and always returns a boolean. + pub fn is_comparison(&self) -> bool { + matches!(self, Self::Eq | Self::Neq | Self::Lt | Self::Le | Self::Gt | Self::Ge) + } + + /// An operator is "logical" if its operands are expected to be booleans. + /// This includes `==>` and the bitwise operators when used in a boolean context. + pub fn is_logical(&self) -> bool { + matches!(self, Self::Implies | Self::And | Self::Or) } } @@ -157,9 +145,7 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { ExprF::FnCall { name, args } => { ExprF::FnCall { name, args: args.into_iter().map(cata_fn).collect() } } - ExprF::Index { expr, index } => { - ExprF::Index { expr: cata_fn(expr), index: cata_fn(index) } - } + ExprF::Index { expr, index } => ExprF::Index { expr: cata_fn(expr), index: cata_fn(index) }, ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr), index }, ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr), target }, ExprF::Literal { value } => ExprF::Literal { value }, @@ -198,9 +184,9 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res ExprF::Tuple { exprs } => { ExprF::Tuple { exprs: exprs.into_iter().map(cata_fn).collect::, _>>()? } } - ExprF::Array { exprs } => ExprF::Array { - exprs: exprs.into_iter().map(cata_fn).collect::, _>>()?, - }, + ExprF::Array { exprs } => { + ExprF::Array { exprs: exprs.into_iter().map(cata_fn).collect::, _>>()? } + } ExprF::Variable(Variable { path, name, id }) => { ExprF::Variable(Variable { path, name, id }) } diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index ec365ef872f..327acb02a99 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -976,12 +976,16 @@ pub mod tests { assert_eq!(expr.0, ""); assert_eq!(expr_expected.0, ""); + let expr_flat: OffsetExpr = cata(expr.1, &|ann, expr| match expr { + ExprF::Parenthesised { expr } => expr, + _ => OffsetExpr { ann, expr: Box::new(expr) }, + }); let expr_expected_flat: OffsetExpr = cata(expr_expected.1, &|ann, expr| match expr { ExprF::Parenthesised { expr } => expr, _ => OffsetExpr { ann, expr: Box::new(expr) }, }); - assert_eq!(strip_ann(expr.1), strip_ann(expr_expected_flat)); + assert_eq!(strip_ann(expr_flat), strip_ann(expr_expected_flat)); } #[test] @@ -1038,7 +1042,6 @@ pub mod tests { test_precedence_equality( "exists(|i| (0 <= i) & (i < 20) & arr[i] > 100)", "exists(|i| (((0 <= i) & (i < 20)) & (arr[i] > 100)))", - // "exists(|i| ((((0 <= i) & (i < 20)) & arr[i]) > 100))", ); } diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index ff32b434b13..d1fa68d70b1 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -1,9 +1,9 @@ -use std::fmt::Display; +use std::{convert::identity, fmt::Display}; use crate::{ MonomorphizationRequest, State, ast::{ - AnnExpr, BinaryOp, ExprF, Literal, SpannedExpr, SpannedTypedExpr, UnaryOp, Variable, + AnnExpr, BinaryOp, ExprF, Literal, SpannedExpr, SpannedTypedExpr, UnaryOp, Variable, cata, strip_ann, try_cata, try_contextual_cata, }, }; @@ -23,6 +23,8 @@ pub enum OptionalType { Well(NoirType), /// Untyped (yet) integer literal or quantified variable IntegerLiteral, + /// Tuple with holes + PartialTuple(Vec), } impl Display for OptionalType { @@ -30,6 +32,7 @@ impl Display for OptionalType { match self { OptionalType::Well(t) => write!(f, "{t}"), OptionalType::IntegerLiteral => write!(f, "Integer literal"), + OptionalType::PartialTuple(types) => write!(f, "Partial tuple with types: {:?}", types), } } } @@ -110,61 +113,235 @@ pub fn bi_can_fit_in(bi: &BigInt, hole_size: &IntegerBitSize, hole_sign: &Signed return FitsIn::Yes; } -// WARN: will (possibly) incorrectly propagate types in tuple literals -// `(1000, 5, 1000).1 == (5 as u8)` -pub fn propagate_concrete_type( - e: SpannedPartiallyTypedExpr, - t: NoirType, +/// Unifies a partially-typed expression with a target concrete type +/// - If the expression is an `IntegerLiteral`, it's checked and given the target type +/// - If the expression is already `Well`-typed, it checks if the types match +/// - If the expression is a `PartialTuple`, it recursively unifies its elements +pub fn unify_expression_with_type( + expr: SpannedPartiallyTypedExpr, + target_type: NoirType, ) -> Result { - let limits = match &t { - NoirType::Field => None, - NoirType::Integer(hole_sign, hole_size) => Some((hole_sign, hole_size)), - _ => todo!( - "Can only propagate integer types, trying to ram {:#?} into {:#?}", - t, - strip_ann(e) - ), - }; + let (location, opt_type) = expr.ann; + let exprf = *expr.expr; + + match opt_type { + OptionalType::IntegerLiteral => match exprf { + ExprF::Literal { value: Literal::Int(bi) } => { + if let NoirType::Integer(ref sign, ref size) = target_type + && let FitsIn::No { need } = bi_can_fit_in(&bi, size, sign) + { + return Err(TypeInferenceError::IntegerLiteralDoesNotFit { + literal: bi.clone(), + literal_type: target_type.clone(), + fit_into: need.clone(), + location, + message: format!( + "Integer literal {} cannot fit in {}, needs at least {:?} or larger", + bi, target_type, need + ), + }); + } - try_cata(e, &|(location, _type), expr| { - match expr { - ExprF::Literal { value: Literal::Int(ref bi) } => { - debug_assert!( - _type == OptionalType::IntegerLiteral, - "Trying to smash type {:?} into {:?} which already has type {:?}", - t, - bi, - _type - ); - // NOTE: only check limits for integer types - // (assume that `NoirType::Field` can hold anything) - if let Some((hole_sign, hole_size)) = limits { - let fits = bi_can_fit_in(bi, hole_size, hole_sign); - match fits { - FitsIn::Yes => {} - FitsIn::No { need } => { - return Err(TypeInferenceError::IntegerLiteralDoesNotFit { - literal: bi.clone(), - literal_type: t.clone(), - fit_into: need.clone(), - message: format!( - "Integer literal {} cannot fit in {}, needs at least {:?} or larger", - bi, t, need, - ), - location, - }); + Ok(SpannedPartiallyTypedExpr { + expr: Box::new(ExprF::Literal { value: Literal::Int(bi) }), + ann: (location, OptionalType::Well(target_type)), + }) + } + // NOTE: quantified variable + ExprF::Variable(var) => Ok(SpannedPartiallyTypedExpr { + ann: (location, OptionalType::Well(target_type)), + expr: Box::new(ExprF::Variable(var)), + }), + ExprF::BinaryOp { op, expr_left, expr_right } => { + let new_left = unify_expression_with_type(expr_left, target_type.clone())?; + let new_right = unify_expression_with_type(expr_right, target_type.clone())?; + let result_type = if op.is_comparison() { NoirType::Bool } else { target_type }; + Ok(SpannedPartiallyTypedExpr { + ann: (location, OptionalType::Well(result_type)), + expr: Box::new(ExprF::BinaryOp { + op, + expr_left: new_left, + expr_right: new_right, + }), + }) + } + ExprF::TupleAccess { expr: inner_tuple_expr, index } => { + let ExprF::Tuple { exprs } = *inner_tuple_expr.expr else { unreachable!() }; + + let new_elements = exprs + .into_iter() + .enumerate() + .map(|(i, elem_expr)| { + if i as u32 == index { + unify_expression_with_type(elem_expr, target_type.clone()) + } else if let OptionalType::IntegerLiteral = elem_expr.ann.1 { + // NOTE: Default other integer literals to Field, as they are unconstrained. + unify_expression_with_type(elem_expr, NoirType::Field) + } else { + Ok(elem_expr) } - } - } + }) + .collect::, _>>()?; + + let new_element_types = + new_elements.iter().map(|e| e.ann.1.clone()).collect::>(); + let updated_inner_tuple_type = new_element_types + .iter() + .map(|ot| match ot { + OptionalType::Well(t) => Some(t.clone()), + _ => None, + }) + .collect::>>() + .map(NoirType::Tuple) + .map(OptionalType::Well) + .unwrap_or(OptionalType::PartialTuple(new_element_types)); + + let updated_inner_tuple = SpannedPartiallyTypedExpr { + ann: (inner_tuple_expr.ann.0, updated_inner_tuple_type), + expr: Box::new(ExprF::Tuple { exprs: new_elements }), + }; + + Ok(SpannedPartiallyTypedExpr { + ann: (location, OptionalType::Well(target_type.clone())), + expr: Box::new(ExprF::TupleAccess { expr: updated_inner_tuple, index }), + }) + } + ExprF::Parenthesised { expr: inner_expr } => { + let new_inner = unify_expression_with_type(inner_expr, target_type)?; + Ok(SpannedPartiallyTypedExpr { + ann: new_inner.ann.clone(), + expr: Box::new(ExprF::Parenthesised { expr: new_inner }), + }) + } + _ => unreachable!( + "ICE: Unexpected expression {:?} found with IntegerLiteral type", + exprf + ), + }, + + OptionalType::Well(well_type) => { + if well_type == target_type { + Ok(SpannedPartiallyTypedExpr { + ann: (location, OptionalType::Well(well_type)), + expr: Box::new(exprf), + }) + } else { + Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { + expected_typ: target_type.to_string(), + expr_typ: well_type.to_string(), + expr_location: location, + })) } - _ => {} } - Ok(SpannedPartiallyTypedExpr { - expr: Box::new(expr), - ann: (location, OptionalType::Well(t.clone())), - }) - }) + OptionalType::PartialTuple(_) => { + let NoirType::Tuple(ref target_expr_types) = target_type else { + return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { + expected_typ: target_type.to_string(), + expr_typ: "tuple".to_string(), + expr_location: location, + })); + }; + + let ExprF::Tuple { exprs } = exprf else { unreachable!() }; + + if exprs.len() != target_expr_types.len() { + return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TupleMismatch { + tuple_types: vec![], + actual_count: exprs.len(), + location, + })); + } + + let new_exprs = exprs + .into_iter() + .zip(target_expr_types) + .map(|(elem_expr, target_elem_type)| { + unify_expression_with_type(elem_expr, target_elem_type.clone()) + }) + .collect::>()?; + + Ok(SpannedPartiallyTypedExpr { + ann: (location, OptionalType::Well(target_type)), + expr: Box::new(ExprF::Tuple { exprs: new_exprs }), + }) + } + } +} + +/// Converts an `OptionalType` into a concrete `NoirType`, +/// using the default for any remaining untyped integer literals, +/// unless in a partial tuple +pub fn concretize_type( + opt_type: OptionalType, + default_integer_literal_type: &NoirType, +) -> Result { + match opt_type { + OptionalType::Well(t) => Ok(t), + OptionalType::IntegerLiteral => Ok(default_integer_literal_type.clone()), + OptionalType::PartialTuple(elements) => elements + .into_iter() + .map(|e| match e { + OptionalType::IntegerLiteral => Err(TypeInferenceError::NoirTypeError( + // TODO: carry optional information in `concretize_type` + TypeCheckError::TypeAnnotationNeededOnArrayLiteral { + is_array: true, + location: Location::dummy(), + }, + )), + _ => concretize_type(e, default_integer_literal_type), + }) + .collect::>() + .map(NoirType::Tuple), + } +} + +/// Takes two partially-typed expressions and attempts to make their types equal +/// Information flows from the `Well` typed expression to the `IntegerLiteral` +/// Returns the two (potentially updated) expressions +pub fn unify( + left: SpannedPartiallyTypedExpr, + right: SpannedPartiallyTypedExpr, + default_literal_type: &NoirType, +) -> Result<(SpannedPartiallyTypedExpr, SpannedPartiallyTypedExpr, NoirType), TypeInferenceError> { + match (&left.ann.1, &right.ann.1) { + (OptionalType::Well(t1), OptionalType::Well(t2)) => { + if t1 != t2 { + return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { + expected_typ: t1.to_string(), + expr_typ: t2.to_string(), + expr_location: right.ann.0, + })); + } + let t = t1.clone(); + Ok((left, right, t)) + } + + (OptionalType::Well(t), OptionalType::IntegerLiteral) => { + let new_right = unify_expression_with_type(right, t.clone())?; + let t = t.clone(); + Ok((left, new_right, t)) + } + (OptionalType::IntegerLiteral, OptionalType::Well(t)) => { + let new_left = unify_expression_with_type(left, t.clone())?; + let t = t.clone(); + Ok((new_left, right, t)) + } + + (OptionalType::IntegerLiteral, OptionalType::IntegerLiteral) => { + let new_left = unify_expression_with_type(left, default_literal_type.clone())?; + let new_right = unify_expression_with_type(right, default_literal_type.clone())?; + Ok((new_left, new_right, default_literal_type.clone())) + } + + // NOTE: All other combinations (e.g., trying to unify a bool with a tuple) are a type mismatch. + // The calling function is responsible for handling recursive cases like tuples. + (t1, t2) => Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { + expected_typ: t1.to_string(), + expr_typ: t2.to_string(), + expr_location: right.ann.0, + })), + } } pub fn type_infer( @@ -294,8 +471,8 @@ pub fn type_infer( OptionalType::Well(NoirType::Reference(t, _)) => { OptionalType::Well(*t.clone()) } - // TODO(totel): better error? - OptionalType::Well(_) | OptionalType::IntegerLiteral => { + _ => { + // TODO(totel): better error? return Err(TypeInferenceError::NoirTypeError( TypeCheckError::UnconstrainedReferenceToConstrained { location, @@ -310,201 +487,224 @@ pub fn type_infer( (exprf.clone(), t) } ExprF::BinaryOp { op, expr_left, expr_right } => { - match op { - BinaryOp::ShiftLeft | BinaryOp::ShiftRight => { - let mut expr_left = expr_left.clone(); - let mut expr_right = expr_right.clone(); - - let shift_amount_type = - NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Eight); - - match &expr_right.ann.1 { - // Fine shift amount, only `u8` is allowed in `Noir` - OptionalType::Well(t) if *t == shift_amount_type => {} - // Integer literal, try type inferring to `u8` - OptionalType::IntegerLiteral => { - expr_right = - propagate_concrete_type(expr_right, shift_amount_type)?; - } - // Not fine shift amount - OptionalType::Well(_) => { - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::InvalidShiftSize { location }, - )); + enum TupleTypes { + Well(Vec), + Partial(Vec), + } + impl From for Vec { + fn from(value: TupleTypes) -> Self { + match value { + TupleTypes::Well(types) => { + types.into_iter().map(OptionalType::Well).collect() } + TupleTypes::Partial(types) => types, + } + } + } + fn is_tupleish(t: &OptionalType) -> Option { + match t { + OptionalType::Well(NoirType::Tuple(types)) => { + Some(TupleTypes::Well(types.clone())) } + OptionalType::PartialTuple(types) => { + Some(TupleTypes::Partial(types.clone())) + } + _ => None, + } + } - match &expr_left.ann.1 { - // Fine shiftee - OptionalType::Well(NoirType::Integer(_, _)) => {} - // Integer literal, try type inferring to u32 - OptionalType::IntegerLiteral => { - expr_left = propagate_concrete_type( - expr_left, - default_literal_type.clone(), - )?; - } - // Not fine shiftee - OptionalType::Well(expr_left_typ) => { + if matches!(op, BinaryOp::Eq | BinaryOp::Neq) + && let (Some(_), Some(_)) = + (is_tupleish(&expr_left.ann.1), is_tupleish(&expr_right.ann.1)) + { + let ExprF::Tuple { exprs: mut left_exprs } = *expr_left.expr.clone() else { + unreachable!() + }; + + let ExprF::Tuple { exprs: mut right_exprs } = *expr_right.expr.clone() + else { + unreachable!() + }; + + if left_exprs.len() != right_exprs.len() { + // TODO(totel): better error? + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TupleMismatch { + location, + tuple_types: vec![], + actual_count: right_exprs.len(), + }, + )); + } + + let mut new_exprs_types = Vec::with_capacity(left_exprs.len()); + + // NOTE: Unify all pair of elements + for i in 0..left_exprs.len() { + let (new_left_expr, new_right_expr, t) = unify( + left_exprs[i].clone(), + right_exprs[i].clone(), + &default_literal_type, + )?; + + (left_exprs[i], right_exprs[i]) = (new_left_expr, new_right_expr); + new_exprs_types.push(t.clone()); + } + + let new_expr_left = SpannedPartiallyTypedExpr { + expr: Box::new(ExprF::Tuple { exprs: left_exprs }), + ann: ( + expr_left.ann.0, + OptionalType::Well(NoirType::Tuple(new_exprs_types.clone())), + ), + }; + let new_expr_right = SpannedPartiallyTypedExpr { + expr: Box::new(ExprF::Tuple { exprs: right_exprs }), + ann: ( + expr_right.ann.0, + OptionalType::Well(NoirType::Tuple(new_exprs_types.clone())), + ), + }; + + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: new_expr_left, + expr_right: new_expr_right, + }, + OptionalType::Well(NoirType::Bool), + ) + } else if op.is_shift() { + let mut new_left = expr_left.clone(); + let mut new_right = expr_right.clone(); + let shift_amount_type = + NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Eight); + + match &new_right.ann.1 { + OptionalType::Well(t) if *t != shift_amount_type => { + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::InvalidShiftSize { location }, + )); + } + OptionalType::IntegerLiteral => { + new_right = + unify_expression_with_type(new_right, shift_amount_type)?; + } + _ => {} // OK + } + + if let OptionalType::IntegerLiteral = &new_left.ann.1 { + new_left = + unify_expression_with_type(new_left, default_literal_type.clone())?; + } + + let result_type = new_left.ann.1.clone(); + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: new_left, + expr_right: new_right, + }, + result_type, + ) + } else { + match (&expr_left.ann.1, &expr_right.ann.1) { + (OptionalType::Well(t1), OptionalType::Well(t2)) => { + if t1 != t2 { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { - expr_typ: expr_left_typ.to_string(), - expected_typ: String::from("Numeric type"), - expr_location: location, + expected_typ: t1.to_string(), + expr_typ: t2.to_string(), + expr_location: expr_right.ann.0, }, )); } + let result_type = + if op.is_comparison() { NoirType::Bool } else { t1.clone() }; + (exprf, OptionalType::Well(result_type)) } - let t = expr_left.ann.1.clone(); - - (ExprF::BinaryOp { op: op.clone(), expr_left, expr_right }, t) - } - BinaryOp::Mul - | BinaryOp::Div - | BinaryOp::Mod - | BinaryOp::Add - | BinaryOp::Sub - | BinaryOp::Eq - | BinaryOp::Neq - | BinaryOp::Lt - | BinaryOp::Le - | BinaryOp::Gt - | BinaryOp::Ge - | BinaryOp::And - | BinaryOp::Or - | BinaryOp::Xor => { - let is_arith = op.is_arithmetic(); - - match (&expr_left.ann.1, &expr_right.ann.1) { - (OptionalType::IntegerLiteral, OptionalType::IntegerLiteral) => { - if is_arith { - (exprf, OptionalType::IntegerLiteral) - } else { - let expr_left_inner = propagate_concrete_type( - expr_left.clone(), - default_literal_type.clone(), - )?; - let expr_right_inner = propagate_concrete_type( - expr_right.clone(), - default_literal_type.clone(), - )?; - - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: expr_left_inner, - expr_right: expr_right_inner, - }, - OptionalType::Well(NoirType::Bool), - ) - } - } - (OptionalType::IntegerLiteral, OptionalType::Well(t2)) => { - // NOTE: `1 & true` - if is_arith && !is_numeric(t2) { - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::TypeMismatch { - expected_typ: "Numeric type".to_string(), - expr_typ: t2.to_string(), - expr_location: location, - }, - )); - } - - let expr_left_inner = - propagate_concrete_type(expr_left.clone(), t2.clone())?; - - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: expr_left_inner, - expr_right: expr_right.clone(), + (OptionalType::Well(t1), OptionalType::IntegerLiteral) => { + if (op.is_arithmetic() || op.is_bitwise()) && !is_numeric(t1) { + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: "a numeric type".to_string(), + expr_typ: t1.to_string(), + expr_location: expr_left.ann.0, }, - OptionalType::Well(if is_arith { - t2.clone() - } else { - NoirType::Bool - }), - ) + )); } - (OptionalType::Well(t1), OptionalType::IntegerLiteral) => { - // NOTE: `true & 1` - if is_arith && !is_numeric(t1) { - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::TypeMismatch { - expected_typ: "Numeric type".to_string(), - expr_typ: t1.to_string(), - expr_location: location, - }, - )); - } - - let expr_right_inner = - propagate_concrete_type(expr_right.clone(), t1.clone())?; + let new_right = + unify_expression_with_type(expr_right.clone(), t1.clone())?; + let result_type = + if op.is_comparison() { NoirType::Bool } else { t1.clone() }; + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: expr_left.clone(), + expr_right: new_right, + }, + OptionalType::Well(result_type), + ) + } - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: expr_left.clone(), - expr_right: expr_right_inner, + (OptionalType::IntegerLiteral, OptionalType::Well(t2)) => { + if (op.is_arithmetic() || op.is_bitwise()) && !is_numeric(t2) { + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: "a numeric type".to_string(), + expr_typ: t2.to_string(), + expr_location: expr_right.ann.0, }, - OptionalType::Well(if is_arith { - t1.clone() - } else { - NoirType::Bool - }), - ) + )); } - (OptionalType::Well(t1), OptionalType::Well(t2)) => { - if t1 != t2 { - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::TypeMismatch { - expected_typ: t2.to_string(), - expr_typ: t1.to_string(), - expr_location: location, - }, - )); - } + let new_left = + unify_expression_with_type(expr_left.clone(), t2.clone())?; + let result_type = + if op.is_comparison() { NoirType::Bool } else { t2.clone() }; + ( + ExprF::BinaryOp { + op: op.clone(), + expr_left: new_left, + expr_right: expr_right.clone(), + }, + OptionalType::Well(result_type), + ) + } + (OptionalType::IntegerLiteral, OptionalType::IntegerLiteral) => { + if op.is_arithmetic() || op.is_bitwise() { + (exprf, OptionalType::IntegerLiteral) + } else { + let new_left = unify_expression_with_type( + expr_left.clone(), + default_literal_type.clone(), + )?; + let new_right = unify_expression_with_type( + expr_right.clone(), + default_literal_type.clone(), + )?; ( ExprF::BinaryOp { op: op.clone(), - expr_left: expr_left.clone(), - expr_right: expr_right.clone(), + expr_left: new_left, + expr_right: new_right, }, - OptionalType::Well(if is_arith { - t1.clone() - } else { - NoirType::Bool - }), + OptionalType::Well(NoirType::Bool), ) } } - } - // pure Boolean - BinaryOp::Implies => { - if expr_left.ann.1 != OptionalType::Well(NoirType::Bool) { + // NOTE: Any other combination involving tuples is an error in this arm + (t1, t2) => { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { - expr_typ: expr_left.ann.1.to_string(), - expected_typ: NoirType::Bool.to_string(), + expected_typ: t1.to_string(), + expr_typ: t2.to_string(), expr_location: location, }, )); } - if expr_right.ann.1 != OptionalType::Well(NoirType::Bool) { - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::TypeMismatch { - expr_typ: expr_right.ann.1.to_string(), - expected_typ: NoirType::Bool.to_string(), - expr_location: location, - }, - )); - } - - (exprf, OptionalType::Well(NoirType::Bool)) } } } @@ -525,13 +725,14 @@ pub fn type_infer( OptionalType::Well(NoirType::Integer(Signedness::Unsigned, _)) => {} // Integer literal, try type inferring to `u32` OptionalType::IntegerLiteral => { - index = propagate_concrete_type(index, default_literal_type.clone())?; + index = + unify_expression_with_type(index, default_literal_type.clone())?; } // Not fine index - OptionalType::Well(_) => { + t => { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { - expr_typ: index.ann.1.to_string(), + expr_typ: t.to_string(), expected_typ: String::from("Unsigned integer type"), expr_location: location, }, @@ -545,26 +746,46 @@ pub fn type_infer( ) } ExprF::TupleAccess { expr, index } => { - let OptionalType::Well(NoirType::Tuple(types)) = &expr.ann.1 else { - // TODO(totel): better error? - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::ResolverError(ResolverError::SelfReferentialType { - location, - }), - )); + let t = match &expr.ann.1 { + OptionalType::Well(NoirType::Tuple(types)) => { + types.get(*index as usize).cloned().map(OptionalType::Well).ok_or( + TypeInferenceError::NoirTypeError( + TypeCheckError::TupleIndexOutOfBounds { + index: *index as usize, + // NOTE: We are converting to Type::Tuple of empty vec because + // the inner types don't really matter for the error reporting + lhs_type: noirc_frontend::Type::Tuple(vec![]), + length: types.len(), + location, + }, + ), + )? + } + OptionalType::PartialTuple(types) => { + types.get(*index as usize).cloned().ok_or( + TypeInferenceError::NoirTypeError( + TypeCheckError::TupleIndexOutOfBounds { + index: *index as usize, + // NOTE: We are converting to Type::Tuple of empty vec because + // the inner types don't really matter for the error reporting + lhs_type: noirc_frontend::Type::Tuple(vec![]), + length: types.len(), + location, + }, + ), + )? + } + _ => { + // TODO(totel): better error? + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::ResolverError(ResolverError::SelfReferentialType { + location, + }), + )); + } }; - let type_inner = types.get(*index as usize).ok_or( - TypeInferenceError::NoirTypeError(TypeCheckError::TupleIndexOutOfBounds { - index: *index as usize, - // NOTE: We are converting to Type::Tuple of empty vec because - // the inner types don't really matter for the error reporting - lhs_type: noirc_frontend::Type::Tuple(vec![]), - length: types.len(), - location, - }), - )?; - (exprf.clone(), OptionalType::Well(type_inner.clone())) + (exprf.clone(), t) } ExprF::Cast { expr, target } => { let mut expr = expr.clone(); @@ -599,7 +820,7 @@ pub fn type_infer( // Try to type infer integer literals as the target type if matches!(expr.ann.1, OptionalType::IntegerLiteral) { - expr = propagate_concrete_type(expr, target.clone())?; + expr = unify_expression_with_type(expr, target.clone())?; } ( @@ -611,55 +832,140 @@ pub fn type_infer( // TODO: support not-yet-typed expressions in the tuple literals, // later back-propagating the type inferrence through the projections // into the tuple - ( - exprf.clone(), - OptionalType::Well(NoirType::Tuple( - exprs - .iter() - .map(|e| e.ann.1.clone()) - .map(|ot| match ot { - OptionalType::Well(t) => Some(t), - OptionalType::IntegerLiteral => None, - }) - .collect::>>() - .ok_or(TypeInferenceError::NoirTypeError( - TypeCheckError::TypeAnnotationsNeededForFieldAccess { - location, - }, - ))?, - )), - ) + + // NOTE: if all sub-expressions are well-typed, produce a well-typed `Tuple` + // otherwise, produce a `OptionalType::PartialTuple` + let t = exprs + .iter() + .map(|e| e.ann.1.clone()) + .map(|ot| match ot { + OptionalType::Well(t) => Some(t), + _ => None, + }) + .collect::>>() + .map(NoirType::Tuple) + .map(OptionalType::Well) + .unwrap_or(OptionalType::PartialTuple( + exprs.iter().map(|ae| ae.ann.1.clone()).collect(), + )); + + (exprf.clone(), t) } ExprF::Array { exprs } => { - match exprs.split_first() { - // NOTE: we do not support empty array literals - // (pretty useless and a PITA to type infer) - None => { - // TODO(totel): better error? - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::InvalidTypeForEntryPoint { location }, - )); - } - Some((first, rest)) => { - // NOTE: all expressions in the array have to have the same type - if rest.iter().any(|e| e.ann.1 != first.ann.1) { - // TODO(totel): better error? - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::InvalidTypeForEntryPoint { location }, - )); - } + if exprs.is_empty() { + // TODO(totel): better error? + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::InvalidTypeForEntryPoint { location }, + )); + } - ( - exprf.clone(), - match first.ann.1 { - OptionalType::Well(ref t) => OptionalType::Well( - NoirType::Array(exprs.len() as u32, Box::new(t.clone())), + let unified_opt_type = exprs.iter().try_fold( + OptionalType::IntegerLiteral, + |acc, current_expr| { + // This closure acts as our "join" operation. + fn join_types( + t1: OptionalType, + t2: OptionalType, + location: Location, + ) -> Result + { + match (t1, t2) { + // An integer literal can join with any other type. + (t, OptionalType::IntegerLiteral) => Ok(t), + (OptionalType::IntegerLiteral, t) => Ok(t), + + // If both types are well-known, they must be identical. + (OptionalType::Well(w1), OptionalType::Well(w2)) => { + if w1 == w2 { + Ok(OptionalType::Well(w1)) + } else { + Err(TypeInferenceError::NoirTypeError( + // TODO: calculate indices + TypeCheckError::NonHomogeneousArray { + first_type: w1.to_string(), + first_location: location, + first_index: 0, + second_type: w2.to_string(), + second_location: location, + second_index: 0, + }, + )) + } + } + + // Recursively join partial tuples. + ( + OptionalType::PartialTuple(v1), + OptionalType::PartialTuple(v2), + ) => { + if v1.len() != v2.len() { + /* TODO: Mismatch error */ + Ok(OptionalType::PartialTuple(vec![])) + } else { + let joined = v1 + .into_iter() + .zip(v2.into_iter()) + .map(|(e1, e2)| join_types(e1, e2, location)) + .collect::>()?; + Ok(OptionalType::PartialTuple(joined)) + } + } + + // Coerce Well(Tuple) to PartialTuple for joining. + ( + OptionalType::Well(NoirType::Tuple(v1)), + t2 @ OptionalType::PartialTuple(_), + ) => join_types( + OptionalType::PartialTuple( + v1.into_iter().map(OptionalType::Well).collect(), + ), + t2, + location, ), - OptionalType::IntegerLiteral => OptionalType::IntegerLiteral, - }, - ) - } - } + ( + t1 @ OptionalType::PartialTuple(_), + OptionalType::Well(NoirType::Tuple(v2)), + ) => join_types( + t1, + OptionalType::PartialTuple( + v2.into_iter().map(OptionalType::Well).collect(), + ), + location, + ), + + // NOTE: All other combinations are a non-homogeneous array error + (other1, other2) => Err(TypeInferenceError::NoirTypeError( + TypeCheckError::NonHomogeneousArray { + first_type: other1.to_string(), + first_location: location, + first_index: 0, + second_type: other2.to_string(), + second_location: location, + second_index: 0, + }, + )), + } + } + join_types(acc, current_expr.ann.1.clone(), location) + }, + )?; + + let concrete_element_type = + concretize_type(unified_opt_type, &default_literal_type)?; + + let new_exprs = exprs + .into_iter() + .map(|expr| { + unify_expression_with_type(expr.clone(), concrete_element_type.clone()) + }) + .collect::, _>>()?; + + let array_type = OptionalType::Well(NoirType::Array( + new_exprs.len() as u32, + Box::new(concrete_element_type), + )); + + (ExprF::Array { exprs: new_exprs }, array_type) } }; @@ -672,7 +978,7 @@ pub fn type_infer( OptionalType::Well(t) => { Ok(SpannedTypedExpr { ann: (location, t), expr: Box::new(exprf) }) } - OptionalType::IntegerLiteral => Err(format!("Expr {:?} still has no type", exprf)), + _ => Err(format!("Expr {:?} still has no type ({:?})", exprf, otype)), }) .expect("Typing should have either succeeded or have resulted in an expected error"); @@ -693,7 +999,7 @@ mod tests { use crate::{ Attribute, - ast::{Literal, Variable, cata}, + ast::{Literal, Variable, cata, strip_ann}, parse::{parse_attribute, tests::*}, }; @@ -941,7 +1247,7 @@ mod tests { // NOTE: untyped integer literal (same for quantifier variables) force the other argument // to also be numeric assert_eq!(expr_typ, "bool"); - assert_eq!(expected_typ, "Numeric type"); + assert_eq!(expected_typ, "a numeric type"); } #[test] @@ -1058,6 +1364,27 @@ mod tests { // assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } + #[test] + fn test_tuple_complex() { + let annotation = "ensures((1000, 5, 1000).1 == 15 as u8)"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr)); + } + #[test] fn test_tuple() { let annotation = "ensures(((), kek, true).2)"; @@ -1079,6 +1406,87 @@ mod tests { dbg!(&strip_ann(spanned_typed_expr)); } + #[test] + fn test_tuple_equality() { + let annotation = "ensures((1 as u8, 15) == (1, 15 as i16))"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr.clone())); + + let ExprF::BinaryOp { op: BinaryOp::Eq, expr_left, expr_right } = *spanned_typed_expr.expr + else { + panic!() + }; + + let ExprF::Tuple { exprs: _ } = *expr_left.expr else { panic!() }; + let ExprF::Tuple { exprs: _ } = *expr_right.expr else { panic!() }; + + // Assert that both tuple types are fully unified + assert_eq!( + expr_left.ann.1, + NoirType::Tuple(vec![ + NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Eight), + NoirType::Integer(Signedness::Signed, IntegerBitSize::Sixteen) + ]) + ); + assert_eq!(expr_left.ann.1, expr_right.ann.1); + } + + #[test] + fn test_array_equality() { + let annotation = "ensures([(1, 2), (1 as u8, 7), (8, 9 as i16)] == [(0, 0), (1 as u8, 2 as i16), (0, 0)])"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr.clone())); + + let ExprF::BinaryOp { op: BinaryOp::Eq, expr_left, expr_right } = *spanned_typed_expr.expr + else { + panic!() + }; + + let ExprF::Array { exprs: _ } = *expr_left.expr else { panic!() }; + let ExprF::Array { exprs: _ } = *expr_right.expr else { panic!() }; + + // Assert that both array types are fully unified + assert_eq!( + expr_left.ann.1, + NoirType::Array( + 3, + Box::new(NoirType::Tuple(vec![ + NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Eight), + NoirType::Integer(Signedness::Signed, IntegerBitSize::Sixteen) + ])) + ) + ); + assert_eq!(expr_left.ann.1, expr_right.ann.1); + } + #[test] fn test_array() { let annotation = "ensures([15 as i32, a, 129 as i32][2])"; diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index 09fde4f10fa..082813d9a8f 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -243,7 +243,7 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> BinaryOp::Xor => bool_or_bitwise(VirBinaryOp::Xor, BitwiseOp::BitXor), }; - let binary_op_type = get_binary_op_type(expr_left.typ.clone(), &op); + let binary_op_type = ast_type_to_vir_type(&typ); let exprx = ExprX::Binary(vir_binary_op, expr_left, expr_right); let vir_binary_expr = @@ -348,7 +348,3 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> } }) } - -fn get_binary_op_type(lhs_type: Typ, binary_op: &BinaryOp) -> Typ { - if !binary_op.is_arithmetic() { Arc::new(TypX::Bool) } else { lhs_type } -} From e68ba1d17d35b4329daae0f661dfc56fbf1aa74f Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 48/86] fix(annotations): type-infer `IntegerLiteral`s through `UnaryOp`s --- compiler/formal_verification/src/typing.rs | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index d1fa68d70b1..794391e5a9e 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -165,6 +165,13 @@ pub fn unify_expression_with_type( }), }) } + ExprF::UnaryOp { op, expr } => { + let new_expr = unify_expression_with_type(expr, target_type.clone())?; + Ok(SpannedPartiallyTypedExpr { + ann: (location, OptionalType::Well(target_type)), + expr: Box::new(ExprF::UnaryOp { op, expr: new_expr }) + }) + } ExprF::TupleAccess { expr: inner_tuple_expr, index } => { let ExprF::Tuple { exprs } = *inner_tuple_expr.expr else { unreachable!() }; @@ -1320,6 +1327,24 @@ mod tests { assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } + #[test] + fn test_unary_op_literals() { + let attribute = "ensures(result as Field != !15)"; + let state = empty_state(); + let attribute = parse_attribute( + attribute, + Location { span: Span::inclusive(0, attribute.len() as u32), file: Default::default() }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&spanned_typed_expr); + assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); + } + #[test] fn test_cast() { let annotation = "ensures((15 as i16 - 3 > 2) & ((result as Field - 6) as u64 == 1 + a as u64 >> 4 as u8))"; From 8e97ec9f0ec0d1d6572bcd5ccc5a296535b8de20 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 49/86] chore(annotations): remove redundant `.clone()` --- compiler/formal_verification/src/typing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 794391e5a9e..71630a59031 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -569,7 +569,7 @@ pub fn type_infer( expr: Box::new(ExprF::Tuple { exprs: right_exprs }), ann: ( expr_right.ann.0, - OptionalType::Well(NoirType::Tuple(new_exprs_types.clone())), + OptionalType::Well(NoirType::Tuple(new_exprs_types)), ), }; From 9656eda87de1f766f3b2b5b6528b60a12199e363 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 50/86] chore(annotations): remove redundant comment --- compiler/formal_verification/src/typing.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 71630a59031..33a7924eebc 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -836,10 +836,6 @@ pub fn type_infer( ) } ExprF::Tuple { exprs } => { - // TODO: support not-yet-typed expressions in the tuple literals, - // later back-propagating the type inferrence through the projections - // into the tuple - // NOTE: if all sub-expressions are well-typed, produce a well-typed `Tuple` // otherwise, produce a `OptionalType::PartialTuple` let t = exprs From bee644f4acae3f6f8d42cdd98259475a48977570 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 51/86] refactor(annotations): in-place mutation when type-inferring - No longer return new `AnnExpr`s in the `SpannedPartiallyTypedExpr` calculation `type_infer` - Also fix equality checks with tuple variables (complex, check code) - Remove `unify` function (now done inline) --- compiler/formal_verification/src/parse.rs | 7 + compiler/formal_verification/src/typing.rs | 792 +++++++++++---------- 2 files changed, 423 insertions(+), 376 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 327acb02a99..d7bbfb3c04a 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -927,6 +927,13 @@ pub mod tests { NoirType::Tuple(vec![NoirType::Bool, NoirType::Unit]), Visibility::Public, ), + ( + LocalId(5), + false, + "pair".to_string(), + NoirType::Tuple(vec![NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Sixteen), NoirType::Field]), + Visibility::Public, + ), ], body: Expression::Block(vec![]), return_type: NoirType::Integer( diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 33a7924eebc..4456679632e 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -113,166 +113,144 @@ pub fn bi_can_fit_in(bi: &BigInt, hole_size: &IntegerBitSize, hole_sign: &Signed return FitsIn::Yes; } -/// Unifies a partially-typed expression with a target concrete type -/// - If the expression is an `IntegerLiteral`, it's checked and given the target type -/// - If the expression is already `Well`-typed, it checks if the types match -/// - If the expression is a `PartialTuple`, it recursively unifies its elements -pub fn unify_expression_with_type( - expr: SpannedPartiallyTypedExpr, - target_type: NoirType, -) -> Result { - let (location, opt_type) = expr.ann; - let exprf = *expr.expr; +impl SpannedPartiallyTypedExpr { + /// Unifies a partially-typed expression with a target concrete type + /// - If the expression is an `IntegerLiteral`, it's checked and given the target type + /// - If the expression is already `Well`-typed, it checks if the types match + /// - If the expression is a `PartialTuple`, it recursively unifies its elements + pub fn unify_with_type(&mut self, target_type: NoirType) -> Result<(), TypeInferenceError> { + match &self.ann.1 { + OptionalType::IntegerLiteral => match self.expr.as_mut() { + ExprF::Literal { value: Literal::Int(bi) } => { + if let NoirType::Integer(ref sign, ref size) = target_type + && let FitsIn::No { need } = bi_can_fit_in(&bi, size, sign) + { + return Err(TypeInferenceError::IntegerLiteralDoesNotFit { + literal: bi.clone(), + literal_type: target_type.clone(), + fit_into: need.clone(), + location: self.ann.0, + message: format!( + "Integer literal {} cannot fit in {}, needs at least {:?} or larger", + bi, target_type, need + ), + }); + } - match opt_type { - OptionalType::IntegerLiteral => match exprf { - ExprF::Literal { value: Literal::Int(bi) } => { - if let NoirType::Integer(ref sign, ref size) = target_type - && let FitsIn::No { need } = bi_can_fit_in(&bi, size, sign) - { - return Err(TypeInferenceError::IntegerLiteralDoesNotFit { - literal: bi.clone(), - literal_type: target_type.clone(), - fit_into: need.clone(), - location, - message: format!( - "Integer literal {} cannot fit in {}, needs at least {:?} or larger", - bi, target_type, need - ), - }); + eprintln!( + "Setting the type of {:?} to {:?}", + strip_ann(self.clone()), + target_type + ); + + self.ann.1 = OptionalType::Well(target_type); + } + // NOTE: quantified variable + ExprF::Variable(_var) => self.ann.1 = OptionalType::Well(target_type), + // NOTE: not shifting + ExprF::BinaryOp { op: _, expr_left, expr_right } => { + expr_left.unify_with_type(target_type.clone())?; + expr_right.unify_with_type(target_type.clone())?; + self.ann.1 = OptionalType::Well(target_type); } + ExprF::UnaryOp { op, expr } => match op { + UnaryOp::Dereference => { + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: self.ann.1.to_string(), + expr_typ: "Reference".to_string(), + expr_location: self.ann.0, + }, + )); + } + UnaryOp::Not => { + expr.unify_with_type(target_type.clone())?; + self.ann.1 = OptionalType::Well(target_type); + } + }, + ExprF::TupleAccess { expr: inner_tuple_expr, index } => { + let ExprF::Tuple { exprs } = inner_tuple_expr.expr.as_mut() else { + unreachable!() + }; - Ok(SpannedPartiallyTypedExpr { - expr: Box::new(ExprF::Literal { value: Literal::Int(bi) }), - ann: (location, OptionalType::Well(target_type)), - }) - } - // NOTE: quantified variable - ExprF::Variable(var) => Ok(SpannedPartiallyTypedExpr { - ann: (location, OptionalType::Well(target_type)), - expr: Box::new(ExprF::Variable(var)), - }), - ExprF::BinaryOp { op, expr_left, expr_right } => { - let new_left = unify_expression_with_type(expr_left, target_type.clone())?; - let new_right = unify_expression_with_type(expr_right, target_type.clone())?; - let result_type = if op.is_comparison() { NoirType::Bool } else { target_type }; - Ok(SpannedPartiallyTypedExpr { - ann: (location, OptionalType::Well(result_type)), - expr: Box::new(ExprF::BinaryOp { - op, - expr_left: new_left, - expr_right: new_right, - }), - }) - } - ExprF::UnaryOp { op, expr } => { - let new_expr = unify_expression_with_type(expr, target_type.clone())?; - Ok(SpannedPartiallyTypedExpr { - ann: (location, OptionalType::Well(target_type)), - expr: Box::new(ExprF::UnaryOp { op, expr: new_expr }) - }) - } - ExprF::TupleAccess { expr: inner_tuple_expr, index } => { - let ExprF::Tuple { exprs } = *inner_tuple_expr.expr else { unreachable!() }; - - let new_elements = exprs - .into_iter() - .enumerate() - .map(|(i, elem_expr)| { - if i as u32 == index { - unify_expression_with_type(elem_expr, target_type.clone()) - } else if let OptionalType::IntegerLiteral = elem_expr.ann.1 { - // NOTE: Default other integer literals to Field, as they are unconstrained. - unify_expression_with_type(elem_expr, NoirType::Field) - } else { - Ok(elem_expr) - } - }) - .collect::, _>>()?; - - let new_element_types = - new_elements.iter().map(|e| e.ann.1.clone()).collect::>(); - let updated_inner_tuple_type = new_element_types - .iter() - .map(|ot| match ot { - OptionalType::Well(t) => Some(t.clone()), - _ => None, - }) - .collect::>>() - .map(NoirType::Tuple) - .map(OptionalType::Well) - .unwrap_or(OptionalType::PartialTuple(new_element_types)); - - let updated_inner_tuple = SpannedPartiallyTypedExpr { - ann: (inner_tuple_expr.ann.0, updated_inner_tuple_type), - expr: Box::new(ExprF::Tuple { exprs: new_elements }), - }; + let new_element_types = exprs + .into_iter() + .enumerate() + .map(|(i, elem_expr)| { + if i as u32 == *index { + elem_expr.unify_with_type(target_type.clone())?; + } else if matches!(elem_expr.ann.1, OptionalType::IntegerLiteral) { + // NOTE: Default other integer literals to Field, as they are unconstrained. + elem_expr.unify_with_type(NoirType::Field)?; + } - Ok(SpannedPartiallyTypedExpr { - ann: (location, OptionalType::Well(target_type.clone())), - expr: Box::new(ExprF::TupleAccess { expr: updated_inner_tuple, index }), - }) - } - ExprF::Parenthesised { expr: inner_expr } => { - let new_inner = unify_expression_with_type(inner_expr, target_type)?; - Ok(SpannedPartiallyTypedExpr { - ann: new_inner.ann.clone(), - expr: Box::new(ExprF::Parenthesised { expr: new_inner }), - }) - } - _ => unreachable!( - "ICE: Unexpected expression {:?} found with IntegerLiteral type", - exprf - ), - }, + Ok(elem_expr.ann.1.clone()) + }) + .collect::, _>>()?; - OptionalType::Well(well_type) => { - if well_type == target_type { - Ok(SpannedPartiallyTypedExpr { - ann: (location, OptionalType::Well(well_type)), - expr: Box::new(exprf), - }) - } else { - Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { - expected_typ: target_type.to_string(), - expr_typ: well_type.to_string(), - expr_location: location, - })) - } - } + let updated_inner_tuple_type = new_element_types + .iter() + .map(|ot| match ot { + OptionalType::Well(t) => Some(t.clone()), + _ => None, + }) + .collect::>>() + .map(NoirType::Tuple) + .map(OptionalType::Well) + .unwrap_or(OptionalType::PartialTuple(new_element_types)); - OptionalType::PartialTuple(_) => { - let NoirType::Tuple(ref target_expr_types) = target_type else { - return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { - expected_typ: target_type.to_string(), - expr_typ: "tuple".to_string(), - expr_location: location, - })); - }; + inner_tuple_expr.ann.1 = updated_inner_tuple_type; + self.ann.1 = OptionalType::Well(target_type); + } + ExprF::Parenthesised { expr: inner_expr } => { + inner_expr.unify_with_type(target_type.clone())?; + self.ann.1 = OptionalType::Well(target_type); + } + _ => unreachable!( + "ICE: Unexpected expression {:?} found with IntegerLiteral type", + self.expr + ), + }, - let ExprF::Tuple { exprs } = exprf else { unreachable!() }; + OptionalType::Well(well_type) => { + if *well_type != target_type { + return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { + expected_typ: target_type.to_string(), + expr_typ: well_type.to_string(), + expr_location: self.ann.0, + })); + } - if exprs.len() != target_expr_types.len() { - return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TupleMismatch { - tuple_types: vec![], - actual_count: exprs.len(), - location, - })); + self.ann.1 = OptionalType::Well(target_type); } - let new_exprs = exprs - .into_iter() - .zip(target_expr_types) - .map(|(elem_expr, target_elem_type)| { - unify_expression_with_type(elem_expr, target_elem_type.clone()) - }) - .collect::>()?; - - Ok(SpannedPartiallyTypedExpr { - ann: (location, OptionalType::Well(target_type)), - expr: Box::new(ExprF::Tuple { exprs: new_exprs }), - }) + OptionalType::PartialTuple(_) => { + let NoirType::Tuple(ref target_expr_types) = target_type else { + return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { + expected_typ: target_type.to_string(), + expr_typ: "tuple".to_string(), + expr_location: self.ann.0, + })); + }; + + let ExprF::Tuple { exprs } = self.expr.as_mut() else { unreachable!() }; + + if exprs.len() != target_expr_types.len() { + return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TupleMismatch { + tuple_types: vec![], + actual_count: exprs.len(), + location: self.ann.0, + })); + } + + std::iter::zip(exprs, target_expr_types) + .try_for_each(|(expr, t)| expr.unify_with_type(t.clone()))?; + + self.ann.1 = OptionalType::Well(target_type); + } } + + Ok(()) } } @@ -303,54 +281,6 @@ pub fn concretize_type( } } -/// Takes two partially-typed expressions and attempts to make their types equal -/// Information flows from the `Well` typed expression to the `IntegerLiteral` -/// Returns the two (potentially updated) expressions -pub fn unify( - left: SpannedPartiallyTypedExpr, - right: SpannedPartiallyTypedExpr, - default_literal_type: &NoirType, -) -> Result<(SpannedPartiallyTypedExpr, SpannedPartiallyTypedExpr, NoirType), TypeInferenceError> { - match (&left.ann.1, &right.ann.1) { - (OptionalType::Well(t1), OptionalType::Well(t2)) => { - if t1 != t2 { - return Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { - expected_typ: t1.to_string(), - expr_typ: t2.to_string(), - expr_location: right.ann.0, - })); - } - let t = t1.clone(); - Ok((left, right, t)) - } - - (OptionalType::Well(t), OptionalType::IntegerLiteral) => { - let new_right = unify_expression_with_type(right, t.clone())?; - let t = t.clone(); - Ok((left, new_right, t)) - } - (OptionalType::IntegerLiteral, OptionalType::Well(t)) => { - let new_left = unify_expression_with_type(left, t.clone())?; - let t = t.clone(); - Ok((new_left, right, t)) - } - - (OptionalType::IntegerLiteral, OptionalType::IntegerLiteral) => { - let new_left = unify_expression_with_type(left, default_literal_type.clone())?; - let new_right = unify_expression_with_type(right, default_literal_type.clone())?; - Ok((new_left, new_right, default_literal_type.clone())) - } - - // NOTE: All other combinations (e.g., trying to unify a bool with a tuple) are a type mismatch. - // The calling function is responsible for handling recursive cases like tuples. - (t1, t2) => Err(TypeInferenceError::NoirTypeError(TypeCheckError::TypeMismatch { - expected_typ: t1.to_string(), - expr_typ: t2.to_string(), - expr_location: right.ann.0, - })), - } -} - pub fn type_infer( state: &State, expr: SpannedExpr, @@ -377,15 +307,15 @@ pub fn type_infer( } quantifier_bound_variables }, - &|location, quantifier_bound_variables, exprf| { - let (exprf, exprf_type) = match &exprf { + &|location, quantifier_bound_variables, mut exprf| { + let exprf_type = match &mut exprf { ExprF::Literal { value } => match value { - Literal::Bool(_) => (exprf, OptionalType::Well(NoirType::Bool)), + Literal::Bool(_) => OptionalType::Well(NoirType::Bool), Literal::Int(_) => { // NOTE: `OptionalType::IntegerLiteral` signifies that this has to be inferred up the chain, // will gain a concrete type when it gets matched in an (arithmetic or predicate) operation // with a variable with a real (integer) type - (exprf, OptionalType::IntegerLiteral) + OptionalType::IntegerLiteral } }, ExprF::Variable(Variable { path, name, id }) => { @@ -424,14 +354,10 @@ pub fn type_infer( ResolverError::VariableNotDeclared { name: name.to_string(), location }, )))?; - ( - ExprF::Variable(Variable { - path: path.clone(), - name: variable_ident.to_string(), - id: variable_id, - }), - variable_type, - ) + *name = variable_ident.to_string(); + *id = variable_id; + + variable_type } ExprF::FnCall { name, args } => { let return_type = state @@ -448,7 +374,7 @@ pub fn type_infer( }, ))?; - (exprf, OptionalType::Well(return_type.clone())) + OptionalType::Well(return_type.clone()) } ExprF::Quantified { variables, .. } => { variables @@ -468,9 +394,10 @@ pub fn type_infer( } }) .collect::, _>>()?; - (exprf, OptionalType::Well(NoirType::Bool)) + + OptionalType::Well(NoirType::Bool) } - ExprF::Parenthesised { expr } => (exprf.clone(), expr.ann.1.clone()), + ExprF::Parenthesised { expr } => expr.ann.1.clone(), ExprF::UnaryOp { op, expr } => { let t = match op { UnaryOp::Dereference => { @@ -491,129 +418,214 @@ pub fn type_infer( UnaryOp::Not => expr.ann.1.clone(), }; - (exprf.clone(), t) + t } ExprF::BinaryOp { op, expr_left, expr_right } => { - enum TupleTypes { - Well(Vec), - Partial(Vec), - } - impl From for Vec { - fn from(value: TupleTypes) -> Self { - match value { - TupleTypes::Well(types) => { - types.into_iter().map(OptionalType::Well).collect() - } - TupleTypes::Partial(types) => types, - } - } - } - fn is_tupleish(t: &OptionalType) -> Option { - match t { - OptionalType::Well(NoirType::Tuple(types)) => { - Some(TupleTypes::Well(types.clone())) - } - OptionalType::PartialTuple(types) => { - Some(TupleTypes::Partial(types.clone())) - } - _ => None, - } + fn is_tupleish(t: &OptionalType) -> bool { + matches!( + t, + OptionalType::Well(NoirType::Tuple(_)) | OptionalType::PartialTuple(_) + ) } if matches!(op, BinaryOp::Eq | BinaryOp::Neq) - && let (Some(_), Some(_)) = - (is_tupleish(&expr_left.ann.1), is_tupleish(&expr_right.ann.1)) + && is_tupleish(&expr_left.ann.1) + && is_tupleish(&expr_right.ann.1) { - let ExprF::Tuple { exprs: mut left_exprs } = *expr_left.expr.clone() else { - unreachable!() - }; - - let ExprF::Tuple { exprs: mut right_exprs } = *expr_right.expr.clone() - else { - unreachable!() - }; + enum TupleElement<'a> { + Literal(&'a mut SpannedPartiallyTypedExpr, OptionalType), + Variable(NoirType), + } + let left_elements: Vec = + match (expr_left.expr.as_mut(), expr_left.ann.1.clone()) { + ( + ExprF::Tuple { exprs: left_exprs }, + OptionalType::Well(NoirType::Tuple(left_types)), + ) => std::iter::zip(left_exprs, left_types) + .map(|(e, t)| TupleElement::Literal(e, OptionalType::Well(t))) + .collect(), + ( + ExprF::Tuple { exprs: left_exprs }, + OptionalType::PartialTuple(left_types), + ) => std::iter::zip(left_exprs, left_types) + .map(|(e, t)| TupleElement::Literal(e, t)) + .collect(), + ( + ExprF::Variable(_left_var), + OptionalType::Well(NoirType::Tuple(left_types)), + ) => left_types.into_iter().map(TupleElement::Variable).collect(), + ( + ExprF::Variable(_left_var), + OptionalType::PartialTuple(_left_types), + ) => { + unreachable!() + } + _ => { + // TODO(totel): better error? + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TupleMismatch { + location, + tuple_types: vec![], + actual_count: 0, + }, + )); + } + }; + let right_elements: Vec = + match (expr_right.expr.as_mut(), expr_right.ann.1.clone()) { + ( + ExprF::Tuple { exprs: right_exprs }, + OptionalType::Well(NoirType::Tuple(right_types)), + ) => std::iter::zip(right_exprs, right_types) + .map(|(e, t)| TupleElement::Literal(e, OptionalType::Well(t))) + .collect(), + ( + ExprF::Tuple { exprs: right_exprs }, + OptionalType::PartialTuple(right_types), + ) => std::iter::zip(right_exprs, right_types) + .map(|(e, t)| TupleElement::Literal(e, t)) + .collect(), + ( + ExprF::Variable(_right_var), + OptionalType::Well(NoirType::Tuple(right_types)), + ) => right_types.into_iter().map(TupleElement::Variable).collect(), + ( + ExprF::Variable(_right_var), + OptionalType::PartialTuple(_right_types), + ) => { + unreachable!() + } + _ => { + // TODO(totel): better error? + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TupleMismatch { + location, + tuple_types: vec![], + actual_count: 0, + }, + )); + } + }; - if left_exprs.len() != right_exprs.len() { + if left_elements.len() != right_elements.len() { // TODO(totel): better error? return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TupleMismatch { location, tuple_types: vec![], - actual_count: right_exprs.len(), + actual_count: left_elements.len(), }, )); } - let mut new_exprs_types = Vec::with_capacity(left_exprs.len()); - // NOTE: Unify all pair of elements - for i in 0..left_exprs.len() { - let (new_left_expr, new_right_expr, t) = unify( - left_exprs[i].clone(), - right_exprs[i].clone(), - &default_literal_type, - )?; - - (left_exprs[i], right_exprs[i]) = (new_left_expr, new_right_expr); - new_exprs_types.push(t.clone()); - } + let new_exprs_types: Vec<_> = std::iter::zip(left_elements, right_elements) + .map(|(left, right)| { + match (left, right) { + ( + TupleElement::Literal(left_expr, left_type), + TupleElement::Literal(right_expr, right_type), + ) => match (left_type, right_type) { + (OptionalType::Well(t1), OptionalType::Well(t2)) => { + if t1 != t2 { + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: t1.to_string(), + expr_typ: t2.to_string(), + expr_location: right_expr.ann.0, + }, + )); + } + Ok(t1.clone()) + } + (OptionalType::Well(t), OptionalType::IntegerLiteral) => { + right_expr.unify_with_type(t.clone())?; + Ok(t.clone()) + } + (OptionalType::IntegerLiteral, OptionalType::Well(t)) => { + left_expr.unify_with_type(t.clone())?; + Ok(t.clone()) + } + ( + OptionalType::IntegerLiteral, + OptionalType::IntegerLiteral, + ) => { + left_expr + .unify_with_type(default_literal_type.clone())?; + right_expr + .unify_with_type(default_literal_type.clone())?; + Ok(default_literal_type.clone()) + } + // NOTE: All other combinations (e.g., trying to unify a bool with a tuple) are a type mismatch. + // The calling function is responsible for handling recursive cases like tuples. + (t1, t2) => Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: t1.to_string(), + expr_typ: t2.to_string(), + expr_location: right_expr.ann.0, + }, + )), + }, + ( + TupleElement::Literal(left_expr, _left_type), + TupleElement::Variable(right_type), + ) => { + left_expr.unify_with_type(right_type.clone())?; + Ok(right_type) + } + ( + TupleElement::Variable(left_type), + TupleElement::Literal(right_expr, _right_type), + ) => { + right_expr.unify_with_type(left_type.clone())?; + Ok(left_type) + } + ( + TupleElement::Variable(left_type), + TupleElement::Variable(right_type), + ) => { + if left_type != right_type { + return Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TypeMismatch { + expected_typ: right_type.to_string(), + expr_typ: left_type.to_string(), + expr_location: location, + }, + )); + } - let new_expr_left = SpannedPartiallyTypedExpr { - expr: Box::new(ExprF::Tuple { exprs: left_exprs }), - ann: ( - expr_left.ann.0, - OptionalType::Well(NoirType::Tuple(new_exprs_types.clone())), - ), - }; - let new_expr_right = SpannedPartiallyTypedExpr { - expr: Box::new(ExprF::Tuple { exprs: right_exprs }), - ann: ( - expr_right.ann.0, - OptionalType::Well(NoirType::Tuple(new_exprs_types)), - ), - }; + Ok(left_type) + } + } + }) + .collect::, _>>()?; - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: new_expr_left, - expr_right: new_expr_right, - }, - OptionalType::Well(NoirType::Bool), - ) + expr_left.ann.1 = + OptionalType::Well(NoirType::Tuple(new_exprs_types.clone())); + expr_right.ann.1 = OptionalType::Well(NoirType::Tuple(new_exprs_types)); + + OptionalType::Well(NoirType::Bool) } else if op.is_shift() { - let mut new_left = expr_left.clone(); - let mut new_right = expr_right.clone(); let shift_amount_type = NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Eight); - match &new_right.ann.1 { - OptionalType::Well(t) if *t != shift_amount_type => { + match &expr_right.ann.1 { + OptionalType::Well(t) if *t == shift_amount_type => {} // OK + OptionalType::IntegerLiteral => { + expr_right.unify_with_type(shift_amount_type)?; + } + OptionalType::Well(_) | OptionalType::PartialTuple(_) => { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::InvalidShiftSize { location }, )); } - OptionalType::IntegerLiteral => { - new_right = - unify_expression_with_type(new_right, shift_amount_type)?; - } - _ => {} // OK } - if let OptionalType::IntegerLiteral = &new_left.ann.1 { - new_left = - unify_expression_with_type(new_left, default_literal_type.clone())?; + if let OptionalType::IntegerLiteral = &expr_left.ann.1 { + expr_left.unify_with_type(default_literal_type.clone())?; } - let result_type = new_left.ann.1.clone(); - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: new_left, - expr_right: new_right, - }, - result_type, - ) + expr_left.ann.1.clone() } else { match (&expr_left.ann.1, &expr_right.ann.1) { (OptionalType::Well(t1), OptionalType::Well(t2)) => { @@ -626,9 +638,12 @@ pub fn type_infer( }, )); } - let result_type = - if op.is_comparison() { NoirType::Bool } else { t1.clone() }; - (exprf, OptionalType::Well(result_type)) + + OptionalType::Well(if op.is_comparison() { + NoirType::Bool + } else { + t1.clone() + }) } (OptionalType::Well(t1), OptionalType::IntegerLiteral) => { @@ -641,18 +656,14 @@ pub fn type_infer( }, )); } - let new_right = - unify_expression_with_type(expr_right.clone(), t1.clone())?; - let result_type = - if op.is_comparison() { NoirType::Bool } else { t1.clone() }; - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: expr_left.clone(), - expr_right: new_right, - }, - OptionalType::Well(result_type), - ) + + expr_right.unify_with_type(t1.clone())?; + + OptionalType::Well(if op.is_comparison() { + NoirType::Bool + } else { + t1.clone() + }) } (OptionalType::IntegerLiteral, OptionalType::Well(t2)) => { @@ -665,40 +676,24 @@ pub fn type_infer( }, )); } - let new_left = - unify_expression_with_type(expr_left.clone(), t2.clone())?; - let result_type = - if op.is_comparison() { NoirType::Bool } else { t2.clone() }; - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: new_left, - expr_right: expr_right.clone(), - }, - OptionalType::Well(result_type), - ) + + expr_left.unify_with_type(t2.clone())?; + + OptionalType::Well(if op.is_comparison() { + NoirType::Bool + } else { + t2.clone() + }) } (OptionalType::IntegerLiteral, OptionalType::IntegerLiteral) => { if op.is_arithmetic() || op.is_bitwise() { - (exprf, OptionalType::IntegerLiteral) + OptionalType::IntegerLiteral } else { - let new_left = unify_expression_with_type( - expr_left.clone(), - default_literal_type.clone(), - )?; - let new_right = unify_expression_with_type( - expr_right.clone(), - default_literal_type.clone(), - )?; - ( - ExprF::BinaryOp { - op: op.clone(), - expr_left: new_left, - expr_right: new_right, - }, - OptionalType::Well(NoirType::Bool), - ) + expr_left.unify_with_type(default_literal_type.clone())?; + expr_right.unify_with_type(default_literal_type.clone())?; + + OptionalType::Well(NoirType::Bool) } } @@ -716,8 +711,6 @@ pub fn type_infer( } } ExprF::Index { expr, index } => { - let mut index = index.clone(); - let OptionalType::Well(NoirType::Array(_, type_inner)) = &expr.ann.1 else { return Err(TypeInferenceError::NoirTypeError( TypeCheckError::TypeMismatch { @@ -732,8 +725,7 @@ pub fn type_infer( OptionalType::Well(NoirType::Integer(Signedness::Unsigned, _)) => {} // Integer literal, try type inferring to `u32` OptionalType::IntegerLiteral => { - index = - unify_expression_with_type(index, default_literal_type.clone())?; + index.unify_with_type(default_literal_type.clone())?; } // Not fine index t => { @@ -747,10 +739,7 @@ pub fn type_infer( } } - ( - ExprF::Index { expr: expr.clone(), index }, - OptionalType::Well(*type_inner.clone()), - ) + OptionalType::Well(*type_inner.clone()) } ExprF::TupleAccess { expr, index } => { let t = match &expr.ann.1 { @@ -792,11 +781,9 @@ pub fn type_infer( } }; - (exprf.clone(), t) + t } ExprF::Cast { expr, target } => { - let mut expr = expr.clone(); - // Non-booleans cannot cast to bool if matches!(target, NoirType::Bool) && !matches!(expr.ann.1, OptionalType::Well(NoirType::Bool)) @@ -827,13 +814,10 @@ pub fn type_infer( // Try to type infer integer literals as the target type if matches!(expr.ann.1, OptionalType::IntegerLiteral) { - expr = unify_expression_with_type(expr, target.clone())?; + expr.unify_with_type(target.clone())?; } - ( - ExprF::Cast { expr, target: target.clone() }, - OptionalType::Well(target.clone()), - ) + OptionalType::Well(target.clone()) } ExprF::Tuple { exprs } => { // NOTE: if all sub-expressions are well-typed, produce a well-typed `Tuple` @@ -852,7 +836,7 @@ pub fn type_infer( exprs.iter().map(|ae| ae.ann.1.clone()).collect(), )); - (exprf.clone(), t) + t } ExprF::Array { exprs } => { if exprs.is_empty() { @@ -956,19 +940,14 @@ pub fn type_infer( let concrete_element_type = concretize_type(unified_opt_type, &default_literal_type)?; - let new_exprs = exprs + exprs .into_iter() - .map(|expr| { - unify_expression_with_type(expr.clone(), concrete_element_type.clone()) - }) - .collect::, _>>()?; + .try_for_each(|expr| expr.unify_with_type(concrete_element_type.clone()))?; - let array_type = OptionalType::Well(NoirType::Array( - new_exprs.len() as u32, + OptionalType::Well(NoirType::Array( + exprs.len() as u32, Box::new(concrete_element_type), - )); - - (ExprF::Array { exprs: new_exprs }, array_type) + )) } }; @@ -1341,6 +1320,28 @@ mod tests { assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); } + #[test] + fn test_cast_simple() { + let annotation = "ensures(15 == 1 as u8)"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr)); + // assert_eq!(spanned_typed_expr.ann.1, NoirType::Bool); + } + #[test] fn test_cast() { let annotation = "ensures((15 as i16 - 3 > 2) & ((result as Field - 6) as u64 == 1 + a as u64 >> 4 as u8))"; @@ -1428,7 +1429,7 @@ mod tests { } #[test] - fn test_tuple_equality() { + fn test_partial_tuple_equality() { let annotation = "ensures((1 as u8, 15) == (1, 15 as i16))"; let state = empty_state(); let attribute = parse_attribute( @@ -1466,6 +1467,45 @@ mod tests { assert_eq!(expr_left.ann.1, expr_right.ann.1); } + #[test] + fn test_tuple_equality() { + let annotation = "ensures((1, 15) == pair)"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(spanned_expr) = attribute else { panic!() }; + let spanned_typed_expr = type_infer(&state, spanned_expr).unwrap(); + dbg!(&strip_ann(spanned_typed_expr.clone())); + + let ExprF::BinaryOp { op: BinaryOp::Eq, expr_left, expr_right } = *spanned_typed_expr.expr + else { + panic!() + }; + + let ExprF::Tuple { exprs: _ } = *expr_left.expr else { panic!() }; + let ExprF::Variable(_) = *expr_right.expr else { panic!() }; + + // Assert that both tuple types are fully unified + assert_eq!( + expr_left.ann.1, + NoirType::Tuple(vec![ + NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Sixteen), + NoirType::Field, + ]) + ); + assert_eq!(expr_left.ann.1, expr_right.ann.1); + } + #[test] fn test_array_equality() { let annotation = "ensures([(1, 2), (1 as u8, 7), (8, 9 as i16)] == [(0, 0), (1 as u8, 2 as i16), (0, 0)])"; From 40047f173f577974c62a8ec037963e1ab101428c Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 52/86] fix(annotations): `empty_state` ids for testing --- compiler/formal_verification/src/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index d7bbfb3c04a..19136a9e1a1 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -968,7 +968,7 @@ pub mod tests { .into_iter() .collect(), )), - min_local_id: Box::leak(Box::new(5)), + min_local_id: Box::leak(Box::new(6)), } } From f2dd3929115da717bab0fcac41229a392557e420 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 53/86] chore(annotations): remove old `eprintln!` call - Was used for debugging purposes --- compiler/formal_verification/src/typing.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 4456679632e..f854ef1cf60 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -137,12 +137,6 @@ impl SpannedPartiallyTypedExpr { }); } - eprintln!( - "Setting the type of {:?} to {:?}", - strip_ann(self.clone()), - target_type - ); - self.ann.1 = OptionalType::Well(target_type); } // NOTE: quantified variable From 505f4bcf3bb7bee37e528e3b17057d18dd5ec7ae Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 54/86] chore(annotations): expand the catch-all case in a `match` --- compiler/formal_verification/src/typing.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index f854ef1cf60..32e814e4299 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -200,7 +200,14 @@ impl SpannedPartiallyTypedExpr { inner_expr.unify_with_type(target_type.clone())?; self.ann.1 = OptionalType::Well(target_type); } - _ => unreachable!( + + ExprF::Literal { value: Literal::Bool(..) } + | ExprF::Quantified { .. } + | ExprF::FnCall { .. } + | ExprF::Index { .. } + | ExprF::Cast { .. } + | ExprF::Tuple { .. } + | ExprF::Array { .. } => unreachable!( "ICE: Unexpected expression {:?} found with IntegerLiteral type", self.expr ), From 8cb5da475bb6cee694aed66d212207121c8fec83 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 55/86] chore(annotations): remove unused imports --- compiler/formal_verification/src/typing.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 32e814e4299..83ad7e93c50 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -1,10 +1,10 @@ -use std::{convert::identity, fmt::Display}; +use std::fmt::Display; use crate::{ MonomorphizationRequest, State, ast::{ - AnnExpr, BinaryOp, ExprF, Literal, SpannedExpr, SpannedTypedExpr, UnaryOp, Variable, cata, - strip_ann, try_cata, try_contextual_cata, + AnnExpr, BinaryOp, ExprF, Literal, SpannedExpr, SpannedTypedExpr, UnaryOp, Variable, + try_cata, try_contextual_cata, }, }; use noirc_errors::Location; From 635f6173c485b81803646397eefe838c8ea142fb Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 56/86] refactor(annotations): deduplicate `tuple_elements_from_expr` - Add `tuple_elements_from_expr` function Co-authored-by: Aristotelis Papanis --- compiler/formal_verification/src/typing.rs | 109 +++++++++------------ 1 file changed, 45 insertions(+), 64 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 83ad7e93c50..301a1212e99 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -437,76 +437,57 @@ pub fn type_infer( Literal(&'a mut SpannedPartiallyTypedExpr, OptionalType), Variable(NoirType), } - let left_elements: Vec = - match (expr_left.expr.as_mut(), expr_left.ann.1.clone()) { + fn tuple_elements_from_expr<'a>( + expr: &'a mut ExprF, + ann: OptionalType, + location: Location, + ) -> Result>, TypeInferenceError> + { + match (expr, ann) { ( - ExprF::Tuple { exprs: left_exprs }, - OptionalType::Well(NoirType::Tuple(left_types)), - ) => std::iter::zip(left_exprs, left_types) + ExprF::Tuple { exprs }, + OptionalType::Well(NoirType::Tuple(types)), + ) => Ok(std::iter::zip(exprs, types) .map(|(e, t)| TupleElement::Literal(e, OptionalType::Well(t))) - .collect(), - ( - ExprF::Tuple { exprs: left_exprs }, - OptionalType::PartialTuple(left_types), - ) => std::iter::zip(left_exprs, left_types) - .map(|(e, t)| TupleElement::Literal(e, t)) - .collect(), - ( - ExprF::Variable(_left_var), - OptionalType::Well(NoirType::Tuple(left_types)), - ) => left_types.into_iter().map(TupleElement::Variable).collect(), - ( - ExprF::Variable(_left_var), - OptionalType::PartialTuple(_left_types), - ) => { - unreachable!() - } - _ => { - // TODO(totel): better error? - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::TupleMismatch { - location, - tuple_types: vec![], - actual_count: 0, - }, - )); + .collect()), + + (ExprF::Tuple { exprs }, OptionalType::PartialTuple(types)) => { + Ok(std::iter::zip(exprs, types) + .map(|(e, t)| TupleElement::Literal(e, t)) + .collect()) } - }; - let right_elements: Vec = - match (expr_right.expr.as_mut(), expr_right.ann.1.clone()) { - ( - ExprF::Tuple { exprs: right_exprs }, - OptionalType::Well(NoirType::Tuple(right_types)), - ) => std::iter::zip(right_exprs, right_types) - .map(|(e, t)| TupleElement::Literal(e, OptionalType::Well(t))) - .collect(), - ( - ExprF::Tuple { exprs: right_exprs }, - OptionalType::PartialTuple(right_types), - ) => std::iter::zip(right_exprs, right_types) - .map(|(e, t)| TupleElement::Literal(e, t)) - .collect(), - ( - ExprF::Variable(_right_var), - OptionalType::Well(NoirType::Tuple(right_types)), - ) => right_types.into_iter().map(TupleElement::Variable).collect(), + ( - ExprF::Variable(_right_var), - OptionalType::PartialTuple(_right_types), - ) => { + ExprF::Variable(_), + OptionalType::Well(NoirType::Tuple(types)), + ) => Ok(types.into_iter().map(TupleElement::Variable).collect()), + + (ExprF::Variable(_), OptionalType::PartialTuple(_)) => { + // Unreachable because if we have a variable of type tuple we + // would always know its type. unreachable!() } - _ => { - // TODO(totel): better error? - return Err(TypeInferenceError::NoirTypeError( - TypeCheckError::TupleMismatch { - location, - tuple_types: vec![], - actual_count: 0, - }, - )); - } - }; + + _ => Err(TypeInferenceError::NoirTypeError( + TypeCheckError::TupleMismatch { + location, + tuple_types: vec![], + actual_count: 0, + }, + )), + } + } + + let left_elements: Vec = tuple_elements_from_expr( + expr_left.expr.as_mut(), + expr_left.ann.1.clone(), + location, + )?; + let right_elements: Vec = tuple_elements_from_expr( + expr_right.expr.as_mut(), + expr_right.ann.1.clone(), + location, + )?; if left_elements.len() != right_elements.len() { // TODO(totel): better error? From 9df7b173c66c8b9c2b90078cc62fe0db6a53f261 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Mon, 4 Aug 2025 12:13:55 +0300 Subject: [PATCH 57/86] feat(monomorphizer): Make `queue_function` public --- compiler/noirc_frontend/src/monomorphization/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 4bd5c345ffb..1b56cb9d5d1 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -1892,7 +1892,7 @@ impl<'interner> Monomorphizer<'interner> { Expression::Literal(Literal::Slice(arr_literal)) } - fn queue_function( + pub fn queue_function( &mut self, id: node_interner::FuncId, expr_id: node_interner::ExprId, From d465ff1bc3938c5fa6c874876d808db357d044e0 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Tue, 5 Aug 2025 17:05:36 +0300 Subject: [PATCH 58/86] feat(monomorph): Process monomorphization requests Now when we receive a monomorphization request from the type inference function we process it. We attach ghost attributes to that function because only ghost functions can be called in FV annotations. Also implemented type conversion between the noirc_frontend AST types and monomorph AST types. This will help in the future for better error reporting Added a test with generics which now successfully verifies. --- compiler/formal_verification/src/lib.rs | 1 + .../src/type_conversion.rs | 146 ++++++++ compiler/formal_verification/src/typing.rs | 12 + compiler/fv_bridge/src/lib.rs | 332 ++++++++++++++---- .../generics_basic/Nargo.toml | 6 + .../generics_basic/src/main.nr | 8 + 6 files changed, 438 insertions(+), 67 deletions(-) create mode 100644 compiler/formal_verification/src/type_conversion.rs create mode 100644 test_programs/formal_verify_success/generics_basic/Nargo.toml create mode 100644 test_programs/formal_verify_success/generics_basic/src/main.nr diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 7010267dad5..301150ee972 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -12,6 +12,7 @@ use crate::{ pub mod ast; pub mod parse; pub mod typing; +pub mod type_conversion; #[derive(Debug)] pub struct State<'a> { diff --git a/compiler/formal_verification/src/type_conversion.rs b/compiler/formal_verification/src/type_conversion.rs new file mode 100644 index 00000000000..f2a9d4b9d88 --- /dev/null +++ b/compiler/formal_verification/src/type_conversion.rs @@ -0,0 +1,146 @@ +use noirc_frontend::{Kind, Type as NoirType, monomorphization::ast::Type as MastType}; + +pub fn convert_mast_to_noir_type(mast_type: MastType) -> NoirType { + match mast_type { + MastType::Field => NoirType::FieldElement, + MastType::Array(len, element_type) => { + // In noirc_frontend, the length of an array is a type itself. + // We represent the concrete length from MAST as a `Type::Constant`. + let length_type = Box::new(NoirType::Constant(len.into(), Kind::Normal)); + let converted_element_type = Box::new(convert_mast_to_noir_type(*element_type)); + NoirType::Array(length_type, converted_element_type) + } + MastType::Integer(sign, bits) => NoirType::Integer(sign, bits), + MastType::Bool => NoirType::Bool, + MastType::String(len) => { + // Similar to arrays, the string length is a `Type::Constant`. + let length_type = Box::new(NoirType::Constant(len.into(), Kind::Normal)); + NoirType::String(length_type) + } + MastType::FmtString(len, elements_type) => { + let length_type = Box::new(NoirType::Constant(len.into(), Kind::Normal)); + let converted_elements_type = Box::new(convert_mast_to_noir_type(*elements_type)); + NoirType::FmtString(length_type, converted_elements_type) + } + MastType::Unit => NoirType::Unit, + MastType::Tuple(mast_elements) => { + // Recursively convert each type within the tuple. + let noir_elements = mast_elements.into_iter().map(convert_mast_to_noir_type).collect(); + NoirType::Tuple(noir_elements) + } + MastType::Slice(element_type) => { + // Recursively convert the slice's element type. + let converted_element_type = Box::new(convert_mast_to_noir_type(*element_type)); + NoirType::Slice(converted_element_type) + } + MastType::Reference(element_type, mutable) => { + let converted_element_type = Box::new(convert_mast_to_noir_type(*element_type)); + NoirType::Reference(converted_element_type, mutable) + } + MastType::Function(args, ret, env, unconstrained) => { + // Recursively convert all function components: arguments, return type, and environment. + let noir_args = args.into_iter().map(convert_mast_to_noir_type).collect(); + let noir_ret = Box::new(convert_mast_to_noir_type(*ret)); + let noir_env = Box::new(convert_mast_to_noir_type(*env)); + NoirType::Function(noir_args, noir_ret, noir_env, unconstrained) + } + } +} + +#[cfg(test)] +mod tests { + use super::convert_mast_to_noir_type; + use noirc_frontend::ast::IntegerBitSize; + use noirc_frontend::shared::Signedness; + use noirc_frontend::{Kind, Type as NoirType, monomorphization::ast::Type as MastType}; + + #[test] + fn test_convert_field() { + let mast_type = MastType::Field; + let expected_noir_type = NoirType::FieldElement; + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } + + #[test] + fn test_convert_integer() { + let mast_type = MastType::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); + let expected_noir_type = NoirType::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } + + #[test] + fn test_convert_bool() { + let mast_type = MastType::Bool; + let expected_noir_type = NoirType::Bool; + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } + + #[test] + fn test_convert_unit() { + let mast_type = MastType::Unit; + let expected_noir_type = NoirType::Unit; + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } + + #[test] + fn test_convert_string() { + let mast_type = MastType::String(10); + let expected_noir_type = + NoirType::String(Box::new(NoirType::Constant(10u32.into(), Kind::Normal))); + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } + + #[test] + fn test_convert_array() { + let mast_type = MastType::Array(5, Box::new(MastType::Field)); + let expected_noir_type = NoirType::Array( + Box::new(NoirType::Constant(5u32.into(), Kind::Normal)), + Box::new(NoirType::FieldElement), + ); + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } + + #[test] + fn test_convert_slice() { + let mast_type = MastType::Slice(Box::new(MastType::Bool)); + let expected_noir_type = NoirType::Slice(Box::new(NoirType::Bool)); + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } + + #[test] + fn test_convert_tuple() { + let mast_type = MastType::Tuple(vec![MastType::Field, MastType::Bool]); + let expected_noir_type = NoirType::Tuple(vec![NoirType::FieldElement, NoirType::Bool]); + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } + + #[test] + fn test_convert_reference() { + // Immutable reference + let mast_imm_ref = MastType::Reference(Box::new(MastType::Field), false); + let expected_imm_ref = NoirType::Reference(Box::new(NoirType::FieldElement), false); + assert_eq!(convert_mast_to_noir_type(mast_imm_ref), expected_imm_ref); + + // Mutable reference + let mast_mut_ref = MastType::Reference(Box::new(MastType::Field), true); + let expected_mut_ref = NoirType::Reference(Box::new(NoirType::FieldElement), true); + assert_eq!(convert_mast_to_noir_type(mast_mut_ref), expected_mut_ref); + } + + #[test] + fn test_convert_function() { + let mast_type = MastType::Function( + vec![MastType::Field, MastType::Bool], + Box::new(MastType::Unit), + Box::new(MastType::Tuple(vec![])), + false, + ); + let expected_noir_type = NoirType::Function( + vec![NoirType::FieldElement, NoirType::Bool], + Box::new(NoirType::Unit), + Box::new(NoirType::Tuple(vec![])), + false, + ); + assert_eq!(convert_mast_to_noir_type(mast_type), expected_noir_type); + } +} diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 301a1212e99..4b937b60329 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -37,6 +37,18 @@ impl Display for OptionalType { } } +impl OptionalType { + pub fn unwrap_or(self, or_arg: NoirType) -> NoirType { + match self { + OptionalType::Well(noir_typ) => noir_typ, + OptionalType::IntegerLiteral => or_arg, + OptionalType::PartialTuple(_optional_types) => { + unreachable!("Partial types must have been resolved") + } + } + } +} + pub type SpannedPartiallyTypedExpr = AnnExpr<(Location, OptionalType)>; #[derive(Debug, Clone)] diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 83684cfa6a7..87076cd9caf 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -1,18 +1,22 @@ -use std::collections::{BTreeMap, HashMap}; - use fm::FileId; -use formal_verification::ast::SpannedTypedExpr; -use formal_verification::typing::type_infer; +use formal_verification::ast::{AnnExpr, SpannedTypedExpr}; +use formal_verification::type_conversion::convert_mast_to_noir_type; +use formal_verification::typing::{OptionalType, TypeInferenceError, type_infer}; use formal_verification::{State, parse::parse_attribute}; use iter_extended::vecmap; use noirc_driver::{CompilationResult, CompileError, CompileOptions, check_crate}; use noirc_errors::CustomDiagnostic; +use noirc_errors::Location; use noirc_evaluator::vir::vir_gen::Attribute; use noirc_evaluator::{ errors::{RuntimeError, SsaReport}, vir::{create_verus_vir_with_ready_annotations, vir_gen::BuildingKrateError}, }; +use noirc_frontend::Kind; +use noirc_frontend::hir_def::expr::HirCallExpression; +use noirc_frontend::hir_def::expr::{HirExpression, HirLiteral}; use noirc_frontend::monomorphization::ast::LocalId; +use noirc_frontend::node_interner::ExprId; use noirc_frontend::{ debug::DebugInstrumenter, graph::CrateId, @@ -28,6 +32,7 @@ use noirc_frontend::{ parser::ParserError, token::SecondaryAttributeKind, }; +use std::collections::{BTreeMap, HashMap, HashSet}; use vir::ast::Krate; use crate::errors::{CompilationErrorBundle, MonomorphizationErrorBundle}; @@ -148,7 +153,7 @@ fn modified_monomorphize( force_unconstrained: bool, ) -> Result<(Program, Vec<(FuncId, Vec)>), MonomorphizationErrorBundle> { let debug_type_tracker = DebugTypeTracker::build_from_debug_instrumenter(debug_instrumenter); - // TODO(totel): Monomorphizer is a `pub(crate)` struct + // NOTE: Monomorphizer is a `pub(crate)` struct which we changed to pub let mut monomorphizer = Monomorphizer::new(interner, debug_type_tracker); monomorphizer.in_unconstrained_function = force_unconstrained; let function_sig = monomorphizer @@ -192,80 +197,102 @@ fn modified_monomorphize( }) .collect(); - let globals = monomorphizer.finished_globals.into_iter().collect::>(); + // Clone because of the borrow checker + let globals = monomorphizer.finished_globals.clone().into_iter().collect::>(); let mut min_available_id: u32 = monomorphizer.locals.values().map(|LocalId(id)| *id).max().unwrap_or_default() + 1; - let fv_annotations: Vec<(FuncId, Vec)> = monomorphizer + let functions_to_process: Vec<(FuncId, node_interner::FuncId)> = monomorphizer .finished_functions - .iter() + .keys() .rev() - // Find the original function ID for each new monomorphized function. - .filter_map(|(new_func_id, function)| { - new_ids_to_old_ids.get(new_func_id).map(|old_id| (*new_func_id, *old_id, function)) + .copied() + .filter_map(|new_func_id| { + new_ids_to_old_ids.get(&new_func_id).map(|old_id| (new_func_id, *old_id)) }) - .map(|(new_func_id, old_id, function)| -> Result<_, MonomorphizationErrorBundle> { - // Create the state once per function to avoid repeated lookups inside a loop. - let state = State { - function, + .collect(); + + let mut fv_annotations = Vec::with_capacity(functions_to_process.len()); + // Functions which get resolved via a MonomorphRequest we have to + // manually add ghost attributes to them. + let mut to_be_added_ghost_attribute: HashSet = HashSet::new(); + + for (new_func_id, old_id) in functions_to_process { + let attribute_data: Vec<_> = monomorphizer + .interner + .function_attributes(&old_id) + .secondary + .iter() + .filter_map(|attribute| { + if let SecondaryAttributeKind::Tag(annotation) = &attribute.kind { + Some((annotation.as_str().to_owned(), attribute.location)) + } else { + None + } + }) + .collect(); + + let mut processed_attributes = Vec::new(); + + for (annotation_body, location) in attribute_data { + let function_for_parser = &monomorphizer.finished_functions[&new_func_id]; + + let parsed_attribute = parse_attribute( + &annotation_body, + location, + function_for_parser, + &globals, + &monomorphizer.finished_functions, + ) + .map_err(|e| MonomorphizationErrorBundle::ParserErrors(e.0))?; + // Ghost functions always get a monomorphization request because + // they are not part of the monomorphizer.finished_functions + let typed_attribute = match parsed_attribute { + formal_verification::Attribute::Ghost => TypedAttribute::Ghost, + formal_verification::Attribute::Ensures(expr) => { + let typed_expr = type_infer_attribute_expr( + &mut monomorphizer, + new_func_id, + &globals, + &mut min_available_id, + expr, + &mut new_ids_to_old_ids, + &mut to_be_added_ghost_attribute, + )?; + TypedAttribute::Ensures(typed_expr) + } + formal_verification::Attribute::Requires(expr) => { + let typed_expr = type_infer_attribute_expr( + &mut monomorphizer, + new_func_id, + &globals, + &mut min_available_id, + expr, + &mut new_ids_to_old_ids, + &mut to_be_added_ghost_attribute, + )?; + TypedAttribute::Requires(typed_expr) + } + }; + + let final_state = State { + function: &monomorphizer.finished_functions[&new_func_id], global_constants: &globals, functions: &monomorphizer.finished_functions, - min_local_id: &mut min_available_id + min_local_id: &mut min_available_id, }; - let attributes = monomorphizer - .interner - .function_attributes(&old_id) - .secondary - .iter() - // Extract only the string-based 'tag' attributes for processing. - .filter_map(|attribute| { - if let SecondaryAttributeKind::Tag(annotation) = &attribute.kind { - Some((annotation.as_str(), attribute.location)) - } else { - None - } - }) - .map(|(annotation_body, location)| -> Result<_, MonomorphizationErrorBundle> { - // Step 1: Parse the attribute string. - let parsed_attribute = parse_attribute( - annotation_body, - location, - function, - &globals, - &monomorphizer.finished_functions, - ) - .map_err(|e| MonomorphizationErrorBundle::ParserErrors(e.0))?; - - // Step 2: Type-infer the parsed attribute expression. - let typed_attribute = match parsed_attribute { - formal_verification::Attribute::Ghost => TypedAttribute::Ghost, - formal_verification::Attribute::Ensures(expr) => { - // TODO(totel): Handle MonomorphRequest error type - let typed_expr = type_infer(&state, expr) - .map_err(|e| MonomorphizationErrorBundle::from(e))?; - TypedAttribute::Ensures(typed_expr) - } - formal_verification::Attribute::Requires(expr) => { - // TODO(totel): Handle MonomorphRequest error type - let typed_expr = type_infer(&state, expr) - .map_err(|e| MonomorphizationErrorBundle::from(e))?; - TypedAttribute::Requires(typed_expr) - } - }; - - // Step 3: Convert the typed attribute into its final representation. - Ok(convert_typed_attribute_to_vir_attribute( - typed_attribute, - &state, - )) - }) - .collect::, _>>()?; + processed_attributes + .push(convert_typed_attribute_to_vir_attribute(typed_attribute, &final_state)); + } - Ok((new_func_id, attributes)) - }) - .collect::, _>>()?; + fv_annotations.push((new_func_id, processed_attributes)); + } + + to_be_added_ghost_attribute.into_iter().for_each(|func_id| { + fv_annotations.push((func_id, vec![Attribute::Ghost])); + }); let functions = vecmap(monomorphizer.finished_functions, |(_, f)| f); @@ -291,3 +318,174 @@ pub struct KrateAndWarnings { pub warnings: Vec, pub parse_annotations_errors: Vec, } + +// Helper function using a bounded for-loop for safer retries. +/// Does type inferring and processes monomorphization requests. +/// Returns the typed attribute expression and a flag if monomorphization +/// request was processed. +fn type_infer_attribute_expr( + monomorphizer: &mut Monomorphizer, + new_func_id: FuncId, + globals: &BTreeMap< + noirc_frontend::monomorphization::ast::GlobalId, + ( + String, + noirc_frontend::monomorphization::ast::Type, + noirc_frontend::monomorphization::ast::Expression, + ), + >, + min_available_id: &mut u32, + expr: AnnExpr, + new_ids_to_old_ids: &mut HashMap, + to_be_added_ghost_attribute: &mut HashSet, +) -> Result { + // Set a reasonable limit to prevent infinite loops in case of a bug. + const MAX_RETRIES: u32 = 100; + // TODO(totel): Check if a monomorphization request was send for the same function twice + // This will indicate that we have reached an infinite recursion point. (Some would call it a fix point) + for _ in 0..MAX_RETRIES { + // The following two variables are defined inside of the `for` loop + // because of the borrow checker. + let function = &monomorphizer.finished_functions[&new_func_id]; + let state = State { + function, + global_constants: &globals, + functions: &monomorphizer.finished_functions, + min_local_id: min_available_id, + }; + + match type_infer(&state, expr.clone()) { + Ok(typed_expr) => { + // Success, return immediately. + return Ok(typed_expr); + } + Err(type_error) => match type_error { + TypeInferenceError::MonomorphizationRequest(request) => { + // This is a recoverable error. Try to resolve it. + monomorphize_one_function( + &request.function_identifier, + request.param_types, + monomorphizer, + new_ids_to_old_ids, + to_be_added_ghost_attribute, + )?; + // After monomorphizing the function try to type infer again. + } + other_error => { + // This is an unrecoverable error, return immediately. + return Err(MonomorphizationErrorBundle::from(other_error)); + } + }, + } + } + + // If the loop finishes, we've exceeded the retry limit. This indicates a likely bug. + // TODO(totel): Define a better error + panic!("Monomorphization limit reached") +} + +fn monomorphize_one_function( + func_name: &str, + param_types: Vec, + monomorphizer: &mut Monomorphizer, + new_ids_to_old_ids: &mut HashMap, + to_be_added_ghost_attribute: &mut HashSet, +) -> Result<(), MonomorphizationErrorBundle> { + let func_id = monomorphizer.interner.find_function(func_name).expect(&format!( + "The provided function name {}, was not found during the completion of MonomorphRequest", + func_name + )); + let func_id_as_expr_id = monomorphizer.interner.function(&func_id).as_expr(); + + let pseudo_args: Vec = std::iter::repeat_with(|| { + monomorphizer.interner.push_expr_full( + HirExpression::Literal(HirLiteral::Bool(true)), + Location::dummy(), + noirc_frontend::Type::Bool, + ) + }) + .take(param_types.len()) + .collect(); + + let pseudo_call_expr = HirExpression::Call(HirCallExpression { + func: func_id_as_expr_id, + arguments: pseudo_args, + location: Location::dummy(), + is_macro_call: false, + }); + + let pseudo_call_expr_id = monomorphizer.interner.push_expr_full( + pseudo_call_expr, + Location::dummy(), + noirc_frontend::Type::Unit, + ); + + let mut typ_bindings = noirc_frontend::Type::Unit.instantiate(&monomorphizer.interner).1; + + // Bind generic types to the type used in the function call + monomorphizer + .interner + .function_meta(&func_id) + .parameters + .0 + .iter() + .map(|(_pattern, typ, _visibility)| typ) + .enumerate() + .filter_map(|(pos, typ)| match typ { + noirc_frontend::Type::NamedGeneric(named_generic) => { + Some((pos, &named_generic.type_var)) + } + noirc_frontend::Type::TypeVariable(type_var) => Some((pos, type_var)), + _ => None, + }) + .for_each(|(param_index, type_var)| { + // The last argument of method `.insert` is the important one + typ_bindings.insert( + type_var.id(), + ( + type_var.clone(), + Kind::Normal, + convert_mast_to_noir_type( + param_types[param_index] + .clone() + .unwrap_or(noirc_frontend::monomorphization::ast::Type::Field), + ), + ), + ); + }); + + monomorphizer.interner.store_instantiation_bindings(pseudo_call_expr_id, typ_bindings); + + // NOTE: `queue_function` was made public by us + monomorphizer.queue_function( + func_id, + pseudo_call_expr_id, + monomorphizer.interner.id_type(func_id_as_expr_id), + vec![], + None, + ); + + while !monomorphizer.queue.is_empty() { + let (next_fn_id, new_id, bindings, trait_method, is_unconstrained, location) = + monomorphizer.queue.pop_front().unwrap(); + monomorphizer.locals.clear(); + + monomorphizer.in_unconstrained_function = is_unconstrained; + + perform_instantiation_bindings(&bindings); + let interner = &monomorphizer.interner; + let impl_bindings = perform_impl_bindings(interner, trait_method, next_fn_id, location) + .map_err(MonomorphizationError::InterpreterError) + .map_err(MonomorphizationErrorBundle::MonomorphizationError)?; + + monomorphizer + .function(next_fn_id, new_id, location) + .map_err(MonomorphizationErrorBundle::MonomorphizationError)?; + new_ids_to_old_ids.insert(new_id, next_fn_id); + undo_instantiation_bindings(impl_bindings); + undo_instantiation_bindings(bindings); + to_be_added_ghost_attribute.insert(new_id); + } + + Ok(()) +} diff --git a/test_programs/formal_verify_success/generics_basic/Nargo.toml b/test_programs/formal_verify_success/generics_basic/Nargo.toml new file mode 100644 index 00000000000..e795dcdebf7 --- /dev/null +++ b/test_programs/formal_verify_success/generics_basic/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "generics_testing_ground" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/formal_verify_success/generics_basic/src/main.nr b/test_programs/formal_verify_success/generics_basic/src/main.nr new file mode 100644 index 00000000000..7ae15302c34 --- /dev/null +++ b/test_programs/formal_verify_success/generics_basic/src/main.nr @@ -0,0 +1,8 @@ +#['requires(generic_func(x, x))] +fn main(x: u32) { +} + +#['ghost] +fn generic_func(x: T, y: u32) -> bool { + true +} From 7ad2ee0fda773411d8560c8655a4e5b53c6a3295 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 59/86] feat(annotations): assign `LocalId`s to quantified variables - Change the type of `State`'s `min_local_id` to a `Rc` of a `RefCell`, since we want to modify it without having to mutate the parent `State` (can be done more cleanly with a `Counter` struct, which does the mutation inside and only exposes a `gen_id` function or similar) - Change `try_contextual_cata` to pass the updated context to the current expression (needed to generating the quantified variables' ids early enough) - Now the following tests also pass - `tests::formal_verify_success_exists_big_element_sum` - `tests::formal_verify_success_exists_zero_in_array` - `tests::formal_verify_success_forall_max_is_max` - `tests::formal_verify_success_forall_sum_of_evens` --- compiler/formal_verification/src/ast.rs | 6 +-- compiler/formal_verification/src/lib.rs | 6 +-- compiler/formal_verification/src/parse.rs | 4 +- compiler/formal_verification/src/typing.rs | 55 ++++++++++++++++++---- compiler/fv_bridge/src/lib.rs | 15 +++--- 5 files changed, 65 insertions(+), 21 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index fd3bbfc408e..12c324f966e 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -227,13 +227,13 @@ where where C: Clone, { - let children_context = update_context(context.clone(), &expr); + let new_context = update_context(context, &expr); let children_results = try_fmap(*expr.expr, &|child_expr| { - recurse(child_expr, children_context.clone(), update_context, algebra) + recurse(child_expr, new_context.clone(), update_context, algebra) })?; - algebra(expr.ann, context, children_results) + algebra(expr.ann, new_context, children_results) } recurse(expr, initial_context, update_context, algebra) diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 301150ee972..5bbaa4eeb4f 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -1,7 +1,7 @@ use noirc_errors::Location; use noirc_frontend::monomorphization::ast as mast; +use std::{cell::RefCell, collections::BTreeMap, fmt::Debug, rc::Rc}; use typing::OptionalType; -use std::{collections::BTreeMap, fmt::Debug}; use crate::{ ast::{OffsetExpr, SpannedExpr, cata}, @@ -11,15 +11,15 @@ use crate::{ // NOTE: all types inside are not prefixed, to be used as `ast::OffsetExpr` pub mod ast; pub mod parse; -pub mod typing; pub mod type_conversion; +pub mod typing; #[derive(Debug)] pub struct State<'a> { pub function: &'a mast::Function, pub global_constants: &'a BTreeMap, pub functions: &'a BTreeMap, - pub min_local_id: &'a mut u32, + pub min_local_id: Rc>, } #[derive(Debug, Clone)] diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 19136a9e1a1..56a7c6593e2 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -865,6 +865,8 @@ pub(crate) fn parse_identifier<'a>(input: Input<'a>) -> PResult<'a, &'a str> { #[cfg(test)] pub mod tests { + use std::{cell::RefCell, rc::Rc}; + use noirc_frontend::{ monomorphization::ast::{ Expression, FuncId, Function, InlineType, LocalId, Type as NoirType, @@ -968,7 +970,7 @@ pub mod tests { .into_iter() .collect(), )), - min_local_id: Box::leak(Box::new(6)), + min_local_id: Rc::new(RefCell::new(6)), } } diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 4b937b60329..0e9d9115532 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -1,9 +1,9 @@ -use std::fmt::Display; +use std::{convert::identity, fmt::Display, ops::AddAssign}; use crate::{ MonomorphizationRequest, State, ast::{ - AnnExpr, BinaryOp, ExprF, Literal, SpannedExpr, SpannedTypedExpr, UnaryOp, Variable, + AnnExpr, BinaryOp, ExprF, Literal, SpannedExpr, SpannedTypedExpr, UnaryOp, Variable, cata, try_cata, try_contextual_cata, }, }; @@ -314,7 +314,11 @@ pub fn type_infer( // NOTE: quantified variables should have no path debug_assert_eq!(path.len(), 0); - name.clone() + // NOTE:: reserve an `id` for this variable + let id = state.min_local_id.borrow().clone(); + state.min_local_id.borrow_mut().add_assign(1); + + (id, name.clone()) }, )); } @@ -342,10 +346,10 @@ pub fn type_infer( OptionalType, ) = quantifier_bound_variables .iter() - .find_map(|bound_variable| { - // TODO: `id` not `None` (when we have a way to generate new `id`s) - (bound_variable == name) - .then(|| (name.as_str(), None, OptionalType::IntegerLiteral)) + .find_map(|(id, bound_variable)| { + (bound_variable == name).then(|| { + (name.as_str(), Some(id.clone()), OptionalType::IntegerLiteral) + }) }) .or_else(|| { state.function.parameters.iter().find_map(|(id, _, par_name, t, _)| { @@ -408,6 +412,18 @@ pub fn type_infer( }) .collect::, _>>()?; + // NOTE: add predertimed `id`s to the quantified variables + variables.iter_mut().for_each(|variable| { + variable.id = Some( + *quantifier_bound_variables + .iter() + .find_map(|(id, bound_variable)| { + (*bound_variable == variable.name).then(|| id) + }) + .expect("Should have been populated while traversing down the AST"), + ); + }); + OptionalType::Well(NoirType::Bool) } ExprF::Parenthesised { expr } => expr.ann.1.clone(), @@ -958,7 +974,30 @@ pub fn type_infer( }) .expect("Typing should have either succeeded or have resulted in an expected error"); - // TODO: `assert!` that only the `FUNC_RETURN_VAR_NAME` + // NOTE: only the `FUNC_RETURN_VAR_NAME` variable should have no id + assert!(cata(fully_typed_expr.clone(), &|ann, exprf| match exprf { + ExprF::Variable(Variable { path: _, name, id }) => { + let res = if name == FUNC_RETURN_VAR_NAME { id.is_none() } else { id.is_some() }; + if !res { + dbg!(ann, name, id); + } + res + } + + ExprF::FnCall { args, .. } => args.into_iter().all(identity), + ExprF::Quantified { expr, .. } => expr, + ExprF::Parenthesised { expr } => expr, + ExprF::UnaryOp { expr, .. } => expr, + ExprF::BinaryOp { expr_left, expr_right, .. } => expr_left && expr_right, + ExprF::Index { expr, index } => expr && index, + ExprF::TupleAccess { expr, .. } => expr, + ExprF::Cast { expr, .. } => expr, + ExprF::Tuple { exprs } => exprs.into_iter().all(identity), + ExprF::Array { exprs } => exprs.into_iter().all(identity), + + // Non-recursive variants don't carry information + ExprF::Literal { .. } => true, + })); Ok(fully_typed_expr) } diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 87076cd9caf..e9d76bee9ba 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -32,7 +32,9 @@ use noirc_frontend::{ parser::ParserError, token::SecondaryAttributeKind, }; +use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet}; +use std::rc::Rc; use vir::ast::Krate; use crate::errors::{CompilationErrorBundle, MonomorphizationErrorBundle}; @@ -200,8 +202,9 @@ fn modified_monomorphize( // Clone because of the borrow checker let globals = monomorphizer.finished_globals.clone().into_iter().collect::>(); - let mut min_available_id: u32 = + let min_available_id: u32 = monomorphizer.locals.values().map(|LocalId(id)| *id).max().unwrap_or_default() + 1; + let min_available_id = Rc::new(RefCell::new(min_available_id)); let functions_to_process: Vec<(FuncId, node_interner::FuncId)> = monomorphizer .finished_functions @@ -255,7 +258,7 @@ fn modified_monomorphize( &mut monomorphizer, new_func_id, &globals, - &mut min_available_id, + min_available_id.clone(), expr, &mut new_ids_to_old_ids, &mut to_be_added_ghost_attribute, @@ -267,7 +270,7 @@ fn modified_monomorphize( &mut monomorphizer, new_func_id, &globals, - &mut min_available_id, + min_available_id.clone(), expr, &mut new_ids_to_old_ids, &mut to_be_added_ghost_attribute, @@ -280,7 +283,7 @@ fn modified_monomorphize( function: &monomorphizer.finished_functions[&new_func_id], global_constants: &globals, functions: &monomorphizer.finished_functions, - min_local_id: &mut min_available_id, + min_local_id: min_available_id.clone(), }; processed_attributes @@ -334,7 +337,7 @@ fn type_infer_attribute_expr( noirc_frontend::monomorphization::ast::Expression, ), >, - min_available_id: &mut u32, + min_available_id: Rc>, expr: AnnExpr, new_ids_to_old_ids: &mut HashMap, to_be_added_ghost_attribute: &mut HashSet, @@ -351,7 +354,7 @@ fn type_infer_attribute_expr( function, global_constants: &globals, functions: &monomorphizer.finished_functions, - min_local_id: min_available_id, + min_local_id: min_available_id.clone(), }; match type_infer(&state, expr.clone()) { From d178eb21f8b5b0d60370de79156fe9f908499a1f Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Mon, 18 Aug 2025 15:42:31 +0300 Subject: [PATCH 60/86] feat(annotations): Type infer args in func calls We now unify the types of the args of each function call with the types of the function's signature. --- compiler/formal_verification/src/typing.rs | 13 ++++++++++--- .../func_call_in_annotation/Nargo.toml | 6 ++++++ .../func_call_in_annotation/src/main.nr | 7 +++++++ 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 test_programs/formal_verify_success/func_call_in_annotation/Nargo.toml create mode 100644 test_programs/formal_verify_success/func_call_in_annotation/src/main.nr diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 0e9d9115532..4a5fd728339 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -377,10 +377,10 @@ pub fn type_infer( variable_type } ExprF::FnCall { name, args } => { - let return_type = state + let func_object = state .functions .iter() - .find_map(|(_, func)| (func.name == *name).then_some(&func.return_type)) + .find_map(|(_, func)| (func.name == *name).then_some(func)) .ok_or(TypeInferenceError::MonomorphizationRequest( MonomorphizationRequest { function_identifier: name.clone(), @@ -391,7 +391,14 @@ pub fn type_infer( }, ))?; - OptionalType::Well(return_type.clone()) + // Unify the function call arguments with their expected type + for (arg, (_id, _mut, _name, typ, _visibility)) in + args.iter_mut().zip(&func_object.parameters) + { + arg.unify_with_type(typ.clone())?; + } + + OptionalType::Well(func_object.return_type.clone()) } ExprF::Quantified { variables, .. } => { variables diff --git a/test_programs/formal_verify_success/func_call_in_annotation/Nargo.toml b/test_programs/formal_verify_success/func_call_in_annotation/Nargo.toml new file mode 100644 index 00000000000..880c4736df8 --- /dev/null +++ b/test_programs/formal_verify_success/func_call_in_annotation/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "func_call_in_annotation" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/formal_verify_success/func_call_in_annotation/src/main.nr b/test_programs/formal_verify_success/func_call_in_annotation/src/main.nr new file mode 100644 index 00000000000..854881424fd --- /dev/null +++ b/test_programs/formal_verify_success/func_call_in_annotation/src/main.nr @@ -0,0 +1,7 @@ +#['ensures(foo(5))] +fn main() {} + +#['ghost] +fn foo(x: u16) -> bool { + true +} From abf3a33229c9aff13410700356769a8b6cddb966 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 61/86] fix(annotations): correct `Offset` calculation for `prefix` / `postfix` --- compiler/formal_verification/src/parse.rs | 73 +++++++++++++---------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 56a7c6593e2..adb2d260a43 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -441,18 +441,20 @@ pub(crate) enum Prefix { } pub(crate) fn parse_prefix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + let (input, prefixes) = context("prefix", many0(parse_any_prefix)).parse(input)?; let (input, base_expr) = parse_postfix_expr(input)?; + let after_offset = input.len(); + let final_expr = prefixes.into_iter().rev().fold(base_expr, |inner_expr, prefix| { - // TODO: real span - let ann = inner_expr.ann; let expr_f = match prefix { Prefix::Dereference => ExprF::UnaryOp { op: UnaryOp::Dereference, expr: inner_expr }, Prefix::Not => ExprF::UnaryOp { op: UnaryOp::Not, expr: inner_expr }, }; - OffsetExpr { ann, expr: Box::new(expr_f) } + build_expr(prev_offset, after_offset, expr_f) }); Ok((input, final_expr)) @@ -483,6 +485,8 @@ pub(crate) enum CastTargetType { } pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { + let prev_offset = input.len(); + let (mut input, mut expr_base) = parse_atom_expr(input)?; loop { @@ -490,39 +494,38 @@ pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr let (next_input, suffix) = cut(opt(parse_any_suffix)).parse(input)?; if let Some(s) = suffix { + let after_offset = next_input.len(); + expr_base = match s { - Postfix::ArrayIndex(index_expr) => { - let ann = build_offset_from_exprs(&expr_base, &index_expr); - OffsetExpr { - ann, - expr: Box::new(ExprF::Index { expr: expr_base, index: index_expr }), - } - } + Postfix::ArrayIndex(index_expr) => build_expr( + prev_offset, + after_offset, + ExprF::Index { expr: expr_base, index: index_expr }, + ), Postfix::TupleMember(index) => { let index_u32 = index.try_into().map_err(|_| { NomErr::Error(build_error(input, ParserErrorKind::InvalidTupleIndex)) })?; - let ann = (expr_base.ann.0, (input.len() - next_input.len()) as u32); // Approximate span - OffsetExpr { - ann, - expr: Box::new(ExprF::TupleAccess { expr: expr_base, index: index_u32 }), - } - } - Postfix::Cast(target_type) => { - let ann = (expr_base.ann.0, (input.len() - next_input.len()) as u32); // Approximate span - OffsetExpr { - ann, - expr: Box::new(ExprF::Cast { - expr: expr_base, - target: match target_type { - CastTargetType::Field => NoirType::Field, - CastTargetType::Integer(s, b) => NoirType::Integer(s, b), - CastTargetType::Bool => NoirType::Bool, - }, - }), - } + build_expr( + prev_offset, + after_offset, + ExprF::TupleAccess { expr: expr_base, index: index_u32 }, + ) } + Postfix::Cast(target_type) => build_expr( + prev_offset, + after_offset, + ExprF::Cast { + expr: expr_base, + target: match target_type { + CastTargetType::Field => NoirType::Field, + CastTargetType::Integer(s, b) => NoirType::Integer(s, b), + CastTargetType::Bool => NoirType::Bool, + }, + }, + ), }; + input = next_input; } else { return Ok((input, expr_base)); @@ -933,7 +936,10 @@ pub mod tests { LocalId(5), false, "pair".to_string(), - NoirType::Tuple(vec![NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Sixteen), NoirType::Field]), + NoirType::Tuple(vec![ + NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Sixteen), + NoirType::Field, + ]), Visibility::Public, ), ], @@ -1126,6 +1132,13 @@ pub mod tests { assert_eq!(expr.0, ""); } + #[test] + fn test_quantifier_span() { + let expr = parse("forall(|x| bool == x)").unwrap(); + dbg!(&expr); + assert_eq!(expr.0, ""); + } + #[test] fn test_ghost() { let annotation = "ghost"; From daf8949886da7c6e6afa6dbc7884fdb119d6e06c Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 62/86] chore(annotations): emit an adequate type errors for array literals - Refactor `concretize_type` to return an `Option` - Return a `TypeAnnotationNeededOnArrayLiteral` --- compiler/formal_verification/src/typing.rs | 25 +++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 4a5fd728339..5d118e9a90f 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -273,23 +273,17 @@ impl SpannedPartiallyTypedExpr { pub fn concretize_type( opt_type: OptionalType, default_integer_literal_type: &NoirType, -) -> Result { +) -> Option { match opt_type { - OptionalType::Well(t) => Ok(t), - OptionalType::IntegerLiteral => Ok(default_integer_literal_type.clone()), + OptionalType::Well(t) => Some(t), + OptionalType::IntegerLiteral => Some(default_integer_literal_type.clone()), OptionalType::PartialTuple(elements) => elements .into_iter() .map(|e| match e { - OptionalType::IntegerLiteral => Err(TypeInferenceError::NoirTypeError( - // TODO: carry optional information in `concretize_type` - TypeCheckError::TypeAnnotationNeededOnArrayLiteral { - is_array: true, - location: Location::dummy(), - }, - )), + OptionalType::IntegerLiteral => None, _ => concretize_type(e, default_integer_literal_type), }) - .collect::>() + .collect::>() .map(NoirType::Tuple), } } @@ -955,7 +949,14 @@ pub fn type_infer( )?; let concrete_element_type = - concretize_type(unified_opt_type, &default_literal_type)?; + concretize_type(unified_opt_type, &default_literal_type).ok_or( + TypeInferenceError::NoirTypeError( + TypeCheckError::TypeAnnotationNeededOnArrayLiteral { + is_array: true, + location, + }, + ), + )?; exprs .into_iter() From 191f4c678e4a5630269fe74c6d1cdaf2dfacfc71 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Tue, 19 Aug 2025 13:36:18 +0300 Subject: [PATCH 63/86] chore(annotation): Correct Location before `parse_attribute` Offset the location before calling the function `parse_attribute`. This resolves the TODO in the function `parse_attribute`. --- compiler/formal_verification/src/parse.rs | 14 ++------------ compiler/fv_bridge/src/lib.rs | 10 +++++++++- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index adb2d260a43..57b505f34ee 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -66,26 +66,17 @@ pub(crate) fn build_offset_from_exprs(left: &OffsetExpr, right: &OffsetExpr) -> pub fn parse_attribute<'a>( annotation: &'a str, - mut location: Location, + location: Location, _function: &'a mast::Function, _global_constants: &'a BTreeMap, _functions: &'a BTreeMap, ) -> Result, Vec)> { - // NOTE: #['...] - // ^^^^^^^ - received `Location` - // ^^^ - relevant stuff - // TODO: don't do this here - location = Location { - span: Span::inclusive(location.span.start() + 3, location.span.end() - 1), - ..location - }; - let locate_parser_error = |parser_error: ParserError| ParserErrorWithLocation { location: build_location( location, annotation.len() as u32, parser_error.offset, - parser_error.offset + 1, + parser_error.offset, // This span is inclusive ), kind: parser_error.kind.clone(), }; @@ -604,7 +595,6 @@ pub(crate) fn parse_cast_suffix<'a>(input: Input<'a>) -> PResult<'a, CastTargetT Err(NomErr::Error(build_error(type_ident, ParserErrorKind::InvalidIntegerLiteral))) } - pub(crate) fn parse_atom_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { alt(( context("parenthesised or tuple", parse_parenthesised_or_tuple_expr), diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index e9d76bee9ba..c2fd18bcdfa 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -5,7 +5,7 @@ use formal_verification::typing::{OptionalType, TypeInferenceError, type_infer}; use formal_verification::{State, parse::parse_attribute}; use iter_extended::vecmap; use noirc_driver::{CompilationResult, CompileError, CompileOptions, check_crate}; -use noirc_errors::CustomDiagnostic; +use noirc_errors::{CustomDiagnostic, Span}; use noirc_errors::Location; use noirc_evaluator::vir::vir_gen::Attribute; use noirc_evaluator::{ @@ -240,6 +240,14 @@ fn modified_monomorphize( for (annotation_body, location) in attribute_data { let function_for_parser = &monomorphizer.finished_functions[&new_func_id]; + + // NOTE: #['...] + // ^^^^^^^ - received `Location` + // ^^^ - relevant stuff + let location = Location { + span: Span::inclusive(location.span.start() + 3, location.span.end() - 1), + ..location + }; let parsed_attribute = parse_attribute( &annotation_body, From f35d7c2e1efc987f0c0e39dbfbc34c5eed4ef7a3 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Tue, 19 Aug 2025 16:53:43 +0300 Subject: [PATCH 64/86] feat(annotation-to-vir): Convert cast expressions --- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 43 +++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index 082813d9a8f..13c0ab7cd43 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -14,10 +14,13 @@ use noirc_evaluator::vir::vir_gen::{ }, }; use noirc_frontend::monomorphization::FUNC_RETURN_VAR_NAME; -use vir::ast::{ - AirQuant, BinaryOp as VirBinaryOp, BitwiseOp, Dt, FieldOpr, FunX, ImplPath, InequalityOp, - IntRange, PathX, Primitive, Quant, Typ, UnaryOpr, VarBinder, VarBinderX, VarIdent, - VarIdentDisambiguate, VariantCheck, +use vir::{ + ast::{ + AirQuant, BinaryOp as VirBinaryOp, BitwiseOp, Dt, FieldOpr, FunX, ImplPath, InequalityOp, + IntRange, PathX, Primitive, Quant, Typ, UnaryOpr, VarBinder, VarBinderX, VarIdent, + VarIdentDisambiguate, VariantCheck, + }, + ast_util::int_range_from_type, }; use vir::{ ast::{ @@ -333,18 +336,38 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> make_expr(vir_exprx, element_type, "Array indexing expression".to_string()) } - ExprF::Cast { expr, target } => { - // TODO(totel): conversion of cast expressions - todo!() - }, + ExprF::Cast { expr: castee, target } => { + let target_type = ast_type_to_vir_type(&target); + // The following unwrap is safe because the semantic analysis of + // the compiler should guarantee correctly typed expressions. + let target_int_range = + int_range_from_type(&target_type).expect("Can not cast to a non integer type"); + + let exprx = ExprX::Unary( + vir::ast::UnaryOp::Clip { + range: target_int_range, + truncate: false, // We are not truncating because Verus doesn't truncate casts + }, + castee, + ); + + SpannedTyped::new( + &build_span_no_id( + format!("Cast expression to target type {}", target), + Some(loc), + ), + &target_type, + exprx, + ) + } ExprF::Tuple { exprs } => { // TODO(totel): conversion of tuple expressions todo!() - }, + } ExprF::Array { exprs } => { // TODO(totel): conversion of array expressions todo!() - }, + } } }) } From ec5cdd95c3d1a83ec9fdc57252927055f234846c Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Tue, 19 Aug 2025 17:03:55 +0300 Subject: [PATCH 65/86] feat(annotations-to-vir): Tuple and Array literals We now convert Tuple and Array literals expressions located in FV annotaitons to VIR. --- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 41 ++++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index 13c0ab7cd43..689113156e1 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -16,9 +16,9 @@ use noirc_evaluator::vir::vir_gen::{ use noirc_frontend::monomorphization::FUNC_RETURN_VAR_NAME; use vir::{ ast::{ - AirQuant, BinaryOp as VirBinaryOp, BitwiseOp, Dt, FieldOpr, FunX, ImplPath, InequalityOp, - IntRange, PathX, Primitive, Quant, Typ, UnaryOpr, VarBinder, VarBinderX, VarIdent, - VarIdentDisambiguate, VariantCheck, + AirQuant, BinaryOp as VirBinaryOp, BinderX, BitwiseOp, Dt, FieldOpr, FunX, ImplPath, + InequalityOp, IntRange, PathX, Primitive, Quant, Typ, UnaryOpr, VarBinder, VarBinderX, + VarIdent, VarIdentDisambiguate, VariantCheck, }, ast_util::int_range_from_type, }; @@ -361,12 +361,39 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> ) } ExprF::Tuple { exprs } => { - // TODO(totel): conversion of tuple expressions - todo!() + let tuple_length = exprs.len(); + + let tuple_exprx = ExprX::Ctor( + Dt::Tuple(tuple_length), + Arc::new(format!("tuple%{tuple_length}")), + Arc::new( + exprs + .into_iter() + .enumerate() + .map(|(index, tuple_expr)| { + Arc::new(BinderX { + name: Arc::new(index.to_string()), + a: tuple_expr, + }) + }) + .collect(), + ), + None, + ); + + SpannedTyped::new( + &build_span_no_id(format!("Tuple constructor expression"), Some(loc)), + &ast_type_to_vir_type(&typ), + tuple_exprx, + ) } ExprF::Array { exprs } => { - // TODO(totel): conversion of array expressions - todo!() + let exprx = ExprX::ArrayLiteral(Arc::new(exprs)); + SpannedTyped::new( + &build_span_no_id(format!("Array literal expression"), Some(loc)), + &ast_type_to_vir_type(&typ), + exprx, + ) } } }) From 547653656d15dfaba92b98f906d393823934771d Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Thu, 21 Aug 2025 11:25:24 +0300 Subject: [PATCH 66/86] chore(tests): Move failing successful tests in fix dir Some tests are failing because we don't support paths and structures in annotations as of yet. We are moving those tests in a "to_be_fixed" directory for now. --- .../array_set_complex_composite_type/Nargo.toml | 0 .../array_set_complex_composite_type/src/main.nr | 0 .../array_set_composite_types_3/Nargo.toml | 0 .../array_set_composite_types_3/src/main.nr | 0 .../call_struct_method/Nargo.toml | 0 .../call_struct_method/src/main.nr | 0 .../forall_structure/Nargo.toml | 0 .../forall_structure/src/main.nr | 0 .../fv_std_old/Nargo.toml | 0 .../fv_std_old/src/main.nr | 0 .../index_composite_array_with_const/Nargo.toml | 0 .../index_composite_array_with_const/src/main.nr | 0 .../index_composite_array_with_var_1/Nargo.toml | 0 .../index_composite_array_with_var_1/src/main.nr | 0 .../index_composite_array_with_var_2/Nargo.toml | 0 .../index_composite_array_with_var_2/src/main.nr | 0 .../index_composite_array_with_var_3/Nargo.toml | 0 .../index_composite_array_with_var_3/src/main.nr | 0 .../index_composite_array_with_var_4/Nargo.toml | 0 .../index_composite_array_with_var_4/src/main.nr | 0 .../index_composite_array_with_var_5/Nargo.toml | 0 .../index_composite_array_with_var_5/src/main.nr | 0 .../integer_consts/Nargo.toml | 0 .../integer_consts/src/main.nr | 0 .../struct/Nargo.toml | 0 .../struct/src/main.nr | 0 .../struct_return/Nargo.toml | 0 .../struct_return/src/main.nr | 0 28 files changed, 0 insertions(+), 0 deletions(-) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/array_set_complex_composite_type/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/array_set_complex_composite_type/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/array_set_composite_types_3/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/array_set_composite_types_3/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/call_struct_method/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/call_struct_method/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/forall_structure/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/forall_structure/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/fv_std_old/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/fv_std_old/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_const/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_const/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_1/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_1/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_2/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_2/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_3/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_3/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_4/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_4/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_5/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/index_composite_array_with_var_5/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/integer_consts/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/integer_consts/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/struct/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/struct/src/main.nr (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/struct_return/Nargo.toml (100%) rename test_programs/{formal_verify_success => formal_verify_to_be_fixed}/struct_return/src/main.nr (100%) diff --git a/test_programs/formal_verify_success/array_set_complex_composite_type/Nargo.toml b/test_programs/formal_verify_to_be_fixed/array_set_complex_composite_type/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/array_set_complex_composite_type/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/array_set_complex_composite_type/Nargo.toml diff --git a/test_programs/formal_verify_success/array_set_complex_composite_type/src/main.nr b/test_programs/formal_verify_to_be_fixed/array_set_complex_composite_type/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/array_set_complex_composite_type/src/main.nr rename to test_programs/formal_verify_to_be_fixed/array_set_complex_composite_type/src/main.nr diff --git a/test_programs/formal_verify_success/array_set_composite_types_3/Nargo.toml b/test_programs/formal_verify_to_be_fixed/array_set_composite_types_3/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/array_set_composite_types_3/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/array_set_composite_types_3/Nargo.toml diff --git a/test_programs/formal_verify_success/array_set_composite_types_3/src/main.nr b/test_programs/formal_verify_to_be_fixed/array_set_composite_types_3/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/array_set_composite_types_3/src/main.nr rename to test_programs/formal_verify_to_be_fixed/array_set_composite_types_3/src/main.nr diff --git a/test_programs/formal_verify_success/call_struct_method/Nargo.toml b/test_programs/formal_verify_to_be_fixed/call_struct_method/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/call_struct_method/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/call_struct_method/Nargo.toml diff --git a/test_programs/formal_verify_success/call_struct_method/src/main.nr b/test_programs/formal_verify_to_be_fixed/call_struct_method/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/call_struct_method/src/main.nr rename to test_programs/formal_verify_to_be_fixed/call_struct_method/src/main.nr diff --git a/test_programs/formal_verify_success/forall_structure/Nargo.toml b/test_programs/formal_verify_to_be_fixed/forall_structure/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/forall_structure/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/forall_structure/Nargo.toml diff --git a/test_programs/formal_verify_success/forall_structure/src/main.nr b/test_programs/formal_verify_to_be_fixed/forall_structure/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/forall_structure/src/main.nr rename to test_programs/formal_verify_to_be_fixed/forall_structure/src/main.nr diff --git a/test_programs/formal_verify_success/fv_std_old/Nargo.toml b/test_programs/formal_verify_to_be_fixed/fv_std_old/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/fv_std_old/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/fv_std_old/Nargo.toml diff --git a/test_programs/formal_verify_success/fv_std_old/src/main.nr b/test_programs/formal_verify_to_be_fixed/fv_std_old/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/fv_std_old/src/main.nr rename to test_programs/formal_verify_to_be_fixed/fv_std_old/src/main.nr diff --git a/test_programs/formal_verify_success/index_composite_array_with_const/Nargo.toml b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_const/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_const/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_const/Nargo.toml diff --git a/test_programs/formal_verify_success/index_composite_array_with_const/src/main.nr b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_const/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_const/src/main.nr rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_const/src/main.nr diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_1/Nargo.toml b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_1/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_1/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_1/Nargo.toml diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_1/src/main.nr b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_1/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_1/src/main.nr rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_1/src/main.nr diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_2/Nargo.toml b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_2/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_2/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_2/Nargo.toml diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_2/src/main.nr b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_2/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_2/src/main.nr rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_2/src/main.nr diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_3/Nargo.toml b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_3/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_3/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_3/Nargo.toml diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_3/src/main.nr b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_3/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_3/src/main.nr rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_3/src/main.nr diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_4/Nargo.toml b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_4/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_4/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_4/Nargo.toml diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_4/src/main.nr b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_4/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_4/src/main.nr rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_4/src/main.nr diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_5/Nargo.toml b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_5/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_5/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_5/Nargo.toml diff --git a/test_programs/formal_verify_success/index_composite_array_with_var_5/src/main.nr b/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_5/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/index_composite_array_with_var_5/src/main.nr rename to test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_5/src/main.nr diff --git a/test_programs/formal_verify_success/integer_consts/Nargo.toml b/test_programs/formal_verify_to_be_fixed/integer_consts/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/integer_consts/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/integer_consts/Nargo.toml diff --git a/test_programs/formal_verify_success/integer_consts/src/main.nr b/test_programs/formal_verify_to_be_fixed/integer_consts/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/integer_consts/src/main.nr rename to test_programs/formal_verify_to_be_fixed/integer_consts/src/main.nr diff --git a/test_programs/formal_verify_success/struct/Nargo.toml b/test_programs/formal_verify_to_be_fixed/struct/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/struct/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/struct/Nargo.toml diff --git a/test_programs/formal_verify_success/struct/src/main.nr b/test_programs/formal_verify_to_be_fixed/struct/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/struct/src/main.nr rename to test_programs/formal_verify_to_be_fixed/struct/src/main.nr diff --git a/test_programs/formal_verify_success/struct_return/Nargo.toml b/test_programs/formal_verify_to_be_fixed/struct_return/Nargo.toml similarity index 100% rename from test_programs/formal_verify_success/struct_return/Nargo.toml rename to test_programs/formal_verify_to_be_fixed/struct_return/Nargo.toml diff --git a/test_programs/formal_verify_success/struct_return/src/main.nr b/test_programs/formal_verify_to_be_fixed/struct_return/src/main.nr similarity index 100% rename from test_programs/formal_verify_success/struct_return/src/main.nr rename to test_programs/formal_verify_to_be_fixed/struct_return/src/main.nr From 31854133b6fb0cd990a8eb57f4bd7a702a06242c Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Thu, 21 Aug 2025 11:51:14 +0300 Subject: [PATCH 67/86] chore(tests): Add some basic tests --- .../array_param_index/Nargo.toml | 7 +++++++ .../array_param_index/src/main.nr | 6 ++++++ .../formal_verify_success/eq_trait/Nargo.toml | 7 +++++++ .../formal_verify_success/eq_trait/src/main.nr | 10 ++++++++++ .../formal_verify_success/eq_trait_1/Nargo.toml | 7 +++++++ .../formal_verify_success/eq_trait_1/src/main.nr | 14 ++++++++++++++ .../ghost_function_in_attribute/Nargo.toml | 7 +++++++ .../ghost_function_in_attribute/src/main.nr | 14 ++++++++++++++ 8 files changed, 72 insertions(+) create mode 100644 test_programs/formal_verify_success/array_param_index/Nargo.toml create mode 100644 test_programs/formal_verify_success/array_param_index/src/main.nr create mode 100644 test_programs/formal_verify_success/eq_trait/Nargo.toml create mode 100644 test_programs/formal_verify_success/eq_trait/src/main.nr create mode 100644 test_programs/formal_verify_success/eq_trait_1/Nargo.toml create mode 100644 test_programs/formal_verify_success/eq_trait_1/src/main.nr create mode 100644 test_programs/formal_verify_success/ghost_function_in_attribute/Nargo.toml create mode 100644 test_programs/formal_verify_success/ghost_function_in_attribute/src/main.nr diff --git a/test_programs/formal_verify_success/array_param_index/Nargo.toml b/test_programs/formal_verify_success/array_param_index/Nargo.toml new file mode 100644 index 00000000000..3785031132b --- /dev/null +++ b/test_programs/formal_verify_success/array_param_index/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "array_param_index" +type = "bin" +authors = [""] +compiler_version = ">=0.35.0" + +[dependencies] diff --git a/test_programs/formal_verify_success/array_param_index/src/main.nr b/test_programs/formal_verify_success/array_param_index/src/main.nr new file mode 100644 index 00000000000..fa1abd1e207 --- /dev/null +++ b/test_programs/formal_verify_success/array_param_index/src/main.nr @@ -0,0 +1,6 @@ +#['requires(x[3].0 == -2)] +#['requires(i < 5)] +fn main(mut x: [(i32, u32); 5], i: u32, y: u32) -> pub [(i32, u32); 5] { + x[i].1 = y; + x +} diff --git a/test_programs/formal_verify_success/eq_trait/Nargo.toml b/test_programs/formal_verify_success/eq_trait/Nargo.toml new file mode 100644 index 00000000000..2d491de682b --- /dev/null +++ b/test_programs/formal_verify_success/eq_trait/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "eq_trait" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] diff --git a/test_programs/formal_verify_success/eq_trait/src/main.nr b/test_programs/formal_verify_success/eq_trait/src/main.nr new file mode 100644 index 00000000000..313b94f4284 --- /dev/null +++ b/test_programs/formal_verify_success/eq_trait/src/main.nr @@ -0,0 +1,10 @@ +#['ensures(result == x)] +fn main(x: u32) -> pub u32 { + id(x) +} + +#['ensures(result == x)] +fn id(x: T) -> T + where T: Eq { + x +} diff --git a/test_programs/formal_verify_success/eq_trait_1/Nargo.toml b/test_programs/formal_verify_success/eq_trait_1/Nargo.toml new file mode 100644 index 00000000000..b243a2d5963 --- /dev/null +++ b/test_programs/formal_verify_success/eq_trait_1/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "eq_trait_1" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] diff --git a/test_programs/formal_verify_success/eq_trait_1/src/main.nr b/test_programs/formal_verify_success/eq_trait_1/src/main.nr new file mode 100644 index 00000000000..c4d458816d0 --- /dev/null +++ b/test_programs/formal_verify_success/eq_trait_1/src/main.nr @@ -0,0 +1,14 @@ +#['ensures(result == x)] +fn main(x: u32) -> pub u32 { + id(x) +} + +#['ensures(result == x)] +fn id(x: T) -> T + where T: Eq { + if x == x { + x + } else { + x + } +} diff --git a/test_programs/formal_verify_success/ghost_function_in_attribute/Nargo.toml b/test_programs/formal_verify_success/ghost_function_in_attribute/Nargo.toml new file mode 100644 index 00000000000..6af505de980 --- /dev/null +++ b/test_programs/formal_verify_success/ghost_function_in_attribute/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ghost_function_in_attribute" +type = "bin" +authors = [""] +compiler_version = ">=0.35.0" + +[dependencies] diff --git a/test_programs/formal_verify_success/ghost_function_in_attribute/src/main.nr b/test_programs/formal_verify_success/ghost_function_in_attribute/src/main.nr new file mode 100644 index 00000000000..88fdab1921c --- /dev/null +++ b/test_programs/formal_verify_success/ghost_function_in_attribute/src/main.nr @@ -0,0 +1,14 @@ +#['ensures(result == baz())] +fn main(x: Field, y: pub Field) -> pub u32 { + foo().1 +} + +#['ensures(result.1 == 2)] +fn foo() -> (u32, u32){ + (1, 2) +} + +#['ghost] +fn baz() -> u32 { + 2 +} From 5bb32b41ad7e79e3e0dd582e3a9e059f0b5bac7d Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Thu, 21 Aug 2025 12:27:27 +0300 Subject: [PATCH 68/86] chore(tests): Remove tmp file --- test_programs/formal_verify_success/tmp | 26 ------------------------- 1 file changed, 26 deletions(-) delete mode 100644 test_programs/formal_verify_success/tmp diff --git a/test_programs/formal_verify_success/tmp b/test_programs/formal_verify_success/tmp deleted file mode 100644 index 15589fc767c..00000000000 --- a/test_programs/formal_verify_success/tmp +++ /dev/null @@ -1,26 +0,0 @@ - array_as_result_global ../../fix_those_tests - array_let_var_in_trigger ../../fix_those_tests - array_set_complex_composite_type ../../fix_those_tests - array_set_composite_types_1 ../../fix_those_tests - array_set_composite_types_2 ../../fix_those_tests - array_set_composite_types_3 ../../fix_those_tests - binary_search_v1 ../../fix_those_tests - binary_search_v2 ../../fix_those_tests - exists_big_element_sum ../../fix_those_tests - exists_zero_in_array ../../fix_those_tests - field_minus_one_is_max ../../fix_those_tests - forall_max_is_max ../../fix_those_tests - forall_structure ../../fix_those_tests - forall_sum_of_evens ../../fix_those_tests - generics ../../fix_those_tests - inline_functions_in_attributes ../../fix_those_tests - inline_tuple_returning_func ../../fix_those_tests - integer_and ../../fix_those_tests - integer_or ../../fix_those_tests - integer_xor ../../fix_those_tests - is_power_of_2 ../../fix_those_tests - is_sorted_no_quant ../../fix_those_tests - load_store_instructions ../../fix_those_tests - self_in_annotation ../../fix_those_tests - struct_return ../../fix_those_tests - tuple_return ../../fix_those_tests From 2d348a815a37e725221d10751d8099631d5ae4dc Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 69/86] refactor(annotations): prefer `impl` over `dyn` `Fn` for `cata` `fn`s --- compiler/formal_verification/src/ast.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 12c324f966e..9a8adef646f 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -132,7 +132,7 @@ pub struct Variable { * cata stuff * */ -pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { +pub fn fmap(expr: ExprF, cata_fn: &impl Fn(A) -> B) -> ExprF { match expr { ExprF::BinaryOp { op, expr_left, expr_right } => { ExprF::BinaryOp { op, expr_left: cata_fn(expr_left), expr_right: cata_fn(expr_right) } @@ -161,7 +161,7 @@ pub fn fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> B) -> ExprF { } } -fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Result, E> { +fn try_fmap(expr: ExprF, cata_fn: &impl Fn(A) -> Result) -> Result, E> { Ok(match expr { ExprF::BinaryOp { op, expr_left, expr_right } => { ExprF::BinaryOp { op, expr_left: cata_fn(expr_left)?, expr_right: cata_fn(expr_right)? } @@ -193,8 +193,7 @@ fn try_fmap(expr: ExprF, cata_fn: &dyn Fn(A) -> Result) -> Res }) } -// TODO: `impl` vs` `dyn` for `cata_fn` -pub fn cata(expr: AnnExpr, algebra: &dyn Fn(A, ExprF) -> B) -> B { +pub fn cata(expr: AnnExpr, algebra: &impl Fn(A, ExprF) -> B) -> B { let children_results = fmap(*expr.expr, &|child| cata(child, algebra)); algebra(expr.ann, children_results) @@ -202,7 +201,7 @@ pub fn cata(expr: AnnExpr, algebra: &dyn Fn(A, ExprF) -> B) -> B { pub fn try_cata( expr: AnnExpr, - algebra: &dyn Fn(A, ExprF) -> Result, + algebra: &impl Fn(A, ExprF) -> Result, ) -> Result { let children_results = try_fmap(*expr.expr, &|child| try_cata(child, algebra))?; @@ -212,8 +211,8 @@ pub fn try_cata( pub fn try_contextual_cata( expr: AnnExpr, initial_context: C, - update_context: &dyn Fn(C, &AnnExpr) -> C, - algebra: &dyn Fn(A, C, ExprF) -> Result, + update_context: &impl Fn(C, &AnnExpr) -> C, + algebra: &impl Fn(A, C, ExprF) -> Result, ) -> Result where C: Clone, @@ -221,8 +220,8 @@ where fn recurse( expr: AnnExpr, context: C, - update_context: &dyn Fn(C, &AnnExpr) -> C, - algebra: &dyn Fn(A, C, ExprF) -> Result, + update_context: &impl Fn(C, &AnnExpr) -> C, + algebra: &impl Fn(A, C, ExprF) -> Result, ) -> Result where C: Clone, @@ -241,7 +240,7 @@ where pub fn try_cata_recoverable( expr: AnnExpr, - algebra: &dyn Fn(A, Result, E>) -> Result, + algebra: &impl Fn(A, Result, E>) -> Result, ) -> Result { let children_results = try_fmap(*expr.expr, &|child| try_cata_recoverable(child, algebra)); From 3206263c72d5aef799d0f43f38b0f7e84aa1474d Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 70/86] chore(annotations): remove old comment linking to `nom` documentation --- compiler/formal_verification/src/parse.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 57b505f34ee..f0254a3ef2e 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -31,8 +31,6 @@ use crate::{ pub type Input<'a> = &'a str; pub type PResult<'a, T> = IResult, T, Error>; -// https://github.com/rust-bakery/nom/blob/main/doc/error_management.md - pub(crate) fn build_location( annotation_location: Location, full_length: u32, From e474dc9c04cf7c992e337d6965aaafbb7ec606b3 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 71/86] refactor(annotations): remove unneeded `alt` call --- compiler/formal_verification/src/parse.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index f0254a3ef2e..a3a91d59222 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -151,11 +151,8 @@ pub fn parse_attribute<'a>( } pub(crate) fn parse_expression<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { - alt(( - // - parse_implication_expr, - )) - .parse(input) + // NOTE: we start parsing from the highest precedence operator + parse_implication_expr(input) } pub(crate) fn parse_implication_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr> { From 0e1e7a8fe700bf64bc7d8d71dbe2c9da079ae29b Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 72/86] chore(annotations): remove outdated comment about `OptionalType`s --- compiler/formal_verification/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 5bbaa4eeb4f..154b37e23d7 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -32,7 +32,6 @@ pub enum Attribute { #[derive(Debug, Clone)] pub struct MonomorphizationRequest { pub function_identifier: String, - // NOTE: `None` for untyped integer literals pub param_types: Vec, } From 07ff0277ef9792da97d799f4685ad1185c54ab26 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 73/86] chore(annotations): remove more outdated comments --- compiler/formal_verification/src/parse/errors.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/formal_verification/src/parse/errors.rs b/compiler/formal_verification/src/parse/errors.rs index 3f3ed24feec..892b4948e6a 100644 --- a/compiler/formal_verification/src/parse/errors.rs +++ b/compiler/formal_verification/src/parse/errors.rs @@ -72,7 +72,6 @@ pub fn get_found_token(input: Input) -> String { /// A specialized version of `map_nom_err` for the common "Expected" error. pub fn expect<'a, P, O>( - // TODO: maybe `Into` or similar? expected_msg: impl AsRef, parser: P, ) -> impl FnMut(Input<'a>) -> IResult, O, Error> @@ -81,7 +80,7 @@ where { map_nom_err(parser, move |fail_input| ParserErrorKind::Expected { expected: expected_msg.as_ref().to_string(), - found: fail_input.to_string(), // get_found_token(fail_input), + found: fail_input.to_string(), }) } From 88b37ed7c9a3f64aefb2a30abf07453125878415 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 74/86] chore(annotations): augment comment about dummy function definition --- compiler/formal_verification/src/parse.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index a3a91d59222..ba350a5678c 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -946,8 +946,9 @@ pub mod tests { Function { id: FuncId(0), name: "banica".to_string(), - // TODO: not type-checking parameters, yet - // might need to do some manual dispatching + // NOTE: no tests are calling this function (yet) + // it's only used in the `parse.rs` tests, + // so it's fine being argument-less parameters: vec![], body: Expression::Block(vec![]), return_type: NoirType::Field, From 5491f9ce85833105f5ef448ea5f2ecfc58e3169d Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 75/86] chore(annotations): remove old code for type-related tests in `parse.rs` --- compiler/formal_verification/src/parse.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index ba350a5678c..9b28877f219 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -993,7 +993,6 @@ pub mod tests { fn test_bool_true() { let (input, expr) = parse("true").unwrap(); assert_eq!(input, ""); - // assert!(matches!(*expr.1.typ, TypX::Bool)); assert!(matches!(*expr.expr, ExprF::Literal { value: Literal::Bool(true) })); } @@ -1002,7 +1001,6 @@ pub mod tests { let chislo = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; let (input, expr) = parse(chislo).unwrap(); assert_eq!(input, ""); - // assert!(matches!(*expr.1.typ, TypX::Int(IntRange::Int))); let ExprF::Literal { value: Literal::Int(ref bi) } = *expr.expr else { panic!() }; assert_eq!(bi.to_str_radix(10), chislo); } @@ -1012,7 +1010,6 @@ pub mod tests { let identche = "Banica_123_"; let (input, expr) = parse(identche).unwrap(); assert_eq!(input, ""); - // assert!(matches!(*expr.1.typ, TypX::Bool)); let ExprF::Variable(Variable { name: input, .. }) = *expr.expr else { panic!() }; assert_eq!(&input, identche); } From 0d5016bc4aa779a26303aa31fe7f74378d478979 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 76/86] chore(annotations): clarify wanted behaviour of `nom`'s `ParserError` --- .../formal_verification/src/parse/errors.rs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/compiler/formal_verification/src/parse/errors.rs b/compiler/formal_verification/src/parse/errors.rs index 892b4948e6a..1e775e24388 100644 --- a/compiler/formal_verification/src/parse/errors.rs +++ b/compiler/formal_verification/src/parse/errors.rs @@ -109,34 +109,40 @@ where } impl<'a> ParseError> for Error { - /// This function is called by nom's primitives when they fail. - /// It should create a generic error that can be enriched later by other combinators. fn from_error_kind(input: Input<'a>, kind: ErrorKind) -> Self { + // TODO: it'd be best in the future if we actually manage to suppress the builtin `nom` + // errors at all levels, even under the `expect and `map_nom_err` calls // unreachable!( // "We should wrap all errors and never have to convert from the built-in `nom` ones, still got a {:?} while parsing {:?} tho", // kind, input // ); + + // NOTE: these errors should not matter, because of our usage of `expect` and `map_nom_err` + // throughout the parser, ensuring that a primitive parser's error never sees the + // light of day let err = ParserError { offset: input_to_offset(input), - // Create a generic message from the nom ErrorKind. kind: ParserErrorKind::Message(format!("nom primitive failed: {:?}", kind)), }; Error { parser_errors: vec![err], contexts: vec![] } } - fn append(input: Input<'a>, kind: ErrorKind, mut other: Self) -> Self { - // // This is called when `alt` fails, for example. We can add to the error stack. - // let err = ParserError { - // span: input_to_span(input), - // kind: ParserErrorKind::Message(format!("nom append error: {:?}", kind)), - // }; - // other.parser_errors.push(err); + fn append(_input: Input<'a>, _kind: ErrorKind, other: Self) -> Self { + // TODO: it'd be best to assert that this never happens either, check the `TODO` comment in + // the `from_error_kind` function above + // unreachable!( + // "We should wrap all errors and never have to convert from the built-in `nom` ones, still got a {:?} on top of {:?} while parsing {:?} tho", + // kind, other, input + // ); + + // NOTE: This usually adds context of which primitive parsers were called before + // encountering the `other` error other } } impl<'a> ContextError> for Error { - fn add_context(input: Input<'a>, ctx: &'static str, mut other: Self) -> Self { + fn add_context(_input: Input<'a>, ctx: &'static str, mut other: Self) -> Self { other.contexts.push(ctx.to_string()); other } From 92d43ad121cafe5998a1409cedb7c33737a45a73 Mon Sep 17 00:00:00 2001 From: reo101 Date: Mon, 14 Jul 2025 07:39:35 +0300 Subject: [PATCH 77/86] chore(annotations): remove outdated comment about unit type --- compiler/formal_verification/src/parse.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 9b28877f219..8790b5c0810 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -638,8 +638,6 @@ pub(crate) fn parse_parenthesised_or_tuple_expr<'a>(input: Input<'a>) -> PResult // - `()` -> 0 expressions // - `(1,)` -> 1 expression with a trailing comma // - `(1, 2)` -> 2 expressions - // TODO: - // empty tuple vs unit? build_expr(prev_offset, after_offset, ExprF::Tuple { exprs }) }; From c1515640859715b141d9a88154006f8b839fd450 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 22 Aug 2025 14:00:41 +0300 Subject: [PATCH 78/86] feat(fv_std): Support calling fv_std::old in annotations Also we are now converting dereference expressions to VIR. Moved the test `fv_std_old` from `formal_verify_to_be_fixed` to `formal_verify_success`. --- compiler/fv_bridge/src/lib.rs | 35 +++++++++-- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 23 ++++++- .../vir/vir_gen/expr_to_vir/std_functions.rs | 60 ++++++++++++------- .../fv_std_old/Nargo.toml | 0 .../fv_std_old/src/main.nr | 0 5 files changed, 91 insertions(+), 27 deletions(-) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/fv_std_old/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/fv_std_old/src/main.nr (100%) diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index c2fd18bcdfa..005903dda6f 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -5,9 +5,10 @@ use formal_verification::typing::{OptionalType, TypeInferenceError, type_infer}; use formal_verification::{State, parse::parse_attribute}; use iter_extended::vecmap; use noirc_driver::{CompilationResult, CompileError, CompileOptions, check_crate}; -use noirc_errors::{CustomDiagnostic, Span}; use noirc_errors::Location; +use noirc_errors::{CustomDiagnostic, Span}; use noirc_evaluator::vir::vir_gen::Attribute; +use noirc_evaluator::vir::vir_gen::expr_to_vir::std_functions::OLD; use noirc_evaluator::{ errors::{RuntimeError, SsaReport}, vir::{create_verus_vir_with_ready_annotations, vir_gen::BuildingKrateError}, @@ -16,7 +17,8 @@ use noirc_frontend::Kind; use noirc_frontend::hir_def::expr::HirCallExpression; use noirc_frontend::hir_def::expr::{HirExpression, HirLiteral}; use noirc_frontend::monomorphization::ast::LocalId; -use noirc_frontend::node_interner::ExprId; +use noirc_frontend::node_interner::{ExprId, FunctionModifiers}; +use noirc_frontend::token::SecondaryAttribute; use noirc_frontend::{ debug::DebugInstrumenter, graph::CrateId, @@ -240,7 +242,7 @@ fn modified_monomorphize( for (annotation_body, location) in attribute_data { let function_for_parser = &monomorphizer.finished_functions[&new_func_id]; - + // NOTE: #['...] // ^^^^^^^ - received `Location` // ^^^ - relevant stuff @@ -495,8 +497,33 @@ fn monomorphize_one_function( new_ids_to_old_ids.insert(new_id, next_fn_id); undo_instantiation_bindings(impl_bindings); undo_instantiation_bindings(bindings); - to_be_added_ghost_attribute.insert(new_id); + + if has_ghost_attribute(monomorphizer.interner.function_modifiers(&func_id)) { + to_be_added_ghost_attribute.insert(new_id); + } else if func_name == OLD { + // Note: We don't want to monomorphize `fv_std::old` into + // a ghost function because we may get a verifier error for + // having a ghost function with a mut reference parameter type. + + // The function call to `fv_std::old` gets converted to a special + // Verus expressions anyways. Therefore the function `old` is not + // being actually called anywhere in the code. + + // Therefore we don't want to produce an error + } else { + // A non-ghost function has been called in a FV annotation. + // This isn't allowed and we have to produce an adequate error. + + // TODO(totel): Better error + panic!("Non-ghost function was called in a FV annotation"); + } } Ok(()) } + +fn has_ghost_attribute(func_modifiers: &FunctionModifiers) -> bool { + func_modifiers.attributes.secondary.iter().find(|SecondaryAttribute { kind, location: _ }| { + matches!(kind, SecondaryAttributeKind::Tag(tag) if tag == "ghost") + }).is_some() +} diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index 689113156e1..076223abf6f 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -8,6 +8,7 @@ use noirc_evaluator::vir::vir_gen::{ Attribute, build_span, build_span_no_id, expr_to_vir::{ expr::{function_name_to_vir_fun, numeric_const_to_vir_exprx, wrap_with_field_modulo}, + std_functions::handle_fv_std_call_in_annotations, types::{ ast_const_to_vir_type_const, ast_type_to_vir_type, get_bit_not_bitwidth, is_type_field, }, @@ -139,6 +140,13 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> } ExprF::FnCall { name, args } => { // TODO(totel): Special handling for `old` from the Noir `fv_std` + + if let Some(expr) = + handle_fv_std_call_in_annotations(&name, &args, loc, &typ) + { + return expr; + } + let exprx = ExprX::Call( CallTarget::Fun( CallTargetKind::Static, @@ -200,8 +208,19 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> } } formal_verification::ast::UnaryOp::Dereference => { - // TODO(totel): conversion of cast expressions - todo!() + // If we have Dereference(Expr), Verus treats them as if it is only Expr. + // Also the expr type gets trimmed from the reference "decoration". + // Therefore we will do the same and only change the type of the expression. + let deref_expr = SpannedTyped::new( + &build_span_no_id( + format!("Dereference expression with type {}", typ), + Some(loc), + ), + &ast_type_to_vir_type(&typ), + expr.x.clone(), // Can not move out of Arc + ); + + return deref_expr; } }; make_expr(exprx, ast_type_to_vir_type(&typ), span_msg) diff --git a/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/std_functions.rs b/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/std_functions.rs index dfe2af9dd64..3f33f73584c 100644 --- a/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/std_functions.rs +++ b/compiler/noirc_evaluator/src/vir/vir_gen/expr_to_vir/std_functions.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; +use noirc_errors::Location; use noirc_frontend::monomorphization::ast::{Call, Expression, GlobalId, Type}; use vir::ast::{Expr, ExprX, Mode, SpannedTyped}; @@ -17,7 +18,7 @@ use crate::vir::vir_gen::{ /// The function `assume()` from `fv_std_lib` static ASSUME: &str = "assume"; /// The function `old()` from `fv_std_lib` -static OLD: &str = "old"; +pub static OLD: &str = "old"; /// Handles function calls from the `fv_std` library and converts them to special VIR expressions. pub fn handle_fv_std_call( @@ -30,51 +31,68 @@ pub fn handle_fv_std_call( _ => return None, }; - match ident.name.as_str() { + // Convert Noir AST exprs into VIR exprs + let arguments = call_expr + .arguments + .iter() + .map(|arg| ast_expr_to_vir_expr(arg, Mode::Spec, globals)) + .collect::>(); + + handle_fv_std_inner(&ident.name, arguments, call_expr.location, &call_expr.return_type) +} + +/// Handles function calls from the `fv_std` library and converts them to special VIR expressions. +pub fn handle_fv_std_call_in_annotations( + function_name: &str, + arguments: &Vec, + location: Location, + return_typ: &Type, +) -> Option { + handle_fv_std_inner(function_name, arguments.clone(), location, return_typ) +} + +fn handle_fv_std_inner( + function_name: &str, + arguments: Vec, + location: Location, + return_typ: &Type, +) -> Option { + match function_name { // Special logic for handling the function `assume` from our Noir `fv_std` library s if s == ASSUME => { assert!( - call_expr.arguments.len() == 1, + arguments.len() == 1, "Expected function `assume` from `noir_fv_std` to have exactly one argument" ); - let condition_expr = ast_expr_to_vir_expr(&call_expr.arguments[0], Mode::Spec, globals); - let exprx = ExprX::AssertAssume { is_assume: true, expr: condition_expr }; + let condition_expr = arguments.into_iter().next().unwrap(); + let exprx = ExprX::AssertAssume { is_assume: true, expr: condition_expr }; let assume_expr = SpannedTyped::new( - &build_span_no_id( - format!("Assume {} is true", call_expr.arguments[0]), - Some(call_expr.location), - ), + &build_span_no_id("Assume expression".to_string(), Some(location)), &make_unit_vir_type(), exprx, ); - Some(wrap_with_ghost_block(assume_expr, Some(call_expr.location))) + Some(wrap_with_ghost_block(assume_expr, Some(location))) } // Special logic for handling the function `old` from our Noir `fv_std` library s if s == OLD => { assert!( - call_expr.arguments.len() == 1, + arguments.len() == 1, "Expected function `old` from `noir_fv_std` to have exactly one argument" ); - let Expression::Ident(var_ident) = &call_expr.arguments[0] else { + let ExprX::Var(vir_ident) = &arguments[0].x else { return None; }; - let ident_id = - ast_definition_to_id(&var_ident.definition).expect("Definition doesn't have an id"); - let vir_ident = ast_ident_to_vir_var_ident(var_ident, ident_id); - let exprx = ExprX::VarAt(vir_ident, vir::ast::VarAt::Pre); + let exprx = ExprX::VarAt(vir_ident.clone(), vir::ast::VarAt::Pre); Some(SpannedTyped::new( - &build_span_no_id( - format!("old({})", call_expr.arguments[0]), - Some(call_expr.location), - ), - &ast_type_to_vir_type(&call_expr.return_type), + &build_span_no_id(format!("old({})", vir_ident.0), Some(location)), + &ast_type_to_vir_type(return_typ), exprx, )) } diff --git a/test_programs/formal_verify_to_be_fixed/fv_std_old/Nargo.toml b/test_programs/formal_verify_success/fv_std_old/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/fv_std_old/Nargo.toml rename to test_programs/formal_verify_success/fv_std_old/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/fv_std_old/src/main.nr b/test_programs/formal_verify_success/fv_std_old/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/fv_std_old/src/main.nr rename to test_programs/formal_verify_success/fv_std_old/src/main.nr From b39de5a312f993b2270e322b029024e8d99be575 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 22 Aug 2025 14:34:47 +0300 Subject: [PATCH 79/86] feat(fv_std): Support global consts We now support global variables inside of annotations. We also now type infer variables with paths. We do that by ignoring the path, beacuse in the `state` parameter the paths are already omitted. This means that all tests which use `fv_std` now pass successfully. --- compiler/formal_verification/src/typing.rs | 19 ++++++++++++++++--- .../integer_consts/Nargo.toml | 0 .../integer_consts/src/main.nr | 0 3 files changed, 16 insertions(+), 3 deletions(-) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/integer_consts/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/integer_consts/src/main.nr (100%) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 5d118e9a90f..30ca6c9d848 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -329,9 +329,9 @@ pub fn type_infer( OptionalType::IntegerLiteral } }, - ExprF::Variable(Variable { path, name, id }) => { - // TODO: we do not support type inferrence of variables with paths - debug_assert_eq!(path.len(), 0); + ExprF::Variable(Variable { path: _, name, id }) => { + // NOTE: Ignoring paths since they're already stripped from the provided state + // NOTE: parsing should not yield `id`s debug_assert_eq!(*id, None); let (variable_ident, variable_id, variable_type): ( @@ -352,6 +352,19 @@ pub fn type_infer( }) }) }) + .or_else(|| { + state.global_constants.iter().find_map( + |(global_id, (global_name, t, _))| { + (global_name == name).then(|| { + ( + name.as_str(), + Some(global_id.0), + OptionalType::Well(t.clone()), + ) + }) + }, + ) + }) .or_else(|| { (name == "result").then(|| { ( diff --git a/test_programs/formal_verify_to_be_fixed/integer_consts/Nargo.toml b/test_programs/formal_verify_success/integer_consts/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/integer_consts/Nargo.toml rename to test_programs/formal_verify_success/integer_consts/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/integer_consts/src/main.nr b/test_programs/formal_verify_success/integer_consts/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/integer_consts/src/main.nr rename to test_programs/formal_verify_success/integer_consts/src/main.nr From 4405e965a92bcdbab59a5c47a8434fb9558c9932 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 22 Aug 2025 15:46:59 +0300 Subject: [PATCH 80/86] chore(errors): Exec function in Spec code error We now have a new error type for the case when we have an exec function call inside of FV annotation. Added a test which produces this error. --- Cargo.lock | 1 + compiler/fv_bridge/Cargo.toml | 1 + compiler/fv_bridge/src/errors.rs | 25 +++++++++++++++++-- compiler/fv_bridge/src/lib.rs | 16 +++++++----- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 2 -- .../exec_function_in_attribute/Nargo.toml | 7 ++++++ .../exec_function_in_attribute/src/main.nr | 13 ++++++++++ 7 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 test_programs/formal_verify_failure/exec_function_in_attribute/Nargo.toml create mode 100644 test_programs/formal_verify_failure/exec_function_in_attribute/src/main.nr diff --git a/Cargo.lock b/Cargo.lock index 5c7ba98edd1..a043f07d810 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2136,6 +2136,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "serde", + "thiserror 1.0.69", "vir", ] diff --git a/compiler/fv_bridge/Cargo.toml b/compiler/fv_bridge/Cargo.toml index 51d4b87012c..27c7d12bc9b 100644 --- a/compiler/fv_bridge/Cargo.toml +++ b/compiler/fv_bridge/Cargo.toml @@ -23,6 +23,7 @@ formal_verification.workspace = true acvm.workspace = true num-bigint.workspace = true num-traits.workspace = true +thiserror.workspace = true [dev-dependencies] iter-extended.workspace = true diff --git a/compiler/fv_bridge/src/errors.rs b/compiler/fv_bridge/src/errors.rs index 858d6b13af3..989b6c1377a 100644 --- a/compiler/fv_bridge/src/errors.rs +++ b/compiler/fv_bridge/src/errors.rs @@ -10,23 +10,44 @@ use noirc_frontend::{ monomorphization::{ast::Type, errors::MonomorphizationError}, parser::ParserError as NoirParserError, }; +use thiserror::Error; use crate::typed_attrs_to_vir::signed_field_from_bigint_wrapping; pub(crate) enum MonomorphizationErrorBundle { MonomorphizationError(MonomorphizationError), - ResolverErrors(Vec), + FvError(FvMonomorphizationError), TypeError(TypeCheckError), ParserErrors(Vec), } pub(crate) enum CompilationErrorBundle { CompileError(CompileError), - ResolverErrors(Vec), + FvError(FvMonomorphizationError), TypeError(TypeCheckError), ParserErrors(Vec), } +#[derive(Error, Debug, Clone)] +pub(crate) enum FvMonomorphizationError { + #[error("Non-ghost function {func_name} was called in FV annotation")] + ExecInSpecError { func_name: String, location: Location }, +} + +impl From for CustomDiagnostic { + fn from(value: FvMonomorphizationError) -> Self { + match value { + FvMonomorphizationError::ExecInSpecError { func_name, location } => { + CustomDiagnostic::simple_error( + format!("Non-ghost function {func_name} was called in FV annotation"), + String::new(), + location, + ) + } + } + } +} + impl From for MonomorphizationErrorBundle { fn from(value: TypeInferenceError) -> Self { match value { diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 005903dda6f..a065c3df94c 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -71,8 +71,8 @@ fn modified_compile_main( let compiled_program = modified_compile_no_check(context, options, main).map_err(|error| match error { CompilationErrorBundle::CompileError(compile_error) => vec![compile_error.into()], - CompilationErrorBundle::ResolverErrors(resolver_errors) => { - resolver_errors.iter().map(Into::into).collect() + CompilationErrorBundle::FvError(fv_monomorphization_error) => { + vec![fv_monomorphization_error.into()] } CompilationErrorBundle::TypeError(type_check_error) => { vec![CustomDiagnostic::from(&type_check_error)] @@ -113,8 +113,8 @@ fn modified_compile_no_check( monomorphization_error, )) } - MonomorphizationErrorBundle::ResolverErrors(resolver_errors) => { - CompilationErrorBundle::ResolverErrors(resolver_errors) + MonomorphizationErrorBundle::FvError(fv_monomorphization_error) => { + CompilationErrorBundle::FvError(fv_monomorphization_error) } MonomorphizationErrorBundle::TypeError(type_check_error) => { CompilationErrorBundle::TypeError(type_check_error) @@ -514,8 +514,12 @@ fn monomorphize_one_function( // A non-ghost function has been called in a FV annotation. // This isn't allowed and we have to produce an adequate error. - // TODO(totel): Better error - panic!("Non-ghost function was called in a FV annotation"); + return Err(MonomorphizationErrorBundle::FvError( + errors::FvMonomorphizationError::ExecInSpecError { + func_name: func_name.to_string(), + location: monomorphizer.interner.function_meta(&func_id).location, + }, + )); } } diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index 076223abf6f..a1fb14605e3 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -139,8 +139,6 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> SpannedTyped::new(&span, &ast_type_to_vir_type(&typ), exprx) } ExprF::FnCall { name, args } => { - // TODO(totel): Special handling for `old` from the Noir `fv_std` - if let Some(expr) = handle_fv_std_call_in_annotations(&name, &args, loc, &typ) { diff --git a/test_programs/formal_verify_failure/exec_function_in_attribute/Nargo.toml b/test_programs/formal_verify_failure/exec_function_in_attribute/Nargo.toml new file mode 100644 index 00000000000..9b6fc7cdb40 --- /dev/null +++ b/test_programs/formal_verify_failure/exec_function_in_attribute/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "exec_function_in_attribute" +type = "bin" +authors = [""] +compiler_version = ">=0.35.0" + +[dependencies] diff --git a/test_programs/formal_verify_failure/exec_function_in_attribute/src/main.nr b/test_programs/formal_verify_failure/exec_function_in_attribute/src/main.nr new file mode 100644 index 00000000000..667d5803c51 --- /dev/null +++ b/test_programs/formal_verify_failure/exec_function_in_attribute/src/main.nr @@ -0,0 +1,13 @@ +#['ensures(result == baz())] +fn main(x: Field, y: pub Field) -> pub u32 { + foo().1 +} + +#['ensures(result.1 == 2)] +fn foo() -> (u32, u32){ + (1, 2) +} + +fn baz() -> u32 { + 2 +} From 53ee4519f87395339d080a1204cf080b26a8cc45 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 22 Aug 2025 17:49:50 +0300 Subject: [PATCH 81/86] chore(fv_std): Add TODO comment for paths --- compiler/formal_verification/src/typing.rs | 4 +++- compiler/fv_bridge/src/lib.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 30ca6c9d848..b3ba64b0865 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -330,7 +330,9 @@ pub fn type_infer( } }, ExprF::Variable(Variable { path: _, name, id }) => { - // NOTE: Ignoring paths since they're already stripped from the provided state + // TODO(totel): Ignoring paths since they're already stripped from the provided state parameter + // This has to be resolved by adding paths to the State which is passed to `type_infer`. + // We then must compare the paths. // NOTE: parsing should not yield `id`s debug_assert_eq!(*id, None); diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index a065c3df94c..fe39b480330 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -527,7 +527,7 @@ fn monomorphize_one_function( } fn has_ghost_attribute(func_modifiers: &FunctionModifiers) -> bool { - func_modifiers.attributes.secondary.iter().find(|SecondaryAttribute { kind, location: _ }| { + func_modifiers.attributes.secondary.iter().any(|SecondaryAttribute { kind, location: _ }| { matches!(kind, SecondaryAttributeKind::Tag(tag) if tag == "ghost") - }).is_some() + }) } From 2dc2db2be95b81883a2ebb7fb8a39c32477629c3 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Mon, 25 Aug 2025 14:30:24 +0300 Subject: [PATCH 82/86] feat(annotations): Parser for structure access op --- compiler/formal_verification/src/ast.rs | 7 +++ compiler/formal_verification/src/parse.rs | 66 +++++++++++++++++++- compiler/formal_verification/src/typing.rs | 22 ++++++- compiler/fv_bridge/src/typed_attrs_to_vir.rs | 4 ++ 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index 9a8adef646f..c550aaefc2c 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -15,6 +15,7 @@ pub enum ExprF { FnCall { name: Identifier, args: Vec }, Index { expr: R, index: R }, TupleAccess { expr: R, index: u32 }, + StructureAccess { expr: R, field: Identifier }, Cast { expr: R, target: NoirType }, Literal { value: Literal }, Tuple { exprs: Vec }, @@ -147,6 +148,9 @@ pub fn fmap(expr: ExprF, cata_fn: &impl Fn(A) -> B) -> ExprF { } ExprF::Index { expr, index } => ExprF::Index { expr: cata_fn(expr), index: cata_fn(index) }, ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr), index }, + ExprF::StructureAccess { expr, field } => { + ExprF::StructureAccess { expr: cata_fn(expr), field } + } ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr), target }, ExprF::Literal { value } => ExprF::Literal { value }, ExprF::Tuple { exprs } => { @@ -179,6 +183,9 @@ fn try_fmap(expr: ExprF, cata_fn: &impl Fn(A) -> Result) -> Re ExprF::Index { expr: cata_fn(expr)?, index: cata_fn(index)? } } ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr)?, index }, + ExprF::StructureAccess { expr, field } => { + ExprF::StructureAccess { expr: cata_fn(expr)?, field } + } ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr)?, target }, ExprF::Literal { value } => ExprF::Literal { value }, ExprF::Tuple { exprs } => { diff --git a/compiler/formal_verification/src/parse.rs b/compiler/formal_verification/src/parse.rs index 8790b5c0810..d8fa1f57ae0 100644 --- a/compiler/formal_verification/src/parse.rs +++ b/compiler/formal_verification/src/parse.rs @@ -24,7 +24,7 @@ use errors::{ use crate::{ Attribute, - ast::{BinaryOp, ExprF, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, + ast::{BinaryOp, ExprF, Identifier, Literal, OffsetExpr, Quantifier, UnaryOp, Variable}, span_expr, }; @@ -462,6 +462,7 @@ pub(crate) enum Postfix { ArrayIndex(OffsetExpr), TupleMember(BigInt), Cast(CastTargetType), + FieldAccess(Identifier), } pub(crate) enum CastTargetType { @@ -498,6 +499,11 @@ pub(crate) fn parse_postfix_expr<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr ExprF::TupleAccess { expr: expr_base, index: index_u32 }, ) } + Postfix::FieldAccess(field) => build_expr( + prev_offset, + after_offset, + ExprF::StructureAccess { expr: expr_base, field }, + ), Postfix::Cast(target_type) => build_expr( prev_offset, after_offset, @@ -523,6 +529,7 @@ pub(crate) fn parse_any_suffix<'a>(input: Input<'a>) -> PResult<'a, Postfix> { alt(( context("index", map(parse_index_suffix, Postfix::ArrayIndex)), context("member", map(parse_member_suffix, Postfix::TupleMember)), + context("struct_field", map(parse_field_access_suffix, Postfix::FieldAccess)), context("cast", map(parse_cast_suffix, Postfix::Cast)), )) .parse(input) @@ -541,10 +548,21 @@ pub(crate) fn parse_index_suffix<'a>(input: Input<'a>) -> PResult<'a, OffsetExpr } pub(crate) fn parse_member_suffix<'a>(input: Input<'a>) -> PResult<'a, BigInt> { - preceded(pair(multispace, expect("'.' for tuple access".to_string(), tag("."))), cut(parse_int)) + preceded(pair(multispace, expect("'.' for tuple access".to_string(), tag("."))), parse_int) .parse(input) } +pub(crate) fn parse_field_access_suffix<'a>(input: Input<'a>) -> PResult<'a, String> { + map( + preceded( + pair(multispace, expect("'.' for field access".to_string(), tag("."))), + cut(parse_identifier), + ), + |s: &str| s.to_string(), + ) + .parse(input) +} + pub(crate) fn parse_cast_suffix<'a>(input: Input<'a>) -> PResult<'a, CastTargetType> { let (input, type_ident) = preceded( expect("'as'", delimited(multispace1, tag("as"), multispace1)), @@ -925,6 +943,18 @@ pub mod tests { ]), Visibility::Public, ), + ( + LocalId(6), + false, + "object".to_string(), + // Structures are of type Tuple in the Mon. Ast + NoirType::Tuple(vec![ + NoirType::Integer(Signedness::Unsigned, IntegerBitSize::Sixteen), + NoirType::Field, + NoirType::Bool, + ]), + Visibility::Public, + ), ], body: Expression::Block(vec![]), return_type: NoirType::Integer( @@ -960,7 +990,7 @@ pub mod tests { .into_iter() .collect(), )), - min_local_id: Rc::new(RefCell::new(6)), + min_local_id: Rc::new(RefCell::new(7)), } } @@ -1179,6 +1209,36 @@ pub mod tests { dbg!(strip_ann(expr)); } + #[test] + fn test_structure_access() { + let annotation = "ensures(object.some_field)"; + let state = empty_state(); + let attribute = parse_attribute( + annotation, + Location { + span: Span::inclusive(0, annotation.len() as u32), + file: Default::default(), + }, + state.function, + state.global_constants, + state.functions, + ) + .unwrap(); + + let Attribute::Ensures(expr) = attribute else { panic!("Expected an 'ensures' attribute") }; + + let ExprF::StructureAccess { expr: inner, field } = &*expr.expr else { + panic!("Expected a StructureAccess expression"); + }; + + let ExprF::Variable(Variable { name, .. }) = &*inner.expr else { + panic!("Expected inner expression to be a variable"); + }; + assert_eq!(name, "object"); + + assert_eq!(field, "some_field"); + } + #[test] fn test_parse_failure_identifier() { let annotation = "ensures(5 > 'invalid)"; diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index b3ba64b0865..1dbd9cd647d 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -219,7 +219,8 @@ impl SpannedPartiallyTypedExpr { | ExprF::Index { .. } | ExprF::Cast { .. } | ExprF::Tuple { .. } - | ExprF::Array { .. } => unreachable!( + | ExprF::Array { .. } + | ExprF::StructureAccess { .. } => unreachable!( "ICE: Unexpected expression {:?} found with IntegerLiteral type", self.expr ), @@ -292,6 +293,10 @@ pub fn type_infer( state: &State, expr: SpannedExpr, ) -> Result { + // TODO(totel): Transform all StructureAccess expressions into TupleAccess + + // TODO(totel): Assert that there are no Expressions of type StructureAccess in the AST + // NOTE: predicate, always bool, // assume subterms are `u32` (like `Noir` does) let default_literal_type = NoirType::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); @@ -982,6 +987,10 @@ pub fn type_infer( Box::new(concrete_element_type), )) } + ExprF::StructureAccess { .. } => { + // All StructureAccess have been converted into TupleAccess expressions + unreachable!() + } }; Ok(SpannedPartiallyTypedExpr { ann: (location, exprf_type), expr: Box::new(exprf) }) @@ -1020,6 +1029,11 @@ pub fn type_infer( // Non-recursive variants don't carry information ExprF::Literal { .. } => true, + + // All StructureAccess have been converted into TupleAccess expressions + ExprF::StructureAccess { .. } => { + unreachable!() + } })); Ok(fully_typed_expr) @@ -1146,6 +1160,8 @@ mod tests { // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, + + ExprF::StructureAccess { .. } => unreachable!(), } }), "All integer literals have the correct inferred type" @@ -1187,6 +1203,8 @@ mod tests { // Non-recursive variants don't carry information ExprF::Literal { value: Literal::Bool(_) } | ExprF::Variable(_) => true, + + ExprF::StructureAccess { .. } => unreachable!(), } }), "All integer literals have the correct inferred type" @@ -1216,6 +1234,8 @@ mod tests { // Non-recursive variants don't carry information ExprF::Literal { .. } => true, + + ExprF::StructureAccess { .. } => unreachable!(), } }), "All bound variables have the correct inferred type" diff --git a/compiler/fv_bridge/src/typed_attrs_to_vir.rs b/compiler/fv_bridge/src/typed_attrs_to_vir.rs index a1fb14605e3..6933970ddfb 100644 --- a/compiler/fv_bridge/src/typed_attrs_to_vir.rs +++ b/compiler/fv_bridge/src/typed_attrs_to_vir.rs @@ -412,6 +412,10 @@ pub(crate) fn ann_expr_to_vir_expr(ann_expr: SpannedTypedExpr, state: &State) -> exprx, ) } + ExprF::StructureAccess { .. } => { + // All expressions of type StructureAccess have been converted to TupleAccess + unreachable!() + } } }) } From 5ac40d34c468a39dafdd94d651c6bc8911e685eb Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Fri, 29 Aug 2025 15:04:50 +0300 Subject: [PATCH 83/86] feat(annotations): Convert structure access to tuple access Adds support for structures by converting all structure access operations to tuple indexing operations. All "to_be_fixed" tests have now been fixed. --- compiler/formal_verification/src/ast.rs | 44 +++++++ .../src/convert_structs.rs | 118 ++++++++++++++++++ compiler/formal_verification/src/lib.rs | 1 + compiler/formal_verification/src/typing.rs | 2 - compiler/fv_bridge/src/errors.rs | 2 + compiler/fv_bridge/src/lib.rs | 63 ++++++++++ .../Nargo.toml | 0 .../src/main.nr | 0 .../array_set_composite_types_3/Nargo.toml | 0 .../array_set_composite_types_3/src/main.nr | 0 .../call_struct_method/Nargo.toml | 0 .../call_struct_method/src/main.nr | 0 .../forall_structure/Nargo.toml | 0 .../forall_structure/src/main.nr | 0 .../Nargo.toml | 0 .../src/main.nr | 0 .../Nargo.toml | 0 .../src/main.nr | 0 .../Nargo.toml | 0 .../src/main.nr | 0 .../Nargo.toml | 0 .../src/main.nr | 0 .../Nargo.toml | 0 .../src/main.nr | 0 .../Nargo.toml | 0 .../src/main.nr | 0 .../struct/Nargo.toml | 0 .../struct/src/main.nr | 6 +- .../struct_return/Nargo.toml | 0 .../struct_return/src/main.nr | 0 30 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 compiler/formal_verification/src/convert_structs.rs rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/array_set_complex_composite_type/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/array_set_complex_composite_type/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/array_set_composite_types_3/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/array_set_composite_types_3/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/call_struct_method/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/call_struct_method/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/forall_structure/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/forall_structure/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_const/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_const/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_1/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_1/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_2/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_2/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_3/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_3/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_4/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_4/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_5/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/index_composite_array_with_var_5/src/main.nr (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/struct/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/struct/src/main.nr (56%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/struct_return/Nargo.toml (100%) rename test_programs/{formal_verify_to_be_fixed => formal_verify_success}/struct_return/src/main.nr (100%) diff --git a/compiler/formal_verification/src/ast.rs b/compiler/formal_verification/src/ast.rs index c550aaefc2c..181ab9f7f4e 100644 --- a/compiler/formal_verification/src/ast.rs +++ b/compiler/formal_verification/src/ast.rs @@ -200,6 +200,41 @@ fn try_fmap(expr: ExprF, cata_fn: &impl Fn(A) -> Result) -> Re }) } +fn try_fmap_mut(expr: ExprF, cata_fn: &mut impl FnMut(A) -> Result) -> Result, E> { + Ok(match expr { + ExprF::BinaryOp { op, expr_left, expr_right } => { + ExprF::BinaryOp { op, expr_left: cata_fn(expr_left)?, expr_right: cata_fn(expr_right)? } + } + ExprF::UnaryOp { op, expr } => ExprF::UnaryOp { op, expr: cata_fn(expr)? }, + ExprF::Parenthesised { expr } => ExprF::Parenthesised { expr: cata_fn(expr)? }, + ExprF::Quantified { quantifier, variables, expr } => { + ExprF::Quantified { quantifier, variables, expr: cata_fn(expr)? } + } + ExprF::FnCall { name, args } => { + let processed_args = args.into_iter().map(cata_fn).collect::, _>>()?; + ExprF::FnCall { name, args: processed_args } + } + ExprF::Index { expr, index } => { + ExprF::Index { index: cata_fn(index)? , expr: cata_fn(expr)? } + } + ExprF::TupleAccess { expr, index } => ExprF::TupleAccess { expr: cata_fn(expr)?, index }, + ExprF::StructureAccess { expr, field } => { + ExprF::StructureAccess { expr: cata_fn(expr)?, field } + } + ExprF::Cast { expr, target } => ExprF::Cast { expr: cata_fn(expr)?, target }, + ExprF::Literal { value } => ExprF::Literal { value }, + ExprF::Tuple { exprs } => { + ExprF::Tuple { exprs: exprs.into_iter().map(cata_fn).collect::, _>>()? } + } + ExprF::Array { exprs } => { + ExprF::Array { exprs: exprs.into_iter().map(cata_fn).collect::, _>>()? } + } + ExprF::Variable(Variable { path, name, id }) => { + ExprF::Variable(Variable { path, name, id }) + } + }) +} + pub fn cata(expr: AnnExpr, algebra: &impl Fn(A, ExprF) -> B) -> B { let children_results = fmap(*expr.expr, &|child| cata(child, algebra)); @@ -215,6 +250,15 @@ pub fn try_cata( algebra(expr.ann, children_results) } +pub fn try_cata_mut( + expr: AnnExpr, + algebra: &mut impl FnMut(A, ExprF) -> Result, +) -> Result { + let children_results = try_fmap_mut(*expr.expr, &mut |child| try_cata_mut(child, algebra))?; + + algebra(expr.ann, children_results) +} + pub fn try_contextual_cata( expr: AnnExpr, initial_context: C, diff --git a/compiler/formal_verification/src/convert_structs.rs b/compiler/formal_verification/src/convert_structs.rs new file mode 100644 index 00000000000..5e9ed1cbb21 --- /dev/null +++ b/compiler/formal_verification/src/convert_structs.rs @@ -0,0 +1,118 @@ +use std::collections::HashMap; + +use noirc_frontend::{ + ast::Ident, + hir::{resolution::errors::ResolverError, type_check::TypeCheckError}, +}; + +use crate::ast::{ExprF, SpannedExpr, try_cata_mut}; + +pub fn convert_struct_access_to_tuple_access( + expr: SpannedExpr, + parameters_hir_types: &HashMap, +) -> Result { + let mut last_important_type: Option = None; + + let res: SpannedExpr = + try_cata_mut(expr.clone(), &mut |loc, exprf| -> Result<_, ResolverOrTypeError> { + match exprf { + ExprF::Index { .. } => { + if let Some(last_type) = &last_important_type { + if let noirc_frontend::Type::Array(_size, inner_type) = last_type { + last_important_type = Some(*inner_type.clone()); + Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) + } else { + // Error, tried to index a non array type + Err(ResolverOrTypeError::TypeError(TypeCheckError::TypeMismatch { + expr_typ: last_type.to_string(), + expected_typ: String::from("array"), + expr_location: loc, + })) + } + } else { + Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) + } + } + ExprF::TupleAccess { ref index, .. } => { + if let Some(last_type) = &last_important_type { + if let noirc_frontend::Type::Tuple(inner_types) = last_type { + last_important_type = Some(inner_types[*index as usize].clone()); + Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) + } else { + // Error, tried to index a non tuple + Err(ResolverOrTypeError::TypeError(TypeCheckError::TypeMismatch { + expr_typ: last_type.to_string(), + expected_typ: String::from("tuple"), + expr_location: loc, + })) + } + } else { + Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) + } + } + ExprF::StructureAccess { expr, field } => { + if let Some(last_type) = last_important_type.take() { + if let noirc_frontend::Type::DataType(struct_type, _) = last_type { + if let Some((typ, _, index)) = + struct_type.borrow().get_field(&field, &[]) + { + last_important_type = Some(typ); + Ok(SpannedExpr { + ann: loc, + expr: Box::new(ExprF::TupleAccess { + expr: expr, + index: index as u32, + }), + }) + } else { + // Error tried to access a non existing field of a structure + Err(ResolverOrTypeError::ResolverError( + ResolverError::NoSuchField { + field: Ident::new(field, loc), + struct_definition: struct_type.borrow().name.clone(), + }, + )) + } + } else { + // Error, tried to access a field of a non struct type + Err(ResolverOrTypeError::TypeError(TypeCheckError::TypeMismatch { + expr_typ: last_type.to_string(), + expected_typ: String::from("structure"), + expr_location: loc, + })) + } + } else { + // Error, can't convert structure access to tuple access because + // of missing type information for inner expression + // Should be unreachable + unreachable!() + } + } + ExprF::Variable(ref var) => { + if let Some(var_typ) = parameters_hir_types.get(&var.name) { + last_important_type = Some(var_typ.clone()); + } else { + // NOTE: We are processing a quantifier index, therefore we don't have + // to keep track of its type because it's an integer + last_important_type = None; + } + Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) + } + ExprF::Parenthesised { .. } + | ExprF::UnaryOp { op: crate::ast::UnaryOp::Dereference, .. } => { + Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) + } + _ => { + last_important_type = None; + Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) + } + } + })?; + + Ok(res) +} + +pub enum ResolverOrTypeError { + ResolverError(ResolverError), + TypeError(TypeCheckError), +} diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index 154b37e23d7..d6d7ecbe546 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -10,6 +10,7 @@ use crate::{ // NOTE: all types inside are not prefixed, to be used as `ast::OffsetExpr` pub mod ast; +pub mod convert_structs; pub mod parse; pub mod type_conversion; pub mod typing; diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index 1dbd9cd647d..a557cc42e20 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -293,8 +293,6 @@ pub fn type_infer( state: &State, expr: SpannedExpr, ) -> Result { - // TODO(totel): Transform all StructureAccess expressions into TupleAccess - // TODO(totel): Assert that there are no Expressions of type StructureAccess in the AST // NOTE: predicate, always bool, diff --git a/compiler/fv_bridge/src/errors.rs b/compiler/fv_bridge/src/errors.rs index 989b6c1377a..97cbbff15f5 100644 --- a/compiler/fv_bridge/src/errors.rs +++ b/compiler/fv_bridge/src/errors.rs @@ -19,6 +19,7 @@ pub(crate) enum MonomorphizationErrorBundle { FvError(FvMonomorphizationError), TypeError(TypeCheckError), ParserErrors(Vec), + ResolverError(ResolverError), } pub(crate) enum CompilationErrorBundle { @@ -26,6 +27,7 @@ pub(crate) enum CompilationErrorBundle { FvError(FvMonomorphizationError), TypeError(TypeCheckError), ParserErrors(Vec), + ResolverError(ResolverError), } #[derive(Error, Debug, Clone)] diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index fe39b480330..17aae13c9f3 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -1,5 +1,6 @@ use fm::FileId; use formal_verification::ast::{AnnExpr, SpannedTypedExpr}; +use formal_verification::convert_structs::convert_struct_access_to_tuple_access; use formal_verification::type_conversion::convert_mast_to_noir_type; use formal_verification::typing::{OptionalType, TypeInferenceError, type_infer}; use formal_verification::{State, parse::parse_attribute}; @@ -16,6 +17,7 @@ use noirc_evaluator::{ use noirc_frontend::Kind; use noirc_frontend::hir_def::expr::HirCallExpression; use noirc_frontend::hir_def::expr::{HirExpression, HirLiteral}; +use noirc_frontend::hir_def::stmt::HirPattern; use noirc_frontend::monomorphization::ast::LocalId; use noirc_frontend::node_interner::{ExprId, FunctionModifiers}; use noirc_frontend::token::SecondaryAttribute; @@ -80,6 +82,9 @@ fn modified_compile_main( CompilationErrorBundle::ParserErrors(parser_errors) => { parser_errors.into_iter().map(CustomDiagnostic::from).collect() } + CompilationErrorBundle::ResolverError(resolver_error) => { + vec![CustomDiagnostic::from(&resolver_error)] + } })?; let compilation_warnings = vecmap(compiled_program.warnings.clone(), CustomDiagnostic::from); @@ -122,6 +127,9 @@ fn modified_compile_no_check( MonomorphizationErrorBundle::ParserErrors(parser_errors) => { CompilationErrorBundle::ParserErrors(parser_errors) } + MonomorphizationErrorBundle::ResolverError(resolver_error) => { + CompilationErrorBundle::ResolverError(resolver_error) + } })?; if options.show_monomorphized { @@ -224,6 +232,9 @@ fn modified_monomorphize( let mut to_be_added_ghost_attribute: HashSet = HashSet::new(); for (new_func_id, old_id) in functions_to_process { + let parameters_hir_types: HashMap = + collect_parameter_hir_types(&monomorphizer, &old_id); + let attribute_data: Vec<_> = monomorphizer .interner .function_attributes(&old_id) @@ -269,6 +280,7 @@ fn modified_monomorphize( new_func_id, &globals, min_available_id.clone(), + ¶meters_hir_types, expr, &mut new_ids_to_old_ids, &mut to_be_added_ghost_attribute, @@ -281,6 +293,7 @@ fn modified_monomorphize( new_func_id, &globals, min_available_id.clone(), + ¶meters_hir_types, expr, &mut new_ids_to_old_ids, &mut to_be_added_ghost_attribute, @@ -348,6 +361,7 @@ fn type_infer_attribute_expr( ), >, min_available_id: Rc>, + parameters_hir_types: &HashMap, expr: AnnExpr, new_ids_to_old_ids: &mut HashMap, to_be_added_ghost_attribute: &mut HashSet, @@ -367,6 +381,16 @@ fn type_infer_attribute_expr( min_local_id: min_available_id.clone(), }; + let expr = convert_struct_access_to_tuple_access(expr.clone(), parameters_hir_types) + .map_err(|e| match e { + formal_verification::convert_structs::ResolverOrTypeError::ResolverError( + resolver_error, + ) => MonomorphizationErrorBundle::ResolverError(resolver_error), + formal_verification::convert_structs::ResolverOrTypeError::TypeError( + type_check_error, + ) => MonomorphizationErrorBundle::TypeError(type_check_error), + })?; + match type_infer(&state, expr.clone()) { Ok(typed_expr) => { // Success, return immediately. @@ -531,3 +555,42 @@ fn has_ghost_attribute(func_modifiers: &FunctionModifiers) -> bool { matches!(kind, SecondaryAttributeKind::Tag(tag) if tag == "ghost") }) } + +fn collect_parameter_hir_types( + monomorphizer: &Monomorphizer, + old_id: &node_interner::FuncId, +) -> HashMap { + let interner = &monomorphizer.interner; + let func_meta = interner.function_meta(old_id); + + let mut struct_arguments: HashMap = func_meta + .parameters + .0 + .iter() + .map(|(hir_pattern, typ, _)| { + // Extract identifier from the pattern + let ident = match hir_pattern { + HirPattern::Identifier(ident) => ident, + HirPattern::Mutable(inner, _) => match inner.as_ref() { + HirPattern::Identifier(ident) => ident, + // NOTE: We assume that only the Hir patterns "Identifier" and "Mutable" + // appear for function parameters + other => unreachable!( + "Expected HirPattern::Identifier inside HirPattern::Mutable, got: {:?}", + other + ), + }, + other => unreachable!( + "Expected HirPattern::Identifier or HirPattern::Mutable, got: {:?}", + other + ), + }; + + (interner.definition(ident.id).name.clone(), typ.clone()) + }) + .collect(); + + struct_arguments.insert("result".to_string(), func_meta.return_type().clone()); + + struct_arguments +} diff --git a/test_programs/formal_verify_to_be_fixed/array_set_complex_composite_type/Nargo.toml b/test_programs/formal_verify_success/array_set_complex_composite_type/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/array_set_complex_composite_type/Nargo.toml rename to test_programs/formal_verify_success/array_set_complex_composite_type/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/array_set_complex_composite_type/src/main.nr b/test_programs/formal_verify_success/array_set_complex_composite_type/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/array_set_complex_composite_type/src/main.nr rename to test_programs/formal_verify_success/array_set_complex_composite_type/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/array_set_composite_types_3/Nargo.toml b/test_programs/formal_verify_success/array_set_composite_types_3/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/array_set_composite_types_3/Nargo.toml rename to test_programs/formal_verify_success/array_set_composite_types_3/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/array_set_composite_types_3/src/main.nr b/test_programs/formal_verify_success/array_set_composite_types_3/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/array_set_composite_types_3/src/main.nr rename to test_programs/formal_verify_success/array_set_composite_types_3/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/call_struct_method/Nargo.toml b/test_programs/formal_verify_success/call_struct_method/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/call_struct_method/Nargo.toml rename to test_programs/formal_verify_success/call_struct_method/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/call_struct_method/src/main.nr b/test_programs/formal_verify_success/call_struct_method/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/call_struct_method/src/main.nr rename to test_programs/formal_verify_success/call_struct_method/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/forall_structure/Nargo.toml b/test_programs/formal_verify_success/forall_structure/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/forall_structure/Nargo.toml rename to test_programs/formal_verify_success/forall_structure/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/forall_structure/src/main.nr b/test_programs/formal_verify_success/forall_structure/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/forall_structure/src/main.nr rename to test_programs/formal_verify_success/forall_structure/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_const/Nargo.toml b/test_programs/formal_verify_success/index_composite_array_with_const/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_const/Nargo.toml rename to test_programs/formal_verify_success/index_composite_array_with_const/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_const/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_const/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_const/src/main.nr rename to test_programs/formal_verify_success/index_composite_array_with_const/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_1/Nargo.toml b/test_programs/formal_verify_success/index_composite_array_with_var_1/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_1/Nargo.toml rename to test_programs/formal_verify_success/index_composite_array_with_var_1/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_1/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_1/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_1/src/main.nr rename to test_programs/formal_verify_success/index_composite_array_with_var_1/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_2/Nargo.toml b/test_programs/formal_verify_success/index_composite_array_with_var_2/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_2/Nargo.toml rename to test_programs/formal_verify_success/index_composite_array_with_var_2/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_2/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_2/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_2/src/main.nr rename to test_programs/formal_verify_success/index_composite_array_with_var_2/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_3/Nargo.toml b/test_programs/formal_verify_success/index_composite_array_with_var_3/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_3/Nargo.toml rename to test_programs/formal_verify_success/index_composite_array_with_var_3/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_3/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_3/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_3/src/main.nr rename to test_programs/formal_verify_success/index_composite_array_with_var_3/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_4/Nargo.toml b/test_programs/formal_verify_success/index_composite_array_with_var_4/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_4/Nargo.toml rename to test_programs/formal_verify_success/index_composite_array_with_var_4/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_4/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_4/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_4/src/main.nr rename to test_programs/formal_verify_success/index_composite_array_with_var_4/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_5/Nargo.toml b/test_programs/formal_verify_success/index_composite_array_with_var_5/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_5/Nargo.toml rename to test_programs/formal_verify_success/index_composite_array_with_var_5/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_5/src/main.nr b/test_programs/formal_verify_success/index_composite_array_with_var_5/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/index_composite_array_with_var_5/src/main.nr rename to test_programs/formal_verify_success/index_composite_array_with_var_5/src/main.nr diff --git a/test_programs/formal_verify_to_be_fixed/struct/Nargo.toml b/test_programs/formal_verify_success/struct/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/struct/Nargo.toml rename to test_programs/formal_verify_success/struct/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/struct/src/main.nr b/test_programs/formal_verify_success/struct/src/main.nr similarity index 56% rename from test_programs/formal_verify_to_be_fixed/struct/src/main.nr rename to test_programs/formal_verify_success/struct/src/main.nr index 71bc460d8fa..10a6d3b3876 100644 --- a/test_programs/formal_verify_to_be_fixed/struct/src/main.nr +++ b/test_programs/formal_verify_success/struct/src/main.nr @@ -1,7 +1,9 @@ -struct A { x: u8, y: u8 } +struct A { x: u8, y: u8, w: B } + +struct B { z: u32 } #['requires((a.x < 10) & (a.y < 10))] #['ensures((result.x == a.x + a.y) & (result.y == a.y))] fn main(a: A) -> pub A { - A { x: a.x + a.y, y: a.y } + A { x: a.x + a.y, y: a.y, w: B {z: 5} } } diff --git a/test_programs/formal_verify_to_be_fixed/struct_return/Nargo.toml b/test_programs/formal_verify_success/struct_return/Nargo.toml similarity index 100% rename from test_programs/formal_verify_to_be_fixed/struct_return/Nargo.toml rename to test_programs/formal_verify_success/struct_return/Nargo.toml diff --git a/test_programs/formal_verify_to_be_fixed/struct_return/src/main.nr b/test_programs/formal_verify_success/struct_return/src/main.nr similarity index 100% rename from test_programs/formal_verify_to_be_fixed/struct_return/src/main.nr rename to test_programs/formal_verify_success/struct_return/src/main.nr From 8aeb7dd9f7227a3f6303b8deae495292381ae903 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Mon, 1 Sep 2025 18:10:03 +0300 Subject: [PATCH 84/86] chore(fv): Expand unhandled case arms --- compiler/formal_verification/src/convert_structs.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/formal_verification/src/convert_structs.rs b/compiler/formal_verification/src/convert_structs.rs index 5e9ed1cbb21..4dcaad7835c 100644 --- a/compiler/formal_verification/src/convert_structs.rs +++ b/compiler/formal_verification/src/convert_structs.rs @@ -14,7 +14,7 @@ pub fn convert_struct_access_to_tuple_access( let mut last_important_type: Option = None; let res: SpannedExpr = - try_cata_mut(expr.clone(), &mut |loc, exprf| -> Result<_, ResolverOrTypeError> { + try_cata_mut(expr, &mut |loc, exprf| -> Result<_, ResolverOrTypeError> { match exprf { ExprF::Index { .. } => { if let Some(last_type) = &last_important_type { @@ -102,7 +102,14 @@ pub fn convert_struct_access_to_tuple_access( | ExprF::UnaryOp { op: crate::ast::UnaryOp::Dereference, .. } => { Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) } - _ => { + ExprF::BinaryOp { .. } + | ExprF::Quantified { .. } + | ExprF::FnCall { .. } + | ExprF::Cast { .. } + | ExprF::Literal { .. } + | ExprF::Tuple { .. } + | ExprF::Array { .. } + | ExprF::UnaryOp { .. } => { last_important_type = None; Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }) } From 1c17fb395b4ea6438fa23e670c511e8066218bf9 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Tue, 2 Sep 2025 19:53:20 +0300 Subject: [PATCH 85/86] feat(globals): Add support for pathed globals in annotations We can now call any global variable from any dependency inside of FV annotations. However there is a limitation. The user must always use the full path for constants located in foreign dependencies. Multiple tests are added to show off the feature and its limitation. --- .../formal_verification/src/inline_globals.rs | 152 ++++++++++++++++++ compiler/formal_verification/src/lib.rs | 1 + compiler/formal_verification/src/typing.rs | 8 +- compiler/fv_bridge/src/lib.rs | 100 +++++++++++- .../non_full_path_global_var/Nargo.toml | 7 + .../non_full_path_global_var/src/main.nr | 8 + .../global_const_in_module/Nargo.toml | 7 + .../global_const_in_module/src/main.nr | 9 ++ .../global_const_in_nested_module/Nargo.toml | 7 + .../global_const_in_nested_module/src/main.nr | 12 ++ .../shadow_global_const/Nargo.toml | 6 + .../shadow_global_const/src/main.nr | 8 + 12 files changed, 320 insertions(+), 5 deletions(-) create mode 100644 compiler/formal_verification/src/inline_globals.rs create mode 100644 test_programs/formal_verify_failure/non_full_path_global_var/Nargo.toml create mode 100644 test_programs/formal_verify_failure/non_full_path_global_var/src/main.nr create mode 100644 test_programs/formal_verify_success/global_const_in_module/Nargo.toml create mode 100644 test_programs/formal_verify_success/global_const_in_module/src/main.nr create mode 100644 test_programs/formal_verify_success/global_const_in_nested_module/Nargo.toml create mode 100644 test_programs/formal_verify_success/global_const_in_nested_module/src/main.nr create mode 100644 test_programs/formal_verify_success/shadow_global_const/Nargo.toml create mode 100644 test_programs/formal_verify_success/shadow_global_const/src/main.nr diff --git a/compiler/formal_verification/src/inline_globals.rs b/compiler/formal_verification/src/inline_globals.rs new file mode 100644 index 00000000000..977d1c8ee9f --- /dev/null +++ b/compiler/formal_verification/src/inline_globals.rs @@ -0,0 +1,152 @@ +use noirc_errors::Location; +use noirc_frontend::{ + ast::Ident, + hir::{ + comptime::Value, + resolution::{errors::ResolverError, import::PathResolutionError}, + }, + node_interner::GlobalValue, +}; +use num_bigint::{BigInt, BigUint, Sign}; +use std::collections::HashMap; + +use crate::ast::{AnnExpr, ExprF, Literal, SpannedExpr, try_cata}; + +/// Replaces all global identifiers with their resolved constant value. +pub fn inline_global_consts( + expr: SpannedExpr, + pathed_globals_with_values: &HashMap, + function_parameters: &[&str], +) -> Result { + try_cata(expr, &|loc, exprf| -> Result<_, ResolverError> { + match exprf { + ExprF::Variable(variable) => { + // --- Guard 1: Handle function parameters first --- + // Parameters have the highest precedence and are never inlined. + let is_parameter = variable.path.is_empty() + && function_parameters.contains(&variable.name.as_str()); + + if is_parameter { + // It's a parameter. Leave it as is and we're done with this variable. + return Ok(SpannedExpr { ann: loc, expr: Box::new(ExprF::Variable(variable)) }); + } + + // --- Guard 2: Try to resolve as a global constant --- + // At this point, we know it's not a shadowed parameter. + let full_path = join_path_segments(variable.path.clone(), variable.name.clone()); + if let Some(global_val) = pathed_globals_with_values.get(&full_path) { + // It's a valid global. Inline it. + return match global_val { + GlobalValue::Unresolved | GlobalValue::Resolving => { + unreachable!("All global constants must have been resolved by now") + } + GlobalValue::Resolved(value) => { + let global_const_as_exprf = resolved_value_to_exprf(value, loc)?; + Ok(SpannedExpr { ann: loc, expr: Box::new(global_const_as_exprf) }) + } + }; + } + + // --- Guard 3: Check for invalid, unresolved paths --- + // It's not a parameter and not a known global. If it has a path, it's an error. + if !variable.path.is_empty() { + return Err(ResolverError::PathResolutionError( + PathResolutionError::Unresolved(Ident::new(full_path, loc)), + )); + } + + // --- Default Case --- + // If none of the guards above returned, the variable must be a local identifier + // that is not a parameter (e.g., `result`, quantifier, or undeclared variable). + // We leave it as is for a later compiler stage to handle. + Ok(SpannedExpr { ann: loc, expr: Box::new(ExprF::Variable(variable)) }) + } + // For any other expression type, just reconstruct it. + _ => Ok(SpannedExpr { ann: loc, expr: Box::new(exprf) }), + } + }) +} + +fn resolved_value_to_exprf( + value: &Value, + location: Location, +) -> Result>, ResolverError> { + Ok(match value { + Value::Unit => ExprF::Tuple { exprs: Vec::new() }, + Value::Bool(bool_val) => ExprF::Literal { value: Literal::Bool(*bool_val) }, + Value::Field(signed_field) => { + let field_as_big_uint: BigUint = signed_field.to_field_element().into_repr().into(); + ExprF::Literal { + value: Literal::Int(BigInt::from_biguint(Sign::Plus, field_as_big_uint)), + } + } + Value::I8(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::I16(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::I32(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::I64(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::U1(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::U8(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::U16(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::U32(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::U64(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::U128(integer) => ExprF::Literal { value: Literal::Int(BigInt::from(*integer)) }, + Value::Tuple(values) => { + let exprs = values + .iter() + .map(|val| { + let exprf = resolved_value_to_exprf(val, location)?; + Ok(SpannedExpr { ann: location, expr: Box::new(exprf) }) + }) + .collect::>()?; + ExprF::Tuple { exprs } + } + Value::Array(values, _) => { + let exprs = values + .iter() + .map(|val| { + let exprf = resolved_value_to_exprf(val, location)?; + Ok(SpannedExpr { ann: location, expr: Box::new(exprf) }) + }) + .collect::>()?; + ExprF::Array { exprs } + } + Value::String(..) + | Value::FormatString(..) + | Value::CtString(..) + // We currently don't support strings in formal verification + | Value::Function(..) + | Value::Closure(..) + | Value::Struct(..) + | Value::Enum(..) + | Value::Pointer(..) + | Value::Slice(..) + | Value::Quoted(..) + | Value::TypeDefinition(..) + | Value::TraitConstraint(..) + | Value::TraitDefinition(..) + | Value::TraitImpl(..) + | Value::FunctionDefinition(..) + | Value::ModuleDefinition(..) + | Value::Type(..) + | Value::Zeroed(..) + | Value::Expr(..) + | Value::TypedExpr(..) + | Value::UnresolvedType(..) => { + // We believe that all global const values are resolved + return Err(ResolverError::UnevaluatedGlobalType { location }); + } + }) +} + +/// Joins path segments and an identifier into a single "::" delimited string. +/// +/// # Arguments +/// * `path_parts` - A vector of strings for the path's base (e.g., ["super", "foo"]). +/// * `identifier` - The final identifier to append (e.g., "bar"). +/// +/// # Returns +/// A single string like "super::foo::bar". +fn join_path_segments(mut path_parts: Vec, identifier: String) -> String { + path_parts.push(identifier); + path_parts.join("::") +} diff --git a/compiler/formal_verification/src/lib.rs b/compiler/formal_verification/src/lib.rs index d6d7ecbe546..83c0a53322c 100644 --- a/compiler/formal_verification/src/lib.rs +++ b/compiler/formal_verification/src/lib.rs @@ -11,6 +11,7 @@ use crate::{ // NOTE: all types inside are not prefixed, to be used as `ast::OffsetExpr` pub mod ast; pub mod convert_structs; +pub mod inline_globals; pub mod parse; pub mod type_conversion; pub mod typing; diff --git a/compiler/formal_verification/src/typing.rs b/compiler/formal_verification/src/typing.rs index a557cc42e20..628022acb39 100644 --- a/compiler/formal_verification/src/typing.rs +++ b/compiler/formal_verification/src/typing.rs @@ -332,10 +332,10 @@ pub fn type_infer( OptionalType::IntegerLiteral } }, - ExprF::Variable(Variable { path: _, name, id }) => { - // TODO(totel): Ignoring paths since they're already stripped from the provided state parameter - // This has to be resolved by adding paths to the State which is passed to `type_infer`. - // We then must compare the paths. + ExprF::Variable(Variable { path, name, id }) => { + // NOTE: All paths should be empty because we have inlined all global variables with paths. + // This occurs in the `inline_globals` pass located in `compiler/formal_verification/src/inline_globals.rs` + assert!(path.is_empty()); // NOTE: parsing should not yield `id`s debug_assert_eq!(*id, None); diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 17aae13c9f3..36c99a995d0 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -1,6 +1,7 @@ use fm::FileId; use formal_verification::ast::{AnnExpr, SpannedTypedExpr}; use formal_verification::convert_structs::convert_struct_access_to_tuple_access; +use formal_verification::inline_globals::inline_global_consts; use formal_verification::type_conversion::convert_mast_to_noir_type; use formal_verification::typing::{OptionalType, TypeInferenceError, type_infer}; use formal_verification::{State, parse::parse_attribute}; @@ -15,11 +16,13 @@ use noirc_evaluator::{ vir::{create_verus_vir_with_ready_annotations, vir_gen::BuildingKrateError}, }; use noirc_frontend::Kind; +use noirc_frontend::graph::CrateGraph; +use noirc_frontend::hir::def_map::{DefMaps, ModuleId, fully_qualified_module_path}; use noirc_frontend::hir_def::expr::HirCallExpression; use noirc_frontend::hir_def::expr::{HirExpression, HirLiteral}; use noirc_frontend::hir_def::stmt::HirPattern; use noirc_frontend::monomorphization::ast::LocalId; -use noirc_frontend::node_interner::{ExprId, FunctionModifiers}; +use noirc_frontend::node_interner::{ExprId, FunctionModifiers, GlobalValue}; use noirc_frontend::token::SecondaryAttribute; use noirc_frontend::{ debug::DebugInstrumenter, @@ -111,6 +114,8 @@ fn modified_compile_no_check( &mut context.def_interner, &DebugInstrumenter::default(), force_unconstrained, + &context.crate_graph, + &context.def_maps, ) .map_err(|e| match e { MonomorphizationErrorBundle::MonomorphizationError(monomorphization_error) => { @@ -163,6 +168,8 @@ fn modified_monomorphize( interner: &mut NodeInterner, debug_instrumenter: &DebugInstrumenter, force_unconstrained: bool, + crate_graph: &CrateGraph, + def_maps: &DefMaps, ) -> Result<(Program, Vec<(FuncId, Vec)>), MonomorphizationErrorBundle> { let debug_type_tracker = DebugTypeTracker::build_from_debug_instrumenter(debug_instrumenter); // NOTE: Monomorphizer is a `pub(crate)` struct which we changed to pub @@ -231,10 +238,27 @@ fn modified_monomorphize( // manually add ghost attributes to them. let mut to_be_added_ghost_attribute: HashSet = HashSet::new(); + // Initialize the globals map and a tracker for the last seen crate ID outside the loop. + let mut globals_with_paths: HashMap = HashMap::new(); + let mut last_crate_id: Option = None; + for (new_func_id, old_id) in functions_to_process { let parameters_hir_types: HashMap = collect_parameter_hir_types(&monomorphizer, &old_id); + // Determine the crate ID of the function currently being processed. + let current_func_crate_id = monomorphizer.interner.function_meta(&old_id).source_crate; + + // Conditionally update the globals map based on the current crate context. + update_globals_if_needed( + &mut last_crate_id, + &mut globals_with_paths, + current_func_crate_id, + &monomorphizer, + def_maps, + crate_graph, + ); + let attribute_data: Vec<_> = monomorphizer .interner .function_attributes(&old_id) @@ -281,6 +305,7 @@ fn modified_monomorphize( &globals, min_available_id.clone(), ¶meters_hir_types, + &globals_with_paths, expr, &mut new_ids_to_old_ids, &mut to_be_added_ghost_attribute, @@ -294,6 +319,7 @@ fn modified_monomorphize( &globals, min_available_id.clone(), ¶meters_hir_types, + &globals_with_paths, expr, &mut new_ids_to_old_ids, &mut to_be_added_ghost_attribute, @@ -362,6 +388,7 @@ fn type_infer_attribute_expr( >, min_available_id: Rc>, parameters_hir_types: &HashMap, + pathed_globals_with_values: &HashMap, expr: AnnExpr, new_ids_to_old_ids: &mut HashMap, to_be_added_ghost_attribute: &mut HashSet, @@ -391,6 +418,32 @@ fn type_infer_attribute_expr( ) => MonomorphizationErrorBundle::TypeError(type_check_error), })?; + let param_names: Vec<&str> = state + .function + .parameters + .iter() + .map(|(_, _, param_name, _, _)| param_name.as_str()) + .collect(); + // NOTE: If a global variable is added to the scope via `use foo::x` it wont be added to + // the `finished_globals` map unless it's being used somewhere in a function's body. + // Therefore the user has to use the full path for global variables even though they + // are added to the scope. + + // NOTE: Because the `finished_globals` map doesn't contain + // all globals but only the ones which are used inside of function's + // body and the fact that we can not easily add values to that map, + // we are doing the following: + // We are gathering all globals from the HIR form of the AST. + // We visit the FV annotation AST and if we encounter a node which + // is a global variable, we inline the const value of that global variable. + + let expr = inline_global_consts( + expr, + pathed_globals_with_values, + ¶m_names, + ) + .map_err(MonomorphizationErrorBundle::ResolverError)?; + match type_infer(&state, expr.clone()) { Ok(typed_expr) => { // Success, return immediately. @@ -594,3 +647,48 @@ fn collect_parameter_hir_types( struct_arguments } + +/// Updates the `globals_with_paths` map if the current function belongs to a new crate. +/// +/// The fully qualified paths of globals are relative to the crate being processed. +/// This function checks if the `current_func_crate_id` is different from the last one seen. +/// If it is, it re-calculates the paths for all globals and updates the map. +/// Otherwise, the existing map is considered valid and is not modified. +fn update_globals_if_needed( + last_crate_id: &mut Option, + globals_with_paths: &mut HashMap, + current_func_crate_id: CrateId, + monomorphizer: &Monomorphizer, + def_maps: &DefMaps, + crate_graph: &CrateGraph, +) { + if last_crate_id.map_or(true, |id| id != current_func_crate_id) { + *globals_with_paths = monomorphizer + .interner + .get_all_globals() + .iter() + .map(|global_info| { + let module_id = + ModuleId { krate: global_info.crate_id, local_id: global_info.local_id }; + + let mut module_path = fully_qualified_module_path( + def_maps, + crate_graph, + ¤t_func_crate_id, + module_id, + ); + // HACK: The function `fully_qualified_module_path` fails to consistently add `::` + // at the end of each path. Therefore we manually add double colons if they are missing. + if !module_path.ends_with("::") { + module_path.push_str("::"); + } + + let full_path = format!("{}{}", module_path, global_info.ident.as_str()); + + (full_path, global_info.value.clone()) + }) + .collect(); + + *last_crate_id = Some(current_func_crate_id); + } +} diff --git a/test_programs/formal_verify_failure/non_full_path_global_var/Nargo.toml b/test_programs/formal_verify_failure/non_full_path_global_var/Nargo.toml new file mode 100644 index 00000000000..b9e0dfb8f2f --- /dev/null +++ b/test_programs/formal_verify_failure/non_full_path_global_var/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "non_full_path_global_var" +type = "bin" +authors = [""] + +[dependencies] +fv_std = { path = "../../../fv_std" } diff --git a/test_programs/formal_verify_failure/non_full_path_global_var/src/main.nr b/test_programs/formal_verify_failure/non_full_path_global_var/src/main.nr new file mode 100644 index 00000000000..75fe4d578bc --- /dev/null +++ b/test_programs/formal_verify_failure/non_full_path_global_var/src/main.nr @@ -0,0 +1,8 @@ +use fv_std::U8_MIN; + +// This test will fail because we only support full paths for globals +// from foreign dependencies. +#['ensures(U8_MIN == result )] +fn main() -> pub u8 { + 0 +} diff --git a/test_programs/formal_verify_success/global_const_in_module/Nargo.toml b/test_programs/formal_verify_success/global_const_in_module/Nargo.toml new file mode 100644 index 00000000000..6a4192ee5b2 --- /dev/null +++ b/test_programs/formal_verify_success/global_const_in_module/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "global_const_in_module" +type = "bin" +authors = [""] + +[dependencies] +fv_std = { path = "../../../fv_std" } diff --git a/test_programs/formal_verify_success/global_const_in_module/src/main.nr b/test_programs/formal_verify_success/global_const_in_module/src/main.nr new file mode 100644 index 00000000000..4b39b4ae15e --- /dev/null +++ b/test_programs/formal_verify_success/global_const_in_module/src/main.nr @@ -0,0 +1,9 @@ +use fv_std; + +#['ensures(bar::x == fv_std::U8_MIN)] +fn main() { +} + +mod bar { + global x: u32 = 0; +} diff --git a/test_programs/formal_verify_success/global_const_in_nested_module/Nargo.toml b/test_programs/formal_verify_success/global_const_in_nested_module/Nargo.toml new file mode 100644 index 00000000000..e9ad7c0742b --- /dev/null +++ b/test_programs/formal_verify_success/global_const_in_nested_module/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "global_const_in_nested_module" +type = "bin" +authors = [""] + +[dependencies] +fv_std = { path = "../../../fv_std" } diff --git a/test_programs/formal_verify_success/global_const_in_nested_module/src/main.nr b/test_programs/formal_verify_success/global_const_in_nested_module/src/main.nr new file mode 100644 index 00000000000..11ca6ada33c --- /dev/null +++ b/test_programs/formal_verify_success/global_const_in_nested_module/src/main.nr @@ -0,0 +1,12 @@ +use fv_std::U8_MAX; + +#['ensures(bar::qux::x == result)] +fn main(y: u32) -> pub u32 { + (U8_MAX - 250) as u32 +} + +mod bar { + mod qux { + global x: u32 = 5; + } +} diff --git a/test_programs/formal_verify_success/shadow_global_const/Nargo.toml b/test_programs/formal_verify_success/shadow_global_const/Nargo.toml new file mode 100644 index 00000000000..d081e36529e --- /dev/null +++ b/test_programs/formal_verify_success/shadow_global_const/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "shadow_global_const" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/formal_verify_success/shadow_global_const/src/main.nr b/test_programs/formal_verify_success/shadow_global_const/src/main.nr new file mode 100644 index 00000000000..ad87985f671 --- /dev/null +++ b/test_programs/formal_verify_success/shadow_global_const/src/main.nr @@ -0,0 +1,8 @@ +global x: u32 = 4; + +// We are showing that 'x' here will be resolved as the parameter x of +// function `main` and not the global variable `x`. +#['ensures(result == x / 2)] +fn main(x: u32) -> pub u32 { + x / 2 +} From 6f977a99228b54ee60adec8f97d0fb5a3081f187 Mon Sep 17 00:00:00 2001 From: Aristotelis Papanis Date: Wed, 10 Sep 2025 16:44:18 +0300 Subject: [PATCH 86/86] fix(globals): Allow access to fully imported globals Now the user doesn't have to write the full paths for imported global variables if the given variable was already imported via `use`. A test which was previously failing is now fixed. --- compiler/fv_bridge/src/lib.rs | 33 ++++++++++++++----- .../non_full_path_global_var/src/main.nr | 8 ----- .../non_full_path_global_var/Nargo.toml | 0 .../non_full_path_global_var/src/main.nr | 6 ++++ 4 files changed, 31 insertions(+), 16 deletions(-) delete mode 100644 test_programs/formal_verify_failure/non_full_path_global_var/src/main.nr rename test_programs/{formal_verify_failure => formal_verify_success}/non_full_path_global_var/Nargo.toml (100%) create mode 100644 test_programs/formal_verify_success/non_full_path_global_var/src/main.nr diff --git a/compiler/fv_bridge/src/lib.rs b/compiler/fv_bridge/src/lib.rs index 36c99a995d0..398a191ca0d 100644 --- a/compiler/fv_bridge/src/lib.rs +++ b/compiler/fv_bridge/src/lib.rs @@ -17,7 +17,9 @@ use noirc_evaluator::{ }; use noirc_frontend::Kind; use noirc_frontend::graph::CrateGraph; -use noirc_frontend::hir::def_map::{DefMaps, ModuleId, fully_qualified_module_path}; +use noirc_frontend::hir::def_map::{ + DefMaps, LocalModuleId, ModuleDefId, ModuleId, fully_qualified_module_path, +}; use noirc_frontend::hir_def::expr::HirCallExpression; use noirc_frontend::hir_def::expr::{HirExpression, HirLiteral}; use noirc_frontend::hir_def::stmt::HirPattern; @@ -248,12 +250,14 @@ fn modified_monomorphize( // Determine the crate ID of the function currently being processed. let current_func_crate_id = monomorphizer.interner.function_meta(&old_id).source_crate; + let current_func_module_id = monomorphizer.interner.function_meta(&old_id).source_module; // Conditionally update the globals map based on the current crate context. update_globals_if_needed( &mut last_crate_id, &mut globals_with_paths, current_func_crate_id, + current_func_module_id, &monomorphizer, def_maps, crate_graph, @@ -437,12 +441,8 @@ fn type_infer_attribute_expr( // We visit the FV annotation AST and if we encounter a node which // is a global variable, we inline the const value of that global variable. - let expr = inline_global_consts( - expr, - pathed_globals_with_values, - ¶m_names, - ) - .map_err(MonomorphizationErrorBundle::ResolverError)?; + let expr = inline_global_consts(expr, pathed_globals_with_values, ¶m_names) + .map_err(MonomorphizationErrorBundle::ResolverError)?; match type_infer(&state, expr.clone()) { Ok(typed_expr) => { @@ -658,11 +658,23 @@ fn update_globals_if_needed( last_crate_id: &mut Option, globals_with_paths: &mut HashMap, current_func_crate_id: CrateId, + current_func_module_id: LocalModuleId, monomorphizer: &Monomorphizer, def_maps: &DefMaps, crate_graph: &CrateGraph, ) { if last_crate_id.map_or(true, |id| id != current_func_crate_id) { + let fully_imported_globals: HashMap<_, _> = def_maps[¤t_func_crate_id] + [current_func_module_id] + .scope() + .values() + .into_iter() + .filter_map(|(identifier, map)| match map.get(&None) { + Some((ModuleDefId::GlobalId(id), ..)) => Some((id, identifier)), + _ => None, + }) + .collect(); + *globals_with_paths = monomorphizer .interner .get_all_globals() @@ -683,7 +695,12 @@ fn update_globals_if_needed( module_path.push_str("::"); } - let full_path = format!("{}{}", module_path, global_info.ident.as_str()); + let full_path = + if let Some(identifier) = fully_imported_globals.get(&global_info.id) { + identifier.identifier().to_string() + } else { + format!("{}{}", module_path, global_info.ident.as_str()) + }; (full_path, global_info.value.clone()) }) diff --git a/test_programs/formal_verify_failure/non_full_path_global_var/src/main.nr b/test_programs/formal_verify_failure/non_full_path_global_var/src/main.nr deleted file mode 100644 index 75fe4d578bc..00000000000 --- a/test_programs/formal_verify_failure/non_full_path_global_var/src/main.nr +++ /dev/null @@ -1,8 +0,0 @@ -use fv_std::U8_MIN; - -// This test will fail because we only support full paths for globals -// from foreign dependencies. -#['ensures(U8_MIN == result )] -fn main() -> pub u8 { - 0 -} diff --git a/test_programs/formal_verify_failure/non_full_path_global_var/Nargo.toml b/test_programs/formal_verify_success/non_full_path_global_var/Nargo.toml similarity index 100% rename from test_programs/formal_verify_failure/non_full_path_global_var/Nargo.toml rename to test_programs/formal_verify_success/non_full_path_global_var/Nargo.toml diff --git a/test_programs/formal_verify_success/non_full_path_global_var/src/main.nr b/test_programs/formal_verify_success/non_full_path_global_var/src/main.nr new file mode 100644 index 00000000000..47b32b07f74 --- /dev/null +++ b/test_programs/formal_verify_success/non_full_path_global_var/src/main.nr @@ -0,0 +1,6 @@ +use fv_std::U8_MIN; + +#['ensures(U8_MIN == result)] +fn main() -> pub u8 { + 0 +}