diff --git a/Cargo.lock b/Cargo.lock index 766802845e8..d2a59fcd377 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ dependencies = [ "flate2", "insta", "noir_protobuf", - "noirc_errors", + "noirc_span", "num-bigint", "num-traits", "num_enum", @@ -72,6 +72,7 @@ dependencies = [ "brillig_vm", "criterion", "indexmap 2.11.0", + "insta", "num-bigint", "pprof", "proptest", @@ -3715,11 +3716,11 @@ version = "1.0.0-beta.12" dependencies = [ "acvm", "base64", - "codespan", "codespan-reporting", "flate2", "fm", "noirc_printable_type", + "noirc_span", "rustc-hash", "serde", "serde_json", @@ -3805,6 +3806,14 @@ dependencies = [ "serde_json", ] +[[package]] +name = "noirc_span" +version = "1.0.0-beta.12" +dependencies = [ + "codespan", + "serde", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 75fdd1697fd..bbfb3cc85e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "compiler/noirc_errors", "compiler/noirc_driver", "compiler/noirc_printable_type", + "compiler/noirc_span", "compiler/fm", "compiler/wasm", # Crates related to tooling built on top of the Noir compiler @@ -101,6 +102,7 @@ noirc_errors = { path = "compiler/noirc_errors" } noirc_evaluator = { path = "compiler/noirc_evaluator" } noirc_frontend = { path = "compiler/noirc_frontend" } noirc_printable_type = { path = "compiler/noirc_printable_type" } +noirc_span = { path = "compiler/noirc_span" } # Noir tooling workspace dependencies noir_greybox_fuzzer = { path = "tooling/greybox_fuzzer" } diff --git a/acvm-repo/acir/Cargo.toml b/acvm-repo/acir/Cargo.toml index 59b72e4dc8c..a814165ec6f 100644 --- a/acvm-repo/acir/Cargo.toml +++ b/acvm-repo/acir/Cargo.toml @@ -19,6 +19,7 @@ workspace = true acir_field.workspace = true brillig.workspace = true noir_protobuf.workspace = true +noirc_span.workspace = true color-eyre.workspace = true serde.workspace = true @@ -26,7 +27,9 @@ thiserror.workspace = true flate2.workspace = true bincode.workspace = true base64.workspace = true +num-bigint.workspace = true num_enum.workspace = true +num-traits.workspace = true prost.workspace = true rmp-serde.workspace = true serde-big-array = "0.5.1" @@ -47,12 +50,9 @@ serde-generate = "0.25.1" rustc-hash.workspace = true criterion.workspace = true pprof.workspace = true -num-bigint.workspace = true regex.workspace = true rmpv.workspace = true insta.workspace = true -num-traits.workspace = true -noirc_errors.workspace = true similar-asserts.workspace = true acir = { path = ".", features = ["arb"] } # Self to turn on `arb`. diff --git a/acvm-repo/acir/src/lib.rs b/acvm-repo/acir/src/lib.rs index 22a4dd60785..b13b3de119a 100644 --- a/acvm-repo/acir/src/lib.rs +++ b/acvm-repo/acir/src/lib.rs @@ -10,7 +10,6 @@ #[doc = include_str!("../README.md")] pub mod circuit; pub mod native_types; -#[cfg(test)] mod parser; mod proto; mod serialization; diff --git a/acvm-repo/acir/src/parser/lexer.rs b/acvm-repo/acir/src/parser/lexer.rs index 5751ed0c25e..7dedfc2f553 100644 --- a/acvm-repo/acir/src/parser/lexer.rs +++ b/acvm-repo/acir/src/parser/lexer.rs @@ -2,8 +2,7 @@ use std::str::{CharIndices, FromStr}; use acir_field::{AcirField, FieldElement}; -use noirc_errors::{Position, Span}; - +use noirc_span::{Position, Span}; use num_bigint::BigInt; use num_traits::One; use thiserror::Error; @@ -50,6 +49,14 @@ impl<'a> Lexer<'a> { } self.next_token() } + '/' if self.peek_char() == Some('/') => { + while let Some(char) = self.next_char() { + if char == '\n' { + break; + } + } + self.next_token() + } '(' => self.single_char_token(Token::LeftParen), ')' => self.single_char_token(Token::RightParen), '[' => self.single_char_token(Token::LeftBracket), diff --git a/acvm-repo/acir/src/parser/mod.rs b/acvm-repo/acir/src/parser/mod.rs index 897fc879750..7db38a6b8a6 100644 --- a/acvm-repo/acir/src/parser/mod.rs +++ b/acvm-repo/acir/src/parser/mod.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeSet, str::FromStr}; use acir_field::{AcirField, FieldElement}; use lexer::{Lexer, LexerError}; -use noirc_errors::Span; +use noirc_span::Span; use thiserror::Error; use token::{Keyword, SpannedToken, Token}; @@ -21,6 +21,7 @@ use crate::{ }; mod lexer; +#[cfg(test)] mod tests; mod token; @@ -153,14 +154,14 @@ impl<'a> Parser<'a> { self.parse_witness_ordered_set() } - fn parse_witness_ordered_set(&mut self) -> ParseResult> { + fn parse_witness_vector(&mut self) -> ParseResult> { self.eat_or_error(Token::LeftBracket)?; - let mut witnesses = BTreeSet::new(); + let mut witnesses = Vec::new(); while !self.eat(Token::RightBracket)? { let witness = self.eat_witness_or_error()?; - witnesses.insert(witness); + witnesses.push(witness); // Eat optional comma if self.eat(Token::Comma)? { @@ -176,8 +177,8 @@ impl<'a> Parser<'a> { Ok(witnesses) } - fn parse_witness_vector(&mut self) -> ParseResult> { - self.parse_witness_ordered_set().map(|set| set.into_iter().collect::>()) + fn parse_witness_ordered_set(&mut self) -> ParseResult> { + self.parse_witness_vector().map(|vec| vec.into_iter().collect::>()) } fn parse_opcodes(&mut self) -> ParseResult>> { diff --git a/acvm-repo/acir/src/parser/tests.rs b/acvm-repo/acir/src/parser/tests.rs index 58574bae35d..d43357633ad 100644 --- a/acvm-repo/acir/src/parser/tests.rs +++ b/acvm-repo/acir/src/parser/tests.rs @@ -479,6 +479,18 @@ fn memory_init() { assert_circuit_roundtrip(src); } +#[test] +fn memory_init_duplicate_witness() { + let src = " + current witness index : _4 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + INIT (id: 4, len: 2, witnesses: [_0, _0]) + "; + assert_circuit_roundtrip(src); +} + #[test] fn memory_databus() { let src = " diff --git a/acvm-repo/acir/src/parser/token.rs b/acvm-repo/acir/src/parser/token.rs index babee65fe9b..7172dec9d36 100644 --- a/acvm-repo/acir/src/parser/token.rs +++ b/acvm-repo/acir/src/parser/token.rs @@ -1,5 +1,5 @@ use acir_field::FieldElement; -use noirc_errors::{Position, Span, Spanned}; +use noirc_span::{Position, Span, Spanned}; #[derive(Debug)] pub(crate) struct SpannedToken(Spanned); diff --git a/acvm-repo/acvm/Cargo.toml b/acvm-repo/acvm/Cargo.toml index e45f8ed9bb3..34988bd2bd1 100644 --- a/acvm-repo/acvm/Cargo.toml +++ b/acvm-repo/acvm/Cargo.toml @@ -38,6 +38,7 @@ ark-bls12-381 = { version = "^0.5.0", default-features = false, features = [ ark-ff.workspace = true ark-bn254.workspace = true bn254_blackbox_solver.workspace = true +insta.workspace = true proptest.workspace = true num-bigint.workspace = true zkhash = { version = "^0.2.0", default-features = false } diff --git a/acvm-repo/acvm/src/compiler/mod.rs b/acvm-repo/acvm/src/compiler/mod.rs index 0e0963645aa..a51ae38c950 100644 --- a/acvm-repo/acvm/src/compiler/mod.rs +++ b/acvm-repo/acvm/src/compiler/mod.rs @@ -110,3 +110,12 @@ pub fn compile( (acir, transformation_map) } + +#[macro_export] +macro_rules! assert_circuit_snapshot { + ($acir:expr, $($arg:tt)*) => { + #[allow(unused_mut)] + let acir_string = $acir.to_string(); + insta::assert_snapshot!(acir_string, $($arg)*) + }; +} diff --git a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs index e256a03d478..eb1ff5f58ea 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs @@ -252,19 +252,15 @@ fn memory_block_implied_max_bits(init: &[Witness]) -> u32 { #[cfg(test)] mod tests { - use std::collections::{BTreeMap, BTreeSet}; + use std::collections::BTreeMap; - use crate::compiler::optimizers::redundant_range::{ - RangeOptimizer, memory_block_implied_max_bits, + use crate::{ + assert_circuit_snapshot, + compiler::optimizers::redundant_range::{RangeOptimizer, memory_block_implied_max_bits}, }; use acir::{ - FieldElement, - circuit::{ - Circuit, ExpressionWidth, Opcode, PublicInputs, - brillig::{BrilligFunctionId, BrilligInputs}, - opcodes::{BlackBoxFuncCall, BlockId, BlockType, FunctionInput, MemOp}, - }, - native_types::{Expression, Witness}, + circuit::{Circuit, brillig::BrilligFunctionId}, + native_types::Witness, }; #[test] @@ -279,33 +275,19 @@ mod tests { assert_eq!(memory_block_implied_max_bits(&[Witness(0); u16::MAX as usize]), 16); } - fn test_range_constraint(witness: Witness, num_bits: u32) -> Opcode { - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput::witness(witness, num_bits), - }) - } - - fn test_circuit(ranges: Vec<(Witness, u32)>) -> Circuit { - let opcodes: Vec<_> = ranges - .into_iter() - .map(|(witness, num_bits)| test_range_constraint(witness, num_bits)) - .collect(); - - Circuit { - current_witness_index: 1, - expression_width: ExpressionWidth::Bounded { width: 4 }, - opcodes, - private_parameters: BTreeSet::new(), - public_parameters: PublicInputs::default(), - return_values: PublicInputs::default(), - assert_messages: Default::default(), - } - } - #[test] fn retain_lowest_range_size() { // The optimizer should keep the lowest bit size range constraint - let circuit = test_circuit(vec![(Witness(1), 32), (Witness(1), 16)]); + let src = " + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 32)] [] + BLACKBOX::RANGE [(_1, 16)] [] + "; + let circuit = Circuit::from_str(src).unwrap(); + let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); let brillig_side_effects = BTreeMap::new(); let optimizer = RangeOptimizer::new(circuit, &brillig_side_effects); @@ -320,111 +302,133 @@ mod tests { ); let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); - assert_eq!(optimized_circuit.opcodes.len(), 1); - - assert_eq!( - optimized_circuit.opcodes[0], - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput::witness(Witness(1), 16) - }) - ); + assert_circuit_snapshot!(optimized_circuit, @r" + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 16)] [] + "); } #[test] fn remove_duplicates() { // The optimizer should remove all duplicate range opcodes. + let src = " + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 16)] [] + BLACKBOX::RANGE [(_1, 16)] [] + BLACKBOX::RANGE [(_2, 23)] [] + BLACKBOX::RANGE [(_2, 23)] [] + "; + let circuit = Circuit::from_str(src).unwrap(); - let circuit = test_circuit(vec![ - (Witness(1), 16), - (Witness(1), 16), - (Witness(2), 23), - (Witness(2), 23), - ]); let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); let brillig_side_effects = BTreeMap::new(); let optimizer = RangeOptimizer::new(circuit, &brillig_side_effects); let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); - assert_eq!(optimized_circuit.opcodes.len(), 2); - - assert_eq!( - optimized_circuit.opcodes[0], - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput::witness(Witness(1), 16) - }) - ); - assert_eq!( - optimized_circuit.opcodes[1], - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput::witness(Witness(2), 23) - }) - ); + assert_circuit_snapshot!(optimized_circuit, @r" + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 16)] [] + BLACKBOX::RANGE [(_2, 23)] [] + "); } #[test] fn non_range_opcodes() { // The optimizer should not remove or change non-range opcodes // The four AssertZero opcodes should remain unchanged. - let mut circuit = test_circuit(vec![(Witness(1), 16), (Witness(1), 16)]); + let src = " + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 16)] [] + BLACKBOX::RANGE [(_1, 16)] [] + EXPR [ 0 ] + EXPR [ 0 ] + EXPR [ 0 ] + EXPR [ 0 ] + "; + let circuit = Circuit::from_str(src).unwrap(); - circuit.opcodes.push(Opcode::AssertZero(Expression::default())); - circuit.opcodes.push(Opcode::AssertZero(Expression::default())); - circuit.opcodes.push(Opcode::AssertZero(Expression::default())); - circuit.opcodes.push(Opcode::AssertZero(Expression::default())); let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); let brillig_side_effects = BTreeMap::new(); let optimizer = RangeOptimizer::new(circuit, &brillig_side_effects); let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); - assert_eq!(optimized_circuit.opcodes.len(), 5); + assert_circuit_snapshot!(optimized_circuit, @r" + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 16)] [] + EXPR [ 0 ] + EXPR [ 0 ] + EXPR [ 0 ] + EXPR [ 0 ] + "); } #[test] fn constant_implied_ranges() { // The optimizer should use knowledge about constant witness assignments to remove range opcodes. - let mut circuit = test_circuit(vec![(Witness(1), 16)]); + let src = " + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 16)] [] + EXPR [ (1, _1) 0 ] + "; + let circuit = Circuit::from_str(src).unwrap(); - circuit.opcodes.push(Opcode::AssertZero(Witness(1).into())); let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); let brillig_side_effects = BTreeMap::new(); let optimizer = RangeOptimizer::new(circuit, &brillig_side_effects); let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); - assert_eq!(optimized_circuit.opcodes.len(), 1); - assert_eq!(optimized_circuit.opcodes[0], Opcode::AssertZero(Witness(1).into())); + assert_circuit_snapshot!(optimized_circuit, @r" + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + EXPR [ (1, _1) 0 ] + "); } #[test] fn potential_side_effects() { // The optimizer should not remove range constraints if doing so might allow invalid side effects to go through. - let mut circuit = test_circuit(vec![(Witness(1), 32)]); + let src = " + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 32)] [] // Call brillig with w2 - circuit.opcodes.push(Opcode::BrilligCall { - id: BrilligFunctionId(0), - inputs: vec![BrilligInputs::Single(Witness(2).into())], - outputs: vec![], - predicate: None, - }); - - circuit.opcodes.push(test_range_constraint(Witness(1), 16)); - - // Anther call - circuit.opcodes.push(Opcode::BrilligCall { - id: BrilligFunctionId(0), - inputs: vec![BrilligInputs::Single(Witness(2).into())], - outputs: vec![], - predicate: None, - }); + BRILLIG CALL func 0: inputs: [EXPR [ (1, _2) 0 ]], outputs: [] + BLACKBOX::RANGE [(_1, 16)] [] + + // Another call + BRILLIG CALL func 0: inputs: [EXPR [ (1, _2) 0 ]], outputs: [] // One more constraint, but this is redundant. - circuit.opcodes.push(test_range_constraint(Witness(1), 64)); + BLACKBOX::RANGE [(_1, 64)] [] + // assert w1 == 0 - circuit.opcodes.push(Opcode::AssertZero(Witness(1).into())); + EXPR [ (1, _1) 0 ] + "; + let circuit = Circuit::from_str(src).unwrap(); let acir_opcode_positions: Vec = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); - // The last constraint is expected to be removed. - let expected_length = circuit.opcodes.len() - 1; - // Consider the Brillig function to have a side effect. let brillig_side_effects = BTreeMap::from_iter(vec![(BrilligFunctionId(0), true)]); @@ -432,45 +436,51 @@ mod tests { let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions.clone()); - assert_eq!(optimized_circuit.opcodes.len(), expected_length); - assert_eq!( - optimized_circuit.opcodes[0], - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput::witness(Witness(1), 32) // The minimum does not propagate backwards. - }) - ); + // `BLACKBOX::RANGE [(_1, 32)] []` remains: The minimum does not propagate backwards. + assert_circuit_snapshot!(optimized_circuit, @r" + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 32)] [] + BRILLIG CALL func 0: inputs: [EXPR [ (1, _2) 0 ]], outputs: [] + BLACKBOX::RANGE [(_1, 16)] [] + BRILLIG CALL func 0: inputs: [EXPR [ (1, _2) 0 ]], outputs: [] + EXPR [ (1, _1) 0 ] + "); // Applying again should have no effect (despite the range having the same bit size as the assert). - let optimizer = RangeOptimizer::new(optimized_circuit, &brillig_side_effects); + let optimizer = RangeOptimizer::new(optimized_circuit.clone(), &brillig_side_effects); let (double_optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); - assert_eq!(double_optimized_circuit.opcodes.len(), expected_length); + assert_eq!(optimized_circuit.to_string(), double_optimized_circuit.to_string()); } #[test] fn array_implied_ranges() { // The optimizer should use knowledge about array lengths and witnesses used to index these to remove range opcodes. - let mut circuit = test_circuit(vec![(Witness(1), 16)]); - - let mem_init = Opcode::MemoryInit { - block_id: BlockId(0), - init: vec![Witness(0); 8], - block_type: BlockType::Memory, - }; - let mem_op = Opcode::MemoryOp { - block_id: BlockId(0), - op: MemOp::read_at_mem_index(Witness(1).into(), Witness(2)), - predicate: None, - }; - - circuit.opcodes.push(mem_init.clone()); - circuit.opcodes.push(mem_op.clone()); + let src = " + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + BLACKBOX::RANGE [(_1, 16)] [] + INIT (id: 0, len: 8, witnesses: [_0, _0, _0, _0, _0, _0, _0, _0]) + MEM (id: 0, read at: EXPR [ (1, _1) 0 ], value: EXPR [ (1, _2) 0 ]) + "; + let circuit = Circuit::from_str(src).unwrap(); + let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); let brillig_side_effects = BTreeMap::new(); let optimizer = RangeOptimizer::new(circuit, &brillig_side_effects); let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); - assert_eq!(optimized_circuit.opcodes.len(), 2); - assert_eq!(optimized_circuit.opcodes[0], mem_init); - assert_eq!(optimized_circuit.opcodes[1], mem_op); + assert_circuit_snapshot!(optimized_circuit, @r" + current witness index : _1 + private parameters indices : [] + public parameters indices : [] + return value indices : [] + INIT (id: 0, len: 8, witnesses: [_0, _0, _0, _0, _0, _0, _0, _0]) + MEM (id: 0, read at: EXPR [ (1, _1) 0 ], value: EXPR [ (1, _2) 0 ]) + "); } } diff --git a/compiler/noirc_errors/Cargo.toml b/compiler/noirc_errors/Cargo.toml index aa2868d4ed8..84a08155ced 100644 --- a/compiler/noirc_errors/Cargo.toml +++ b/compiler/noirc_errors/Cargo.toml @@ -14,10 +14,10 @@ workspace = true [dependencies] acvm.workspace = true codespan-reporting.workspace = true -codespan.workspace = true fm.workspace = true rustc-hash.workspace = true noirc_printable_type.workspace = true +noirc_span.workspace = true serde.workspace = true tracing.workspace = true flate2.workspace = true diff --git a/compiler/noirc_errors/src/lib.rs b/compiler/noirc_errors/src/lib.rs index a775bbf2e8b..62c030fc9f3 100644 --- a/compiler/noirc_errors/src/lib.rs +++ b/compiler/noirc_errors/src/lib.rs @@ -5,7 +5,8 @@ pub mod call_stack; pub mod debug_info; mod position; pub mod reporter; -pub use position::{Located, Location, Position, Span, Spanned}; +pub use noirc_span::{Span, Spanned}; +pub use position::{Located, Location, Position}; pub use reporter::{CustomDiagnostic, DiagnosticKind}; use std::io::Write; diff --git a/compiler/noirc_errors/src/position.rs b/compiler/noirc_errors/src/position.rs index 711c7f05077..bfa876b4bc9 100644 --- a/compiler/noirc_errors/src/position.rs +++ b/compiler/noirc_errors/src/position.rs @@ -1,10 +1,9 @@ -use codespan::Span as ByteSpan; use fm::FileId; +use noirc_span::Span; use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, hash::{Hash, Hasher}, - ops::Range, }; pub type Position = u32; @@ -64,113 +63,6 @@ impl Located { } } -#[derive(PartialOrd, Eq, Ord, Debug, Clone, Default)] -pub struct Spanned { - pub contents: T, - span: Span, -} - -/// This is important for tests. Two Spanned objects are equal if their content is equal -/// They may not have the same span. Use into_span to test for Span being equal specifically -impl PartialEq> for Spanned { - fn eq(&self, other: &Spanned) -> bool { - self.contents == other.contents - } -} - -/// Hash-based data structures (HashMap, HashSet) rely on the inverse of Hash -/// being injective, i.e. x.eq(y) => hash(x, H) == hash(y, H), we hence align -/// this with the above -impl Hash for Spanned { - fn hash(&self, state: &mut H) { - self.contents.hash(state); - } -} - -impl Spanned { - pub fn from_position(start: Position, end: Position, contents: T) -> Spanned { - Spanned { span: Span::inclusive(start, end), contents } - } - - pub fn from(t_span: Span, contents: T) -> Spanned { - Spanned { span: t_span, contents } - } - - pub fn span(&self) -> Span { - self.span - } -} - -impl std::borrow::Borrow for Spanned { - fn borrow(&self) -> &T { - &self.contents - } -} - -#[derive( - PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone, Default, Deserialize, Serialize, -)] -pub struct Span(ByteSpan); - -impl Span { - pub fn inclusive(start: u32, end: u32) -> Span { - Span(ByteSpan::from(start..end + 1)) - } - - pub fn single_char(start: u32) -> Span { - Span::inclusive(start, start) - } - - pub fn empty(position: u32) -> Span { - Span::from(position..position) - } - - #[must_use] - pub fn merge(self, other: Span) -> Span { - Span(self.0.merge(other.0)) - } - - pub fn to_byte_span(self) -> ByteSpan { - self.0 - } - - pub fn start(&self) -> u32 { - self.0.start().into() - } - - pub fn end(&self) -> u32 { - self.0.end().into() - } - - pub fn contains(&self, other: &Span) -> bool { - 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() - } - - pub fn is_smaller(&self, other: &Span) -> bool { - let self_distance = self.end() - self.start(); - let other_distance = other.end() - other.start(); - self_distance < other_distance - } -} - -impl From for Range { - fn from(span: Span) -> Self { - span.0.into() - } -} - -impl From> for Span { - fn from(Range { start, end }: Range) -> Self { - Self(ByteSpan::new(start, end)) - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct Location { pub span: Span, @@ -211,37 +103,3 @@ impl PartialOrd for Location { Some(self.cmp(other)) } } - -#[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/compiler/noirc_errors/src/reporter.rs b/compiler/noirc_errors/src/reporter.rs index d406e897d65..b016d13f49e 100644 --- a/compiler/noirc_errors/src/reporter.rs +++ b/compiler/noirc_errors/src/reporter.rs @@ -1,10 +1,11 @@ use std::io::IsTerminal; -use crate::{Location, Span}; +use crate::Location; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::files::Files; use codespan_reporting::term; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; +use noirc_span::Span; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CustomDiagnostic { diff --git a/compiler/noirc_span/Cargo.toml b/compiler/noirc_span/Cargo.toml new file mode 100644 index 00000000000..25b6d8942d7 --- /dev/null +++ b/compiler/noirc_span/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "noirc_span" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[lints] +workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +codespan.workspace = true +serde.workspace = true diff --git a/compiler/noirc_span/src/lib.rs b/compiler/noirc_span/src/lib.rs new file mode 100644 index 00000000000..f76a98a37f9 --- /dev/null +++ b/compiler/noirc_span/src/lib.rs @@ -0,0 +1,5 @@ +#![forbid(unsafe_code)] +#![warn(unused_crate_dependencies, unused_extern_crates)] + +mod position; +pub use position::{Position, Span, Spanned}; diff --git a/compiler/noirc_span/src/position.rs b/compiler/noirc_span/src/position.rs new file mode 100644 index 00000000000..50300c5f3b3 --- /dev/null +++ b/compiler/noirc_span/src/position.rs @@ -0,0 +1,149 @@ +use codespan::Span as ByteSpan; +use serde::{Deserialize, Serialize}; +use std::{ + hash::{Hash, Hasher}, + ops::Range, +}; + +pub type Position = u32; + +#[derive(PartialOrd, Eq, Ord, Debug, Clone, Default)] +pub struct Spanned { + pub contents: T, + span: Span, +} + +/// This is important for tests. Two Spanned objects are equal if their content is equal +/// They may not have the same span. Use into_span to test for Span being equal specifically +impl PartialEq> for Spanned { + fn eq(&self, other: &Spanned) -> bool { + self.contents == other.contents + } +} + +/// Hash-based data structures (HashMap, HashSet) rely on the inverse of Hash +/// being injective, i.e. x.eq(y) => hash(x, H) == hash(y, H), we hence align +/// this with the above +impl Hash for Spanned { + fn hash(&self, state: &mut H) { + self.contents.hash(state); + } +} + +impl Spanned { + pub fn from_position(start: Position, end: Position, contents: T) -> Spanned { + Spanned { span: Span::inclusive(start, end), contents } + } + + pub fn from(t_span: Span, contents: T) -> Spanned { + Spanned { span: t_span, contents } + } + + pub fn span(&self) -> Span { + self.span + } +} + +impl std::borrow::Borrow for Spanned { + fn borrow(&self) -> &T { + &self.contents + } +} + +#[derive( + PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone, Default, Deserialize, Serialize, +)] +pub struct Span(ByteSpan); + +impl Span { + pub fn inclusive(start: u32, end: u32) -> Span { + Span(ByteSpan::from(start..end + 1)) + } + + pub fn single_char(start: u32) -> Span { + Span::inclusive(start, start) + } + + pub fn empty(position: u32) -> Span { + Span::from(position..position) + } + + #[must_use] + pub fn merge(self, other: Span) -> Span { + Span(self.0.merge(other.0)) + } + + pub fn to_byte_span(self) -> ByteSpan { + self.0 + } + + pub fn start(&self) -> u32 { + self.0.start().into() + } + + pub fn end(&self) -> u32 { + self.0.end().into() + } + + pub fn contains(&self, other: &Span) -> bool { + 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() + } + + pub fn is_smaller(&self, other: &Span) -> bool { + let self_distance = self.end() - self.start(); + let other_distance = other.end() - other.start(); + self_distance < other_distance + } +} + +impl From for Range { + fn from(span: Span) -> Self { + span.0.into() + } +} + +impl From> for Span { + fn from(Range { start, end }: Range) -> Self { + Self(ByteSpan::new(start, end)) + } +} + +#[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))); + } +}