From 391a6b7377a5253f2c47fa5ec949f255b284da00 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:08:01 +0000 Subject: [PATCH] chore: pull out sync changes (#10274) Please read [contributing guidelines](CONTRIBUTING.md) and remove this line. --- .../.github/workflows/test-js-packages.yml | 3 ++ .../compiler/noirc_errors/src/position.rs | 38 ++++++++++++- .../compiler/noirc_evaluator/src/acir/mod.rs | 52 ++---------------- .../src/brillig/brillig_gen.rs | 54 +++++++++++++++++-- .../src/ssa/ir/function_inserter.rs | 2 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 7 +-- .../src/ssa/ir/instruction/call.rs | 34 +++++++++++- .../noirc_evaluator/src/ssa/ir/printer.rs | 4 +- .../noirc_evaluator/src/ssa/opt/die.rs | 2 +- .../noirc_evaluator/src/ssa/parser/ast.rs | 7 +++ .../src/ssa/parser/into_ssa.rs | 41 +++++++++++--- .../noirc_evaluator/src/ssa/parser/lexer.rs | 46 +++++++++++++++- .../noirc_evaluator/src/ssa/parser/mod.rs | 50 +++++++++++++++-- .../noirc_evaluator/src/ssa/parser/tests.rs | 37 +++++++++++++ .../noirc_evaluator/src/ssa/parser/token.rs | 8 +++ .../noirc_frontend/src/parser/errors.rs | 7 +++ .../noirc_frontend/src/parser/parser.rs | 7 +++ .../noirc_frontend/src/parser/parser/types.rs | 5 +- .../requests/code_action/import_or_qualify.rs | 25 +++++++++ 19 files changed, 356 insertions(+), 73 deletions(-) diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index 152d8b1653e..4a5d0b8179b 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -478,6 +478,9 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1.2.0 + with: + version: nightly-8660e5b941fe7f4d67e246cfd3dafea330fb53b1 + - name: Install `bb` run: | diff --git a/noir/noir-repo/compiler/noirc_errors/src/position.rs b/noir/noir-repo/compiler/noirc_errors/src/position.rs index 8131db323b9..c7a64c4f422 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/position.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/position.rs @@ -94,8 +94,10 @@ impl Span { self.start() <= other.start() && self.end() >= other.end() } + /// Returns `true` if any point of `self` intersects a point of `other`. + /// Adjacent spans are considered to intersect (so, for example, `0..1` intersects `1..3`). pub fn intersects(&self, other: &Span) -> bool { - self.end() > other.start() && self.start() < other.end() + self.end() >= other.start() && self.start() <= other.end() } pub fn is_smaller(&self, other: &Span) -> bool { @@ -140,3 +142,37 @@ impl Location { self.file == other.file && self.span.contains(&other.span) } } + +#[cfg(test)] +mod tests { + use crate::Span; + + #[test] + fn test_intersects() { + assert!(Span::from(5..10).intersects(&Span::from(5..10))); + + assert!(Span::from(5..10).intersects(&Span::from(5..5))); + assert!(Span::from(5..5).intersects(&Span::from(5..10))); + + assert!(Span::from(10..10).intersects(&Span::from(5..10))); + assert!(Span::from(5..10).intersects(&Span::from(10..10))); + + assert!(Span::from(5..10).intersects(&Span::from(6..9))); + assert!(Span::from(6..9).intersects(&Span::from(5..10))); + + assert!(Span::from(5..10).intersects(&Span::from(4..11))); + assert!(Span::from(4..11).intersects(&Span::from(5..10))); + + assert!(Span::from(5..10).intersects(&Span::from(4..6))); + assert!(Span::from(4..6).intersects(&Span::from(5..10))); + + assert!(Span::from(5..10).intersects(&Span::from(9..11))); + assert!(Span::from(9..11).intersects(&Span::from(5..10))); + + assert!(!Span::from(5..10).intersects(&Span::from(3..4))); + assert!(!Span::from(3..4).intersects(&Span::from(5..10))); + + assert!(!Span::from(5..10).intersects(&Span::from(11..12))); + assert!(!Span::from(11..12).intersects(&Span::from(5..10))); + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs index 5c7899b5035..46d0924b322 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs @@ -24,12 +24,10 @@ mod big_int; mod brillig_directive; mod generated_acir; +use crate::brillig::brillig_gen::gen_brillig_for; use crate::brillig::{ brillig_gen::brillig_fn::FunctionContext as BrilligFunctionContext, - brillig_ir::{ - artifact::{BrilligParameter, GeneratedBrillig}, - BrilligContext, - }, + brillig_ir::artifact::{BrilligParameter, GeneratedBrillig}, Brillig, }; use crate::errors::{InternalError, InternalWarning, RuntimeError, SsaReport}; @@ -518,7 +516,7 @@ impl<'a> Context<'a> { let outputs: Vec = vecmap(main_func.returns(), |result_id| dfg.type_of_value(*result_id).into()); - let code = self.gen_brillig_for(main_func, arguments.clone(), brillig)?; + let code = gen_brillig_for(main_func, arguments.clone(), brillig)?; // We specifically do not attempt execution of the brillig code being generated as this can result in it being // replaced with constraints on witnesses to the program outputs. @@ -878,8 +876,7 @@ impl<'a> Context<'a> { None, )? } else { - let code = - self.gen_brillig_for(func, arguments.clone(), brillig)?; + let code = gen_brillig_for(func, arguments.clone(), brillig)?; let generated_pointer = self.shared_context.new_generated_pointer(); let output_values = self.acir_context.brillig_call( @@ -999,47 +996,6 @@ impl<'a> Context<'a> { .collect() } - fn gen_brillig_for( - &self, - func: &Function, - arguments: Vec, - brillig: &Brillig, - ) -> Result, InternalError> { - // Create the entry point artifact - let mut entry_point = BrilligContext::new_entry_point_artifact( - arguments, - BrilligFunctionContext::return_values(func), - func.id(), - ); - entry_point.name = func.name().to_string(); - - // Link the entry point with all dependencies - while let Some(unresolved_fn_label) = entry_point.first_unresolved_function_call() { - let artifact = &brillig.find_by_label(unresolved_fn_label.clone()); - let artifact = match artifact { - Some(artifact) => artifact, - None => { - return Err(InternalError::General { - message: format!("Cannot find linked fn {unresolved_fn_label}"), - call_stack: CallStack::new(), - }) - } - }; - entry_point.link_with(artifact); - // Insert the range of opcode locations occupied by a procedure - if let Some(procedure_id) = &artifact.procedure { - let num_opcodes = entry_point.byte_code.len(); - let previous_num_opcodes = entry_point.byte_code.len() - artifact.byte_code.len(); - // We subtract one as to keep the range inclusive on both ends - entry_point - .procedure_locations - .insert(procedure_id.clone(), (previous_num_opcodes, num_opcodes - 1)); - } - } - // Generate the final bytecode - Ok(entry_point.finish()) - } - /// Handles an ArrayGet or ArraySet instruction. /// To set an index of the array (and create a new array in doing so), pass Some(value) for /// store_value. To just retrieve an index of the array, pass None for store_value. diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs index 786a03031d6..ca4e783aa93 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen.rs @@ -9,11 +9,17 @@ mod variable_liveness; use acvm::FieldElement; use self::{brillig_block::BrilligBlock, brillig_fn::FunctionContext}; -use super::brillig_ir::{ - artifact::{BrilligArtifact, Label}, - BrilligContext, +use super::{ + brillig_ir::{ + artifact::{BrilligArtifact, BrilligParameter, GeneratedBrillig, Label}, + BrilligContext, + }, + Brillig, +}; +use crate::{ + errors::InternalError, + ssa::ir::{dfg::CallStack, function::Function}, }; -use crate::ssa::ir::function::Function; /// Converting an SSA function into Brillig bytecode. pub(crate) fn convert_ssa_function( @@ -36,3 +42,43 @@ pub(crate) fn convert_ssa_function( artifact.name = func.name().to_string(); artifact } + +pub(crate) fn gen_brillig_for( + func: &Function, + arguments: Vec, + brillig: &Brillig, +) -> Result, InternalError> { + // Create the entry point artifact + let mut entry_point = BrilligContext::new_entry_point_artifact( + arguments, + FunctionContext::return_values(func), + func.id(), + ); + entry_point.name = func.name().to_string(); + + // Link the entry point with all dependencies + while let Some(unresolved_fn_label) = entry_point.first_unresolved_function_call() { + let artifact = &brillig.find_by_label(unresolved_fn_label.clone()); + let artifact = match artifact { + Some(artifact) => artifact, + None => { + return Err(InternalError::General { + message: format!("Cannot find linked fn {unresolved_fn_label}"), + call_stack: CallStack::new(), + }) + } + }; + entry_point.link_with(artifact); + // Insert the range of opcode locations occupied by a procedure + if let Some(procedure_id) = &artifact.procedure { + let num_opcodes = entry_point.byte_code.len(); + let previous_num_opcodes = entry_point.byte_code.len() - artifact.byte_code.len(); + // We subtract one as to keep the range inclusive on both ends + entry_point + .procedure_locations + .insert(procedure_id.clone(), (previous_num_opcodes, num_opcodes - 1)); + } + } + // Generate the final bytecode + Ok(entry_point.finish()) +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs index 5e133072067..a0c23ad70aa 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs @@ -25,7 +25,7 @@ pub(crate) struct FunctionInserter<'f> { /// /// This is optional since caching arrays relies on the inserter inserting strictly /// in control-flow order. Otherwise, if arrays later in the program are cached first, - /// they may be refered to by instructions earlier in the program. + /// they may be referred to by instructions earlier in the program. array_cache: Option, /// If this pass is loop unrolling, store the block before the loop to optionally diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 3b3fd73a1fd..ba854d6a3c1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -16,6 +16,7 @@ use crate::ssa::opt::flatten_cfg::value_merger::ValueMerger; use super::{ basic_block::BasicBlockId, dfg::{CallStack, DataFlowGraph}, + function::Function, map::Id, types::{NumericType, Type}, value::{Value, ValueId}, @@ -366,12 +367,12 @@ impl Instruction { } } - pub(crate) fn can_eliminate_if_unused(&self, dfg: &DataFlowGraph) -> bool { + pub(crate) fn can_eliminate_if_unused(&self, function: &Function) -> bool { use Instruction::*; match self { Binary(binary) => { if matches!(binary.operator, BinaryOp::Div | BinaryOp::Mod) { - if let Some(rhs) = dfg.get_numeric_constant(binary.rhs) { + if let Some(rhs) = function.dfg.get_numeric_constant(binary.rhs) { rhs != FieldElement::zero() } else { false @@ -398,7 +399,7 @@ impl Instruction { | RangeCheck { .. } => false, // Some `Intrinsic`s have side effects so we must check what kind of `Call` this is. - Call { func, .. } => match dfg[*func] { + Call { func, .. } => match function.dfg[*func] { // Explicitly allows removal of unused ec operations, even if they can fail Value::Intrinsic(Intrinsic::BlackBox(BlackBoxFunc::MultiScalarMul)) | Value::Intrinsic(Intrinsic::BlackBox(BlackBoxFunc::EmbeddedCurveAdd)) => true, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 95447f941b0..7e41512fd8f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -806,7 +806,8 @@ fn simplify_derive_generators( results.push(is_infinite); } let len = results.len(); - let typ = Type::Array(vec![Type::field()].into(), len); + let typ = + Type::Array(vec![Type::field(), Type::field(), Type::unsigned(1)].into(), len / 3); let result = make_array(dfg, results.into(), typ, block, call_stack); SimplifyResult::SimplifiedTo(result) } else { @@ -816,3 +817,34 @@ fn simplify_derive_generators( unreachable!("Unexpected number of arguments to derive_generators"); } } + +#[cfg(test)] +mod tests { + use crate::ssa::{opt::assert_normalized_ssa_equals, Ssa}; + + #[test] + fn simplify_derive_generators_has_correct_type() { + let src = " + brillig(inline) fn main f0 { + b0(): + v0 = make_array [u8 68, u8 69, u8 70, u8 65, u8 85, u8 76, u8 84, u8 95, u8 68, u8 79, u8 77, u8 65, u8 73, u8 78, u8 95, u8 83, u8 69, u8 80, u8 65, u8 82, u8 65, u8 84, u8 79, u8 82] : [u8; 24] + + // This call was previously incorrectly simplified to something that returned `[Field; 3]` + v2 = call derive_pedersen_generators(v0, u32 0) -> [(Field, Field, u1); 1] + + return v2 + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + brillig(inline) fn main f0 { + b0(): + v15 = make_array [u8 68, u8 69, u8 70, u8 65, u8 85, u8 76, u8 84, u8 95, u8 68, u8 79, u8 77, u8 65, u8 73, u8 78, u8 95, u8 83, u8 69, u8 80, u8 65, u8 82, u8 65, u8 84, u8 79, u8 82] : [u8; 24] + v19 = make_array [Field 3728882899078719075161482178784387565366481897740339799480980287259621149274, Field -9903063709032878667290627648209915537972247634463802596148419711785767431332, u1 0] : [(Field, Field, u1); 1] + return v19 + } + "; + assert_normalized_ssa_equals(ssa, expected); + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index b981b81f365..6bebd21fe61 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -272,13 +272,13 @@ fn display_constrain_error( ) -> Result { match error { ConstrainError::StaticString(assert_message_string) => { - writeln!(f, " '{assert_message_string:?}'") + writeln!(f, ", {assert_message_string:?}") } ConstrainError::Dynamic(_, is_string, values) => { if let Some(constant_string) = try_to_extract_string_from_error_payload(*is_string, values, &function.dfg) { - writeln!(f, " '{}'", constant_string) + writeln!(f, ", {constant_string:?}") } else { writeln!(f, ", data {}", value_list(function, values)) } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs index 666a8e32246..8d3fa9cc615 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -172,7 +172,7 @@ impl Context { fn is_unused(&self, instruction_id: InstructionId, function: &Function) -> bool { let instruction = &function.dfg[instruction_id]; - if instruction.can_eliminate_if_unused(&function.dfg) { + if instruction.can_eliminate_if_unused(function) { let results = function.dfg.instruction_results(instruction_id); results.iter().all(|result| !self.used_values.contains(result)) } else if let Instruction::Call { func, arguments } = instruction { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/ast.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/ast.rs index a34b7fd70d3..6c7608a2f16 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/ast.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/ast.rs @@ -89,6 +89,7 @@ pub(crate) enum ParsedInstruction { Constrain { lhs: ParsedValue, rhs: ParsedValue, + assert_message: Option, }, DecrementRc { value: ParsedValue, @@ -129,6 +130,12 @@ pub(crate) enum ParsedInstruction { }, } +#[derive(Debug)] +pub(crate) enum AssertMessage { + Static(String), + Dynamic(Vec), +} + #[derive(Debug)] pub(crate) enum ParsedTerminator { Jmp { destination: Identifier, arguments: Vec }, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs index 552ac0781c7..e78cbbd75a1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs @@ -1,13 +1,18 @@ use std::collections::HashMap; +use acvm::acir::circuit::ErrorSelector; + use crate::ssa::{ function_builder::FunctionBuilder, - ir::{basic_block::BasicBlockId, function::FunctionId, value::ValueId}, + ir::{ + basic_block::BasicBlockId, function::FunctionId, instruction::ConstrainError, + value::ValueId, + }, }; use super::{ - Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedSsa, ParsedTerminator, - ParsedValue, RuntimeType, Ssa, SsaError, + ast::AssertMessage, Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedSsa, + ParsedTerminator, ParsedValue, RuntimeType, Ssa, SsaError, }; impl ParsedSsa { @@ -31,6 +36,8 @@ struct Translator { /// passes already which replaced some of the original IDs. The translator /// will recreate the SSA step by step, which can result in a new ID layout. variables: HashMap>, + + error_selector_counter: u64, } impl Translator { @@ -64,8 +71,13 @@ impl Translator { functions.insert(function.internal_name.clone(), function_id); } - let mut translator = - Self { builder, functions, variables: HashMap::new(), blocks: HashMap::new() }; + let mut translator = Self { + builder, + functions, + variables: HashMap::new(), + blocks: HashMap::new(), + error_selector_counter: 0, + }; translator.translate_function_body(main_function)?; Ok(translator) @@ -198,10 +210,25 @@ impl Translator { let value_id = self.builder.insert_cast(lhs, typ); self.define_variable(target, value_id)?; } - ParsedInstruction::Constrain { lhs, rhs } => { + ParsedInstruction::Constrain { lhs, rhs, assert_message } => { let lhs = self.translate_value(lhs)?; let rhs = self.translate_value(rhs)?; - self.builder.insert_constrain(lhs, rhs, None); + let assert_message = match assert_message { + Some(AssertMessage::Static(string)) => { + Some(ConstrainError::StaticString(string)) + } + Some(AssertMessage::Dynamic(values)) => { + let error_selector = ErrorSelector::new(self.error_selector_counter); + self.error_selector_counter += 1; + + let is_string_type = false; + let values = self.translate_values(values)?; + + Some(ConstrainError::Dynamic(error_selector, is_string_type, values)) + } + None => None, + }; + self.builder.insert_constrain(lhs, rhs, assert_message); } ParsedInstruction::DecrementRc { value } => { let value = self.translate_value(value)?; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/lexer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/lexer.rs index 4c90475be74..d89bc1e9e28 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/lexer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/lexer.rs @@ -61,6 +61,7 @@ impl<'a> Lexer<'a> { Some('&') => self.single_char_token(Token::Ampersand), Some('-') if self.peek_char() == Some('>') => self.double_char_token(Token::Arrow), Some('-') => self.single_char_token(Token::Dash), + Some('"') => self.eat_string_literal(), Some(ch) if ch.is_ascii_alphanumeric() || ch == '_' => self.eat_alpha_numeric(ch), Some(char) => Err(LexerError::UnexpectedCharacter { char, @@ -177,6 +178,41 @@ impl<'a> Lexer<'a> { Ok(integer_token.into_span(start, end)) } + fn eat_string_literal(&mut self) -> SpannedTokenResult { + let start = self.position; + let mut string = String::new(); + + while let Some(next) = self.next_char() { + let char = match next { + '"' => break, + '\\' => match self.next_char() { + Some('r') => '\r', + Some('n') => '\n', + Some('t') => '\t', + Some('0') => '\0', + Some('"') => '"', + Some('\\') => '\\', + Some(escaped) => { + let span = Span::inclusive(start, self.position); + return Err(LexerError::InvalidEscape { escaped, span }); + } + None => { + let span = Span::inclusive(start, self.position); + return Err(LexerError::UnterminatedStringLiteral { span }); + } + }, + other => other, + }; + + string.push(char); + } + + let str_literal_token = Token::Str(string); + + let end = self.position; + Ok(str_literal_token.into_span(start, end)) + } + fn eat_while bool>( &mut self, initial_char: Option, @@ -247,6 +283,12 @@ pub(crate) enum LexerError { InvalidIntegerLiteral { span: Span, found: String }, #[error("Integer literal too large")] IntegerLiteralTooLarge { span: Span, limit: String }, + #[error("Unterminated string literal")] + UnterminatedStringLiteral { span: Span }, + #[error( + "'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character." + )] + InvalidEscape { escaped: char, span: Span }, } impl LexerError { @@ -254,7 +296,9 @@ impl LexerError { match self { LexerError::UnexpectedCharacter { span, .. } | LexerError::InvalidIntegerLiteral { span, .. } - | LexerError::IntegerLiteralTooLarge { span, .. } => *span, + | LexerError::IntegerLiteralTooLarge { span, .. } + | LexerError::UnterminatedStringLiteral { span } + | LexerError::InvalidEscape { span, .. } => *span, } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/mod.rs index 2db2c636a8f..3d8bd37dead 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/mod.rs @@ -10,8 +10,8 @@ use super::{ use acvm::{AcirField, FieldElement}; use ast::{ - Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedParameter, ParsedSsa, - ParsedValue, + AssertMessage, Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedParameter, + ParsedSsa, ParsedValue, }; use lexer::{Lexer, LexerError}; use noirc_errors::Span; @@ -28,6 +28,11 @@ mod tests; mod token; impl Ssa { + /// Creates an Ssa object from the given string. + /// + /// Note that the resulting Ssa might not be exactly the same as the given string. + /// This is because, internally, the Ssa is built using a `FunctionBuilder`, so + /// some instructions might be simplified while they are inserted. pub(crate) fn from_str(src: &str) -> Result { let mut parser = Parser::new(src).map_err(|err| SsaErrorWithSource::parse_error(err, src))?; @@ -308,7 +313,20 @@ impl<'a> Parser<'a> { let lhs = self.parse_value_or_error()?; self.eat_or_error(Token::Equal)?; let rhs = self.parse_value_or_error()?; - Ok(Some(ParsedInstruction::Constrain { lhs, rhs })) + + let assert_message = if self.eat(Token::Comma)? { + if let Some(str) = self.eat_str()? { + Some(AssertMessage::Static(str)) + } else if self.eat_keyword(Keyword::Data)? { + Some(AssertMessage::Dynamic(self.parse_comma_separated_values()?)) + } else { + return self.expected_string_or_data(); + } + } else { + None + }; + + Ok(Some(ParsedInstruction::Constrain { lhs, rhs, assert_message })) } fn parse_decrement_rc(&mut self) -> ParseResult> { @@ -649,6 +667,10 @@ impl<'a> Parser<'a> { return Ok(Type::Reference(Arc::new(typ))); } + if self.eat_keyword(Keyword::Function)? { + return Ok(Type::Function); + } + self.expected_type() } @@ -762,6 +784,18 @@ impl<'a> Parser<'a> { } } + fn eat_str(&mut self) -> ParseResult> { + if matches!(self.token.token(), Token::Str(..)) { + let token = self.bump()?; + match token.into_token() { + Token::Str(string) => Ok(Some(string)), + _ => unreachable!(), + } + } else { + Ok(None) + } + } + fn eat(&mut self, token: Token) -> ParseResult { if self.token.token() == &token { self.bump()?; @@ -807,6 +841,13 @@ impl<'a> Parser<'a> { }) } + fn expected_string_or_data(&mut self) -> ParseResult { + Err(ParserError::ExpectedStringOrData { + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + fn expected_identifier(&mut self) -> ParseResult { Err(ParserError::ExpectedIdentifier { found: self.token.token().clone(), @@ -868,6 +909,8 @@ pub(crate) enum ParserError { ExpectedType { found: Token, span: Span }, #[error("Expected an instruction or terminator, found '{found}'")] ExpectedInstructionOrTerminator { found: Token, span: Span }, + #[error("Expected a string literal or 'data', found '{found}'")] + ExpectedStringOrData { found: Token, span: Span }, #[error("Expected a value, found '{found}'")] ExpectedValue { found: Token, span: Span }, #[error("Multiple return values only allowed for call")] @@ -884,6 +927,7 @@ impl ParserError { | ParserError::ExpectedInt { span, .. } | ParserError::ExpectedType { span, .. } | ParserError::ExpectedInstructionOrTerminator { span, .. } + | ParserError::ExpectedStringOrData { span, .. } | ParserError::ExpectedValue { span, .. } => *span, ParserError::MultipleReturnValuesOnlyAllowedForCall { second_target, .. } => { second_target.span diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/tests.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/tests.rs index 60d398bf9d5..593b66d0c98 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/tests.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/tests.rs @@ -214,6 +214,31 @@ fn test_constrain() { assert_ssa_roundtrip(src); } +#[test] +fn test_constrain_with_static_message() { + let src = r#" + acir(inline) fn main f0 { + b0(v0: Field): + constrain v0 == Field 1, "Oh no!" + return + } + "#; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_constrain_with_dynamic_message() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field, v1: Field): + v7 = make_array [u8 123, u8 120, u8 125, u8 32, u8 123, u8 121, u8 125] : [u8; 7] + constrain v0 == Field 1, data v7, u32 2, v0, v1 + return + } + "; + assert_ssa_roundtrip(src); +} + #[test] fn test_enable_side_effects() { let src = " @@ -441,3 +466,15 @@ fn test_negative() { "; assert_ssa_roundtrip(src); } + +#[test] +fn test_function_type() { + let src = " + acir(inline) fn main f0 { + b0(): + v0 = allocate -> &mut function + return + } + "; + assert_ssa_roundtrip(src); +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/token.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/token.rs index f663879e899..d8dd4ec011e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/token.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/token.rs @@ -29,6 +29,7 @@ impl SpannedToken { pub(crate) enum Token { Ident(String), Int(FieldElement), + Str(String), Keyword(Keyword), IntType(IntType), /// = @@ -77,6 +78,7 @@ impl Display for Token { match self { Token::Ident(ident) => write!(f, "{}", ident), Token::Int(int) => write!(f, "{}", int), + Token::Str(string) => write!(f, "{string:?}"), Token::Keyword(keyword) => write!(f, "{}", keyword), Token::IntType(int_type) => write!(f, "{}", int_type), Token::Assign => write!(f, "="), @@ -120,6 +122,7 @@ pub(crate) enum Keyword { Call, Cast, Constrain, + Data, DecRc, Div, Inline, @@ -130,6 +133,7 @@ pub(crate) enum Keyword { Field, Fold, Fn, + Function, IncRc, Index, Jmp, @@ -175,6 +179,7 @@ impl Keyword { "call" => Keyword::Call, "cast" => Keyword::Cast, "constrain" => Keyword::Constrain, + "data" => Keyword::Data, "dec_rc" => Keyword::DecRc, "div" => Keyword::Div, "else" => Keyword::Else, @@ -185,6 +190,7 @@ impl Keyword { "Field" => Keyword::Field, "fold" => Keyword::Fold, "fn" => Keyword::Fn, + "function" => Keyword::Function, "inc_rc" => Keyword::IncRc, "index" => Keyword::Index, "jmp" => Keyword::Jmp, @@ -234,6 +240,7 @@ impl Display for Keyword { Keyword::Call => write!(f, "call"), Keyword::Cast => write!(f, "cast"), Keyword::Constrain => write!(f, "constrain"), + Keyword::Data => write!(f, "data"), Keyword::DecRc => write!(f, "dec_rc"), Keyword::Div => write!(f, "div"), Keyword::Else => write!(f, "else"), @@ -242,6 +249,7 @@ impl Display for Keyword { Keyword::Field => write!(f, "Field"), Keyword::Fold => write!(f, "fold"), Keyword::Fn => write!(f, "fn"), + Keyword::Function => write!(f, "function"), Keyword::IncRc => write!(f, "inc_rc"), Keyword::Index => write!(f, "index"), Keyword::Inline => write!(f, "inline"), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs index bcb4ce1c616..899928528e6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -19,6 +19,8 @@ pub enum ParserErrorReason { UnexpectedComma, #[error("Expected a `{token}` separating these two {items}")] ExpectedTokenSeparatingTwoItems { token: Token, items: &'static str }, + #[error("Expected `mut` after `&`, found `{found}`")] + ExpectedMutAfterAmpersand { found: Token }, #[error("Invalid left-hand side of assignment")] InvalidLeftHandSideOfAssignment, #[error("Expected trait, found {found}")] @@ -265,6 +267,11 @@ impl<'a> From<&'a ParserError> for Diagnostic { error.span, ), ParserErrorReason::Lexer(error) => error.into(), + ParserErrorReason::ExpectedMutAfterAmpersand { found } => Diagnostic::simple_error( + format!("Expected `mut` after `&`, found `{found}`"), + "Noir doesn't have immutable references, only mutable references".to_string(), + error.span, + ), other => Diagnostic::simple_error(format!("{other}"), String::new(), error.span), }, None => { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index f369839ddd4..c2f7b781873 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -498,6 +498,13 @@ impl<'a> Parser<'a> { self.push_error(ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items }, span); } + fn expected_mut_after_ampersand(&mut self) { + self.push_error( + ParserErrorReason::ExpectedMutAfterAmpersand { found: self.token.token().clone() }, + self.current_token_span, + ); + } + fn modifiers_not_followed_by_an_item(&mut self, modifiers: Modifiers) { self.visibility_not_followed_by_an_item(modifiers); self.unconstrained_not_followed_by_an_item(modifiers); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs index be3d5287cab..0de94a89be5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs @@ -341,7 +341,10 @@ impl<'a> Parser<'a> { fn parses_mutable_reference_type(&mut self) -> Option { if self.eat(Token::Ampersand) { - self.eat_keyword_or_error(Keyword::Mut); + if !self.eat_keyword(Keyword::Mut) { + self.expected_mut_after_ampersand(); + } + return Some(UnresolvedTypeData::MutableReference(Box::new( self.parse_type_or_error(), ))); diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs b/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs index ffc83b05a5b..609a81bdfe7 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_action/import_or_qualify.rs @@ -183,6 +183,31 @@ mod foo { } } +fn foo(x: SomeTypeInBar) {}"#; + + assert_code_action(title, src, expected).await; + } + + #[test] + async fn test_import_code_action_for_struct_at_beginning_of_name() { + let title = "Import foo::bar::SomeTypeInBar"; + + let src = r#"mod foo { + pub mod bar { + pub struct SomeTypeInBar {} + } +} + +fn foo(x: >|