* resolve #273: remove global constant MAX_DEPTH, instead use the respective value from Params * simplify partialeq at mockmainpod
This commit is contained in:
parent
115c3c1152
commit
b6041508e5
7 changed files with 60 additions and 41 deletions
|
|
@ -40,7 +40,7 @@ impl PodProver for MockProver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct MockMainPod {
|
pub struct MockMainPod {
|
||||||
params: Params,
|
params: Params,
|
||||||
id: PodId,
|
id: PodId,
|
||||||
|
|
@ -56,19 +56,6 @@ pub struct MockMainPod {
|
||||||
merkle_proofs_containers: Vec<MerkleClaimAndProof>,
|
merkle_proofs_containers: Vec<MerkleClaimAndProof>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for MockMainPod {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.params == other.params
|
|
||||||
&& self.id == other.id
|
|
||||||
&& self.vd_set == other.vd_set
|
|
||||||
&& self.input_signed_pods == other.input_signed_pods
|
|
||||||
&& self.input_recursive_pods == other.input_recursive_pods
|
|
||||||
&& self.statements == other.statements
|
|
||||||
&& self.operations == other.operations
|
|
||||||
&& self.public_statements == other.public_statements
|
|
||||||
&& self.merkle_proofs_containers == other.merkle_proofs_containers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for MockMainPod {}
|
impl Eq for MockMainPod {}
|
||||||
|
|
||||||
impl fmt::Display for MockMainPod {
|
impl fmt::Display for MockMainPod {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
primitives::merkletree::MerkleTree,
|
primitives::merkletree::MerkleTree,
|
||||||
},
|
},
|
||||||
constants::MAX_DEPTH,
|
|
||||||
middleware::{
|
middleware::{
|
||||||
containers::Dictionary, hash_str, serialization::ordered_map, AnchoredKey, Key, Params,
|
containers::Dictionary, hash_str, serialization::ordered_map, AnchoredKey, Key, Params,
|
||||||
Pod, PodId, PodSigner, PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE, SELF,
|
Pod, PodId, PodSigner, PodType, RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE, SELF,
|
||||||
|
|
@ -35,7 +34,12 @@ impl MockSigner {
|
||||||
let dict = Dictionary::new(params.max_depth_mt_containers, kvs.clone())?;
|
let dict = Dictionary::new(params.max_depth_mt_containers, kvs.clone())?;
|
||||||
let id = PodId(dict.commitment());
|
let id = PodId(dict.commitment());
|
||||||
let signature = format!("{}_signed_by_{}", id, pubkey);
|
let signature = format!("{}_signed_by_{}", id, pubkey);
|
||||||
Ok(MockSignedPod { id, signature, kvs })
|
Ok(MockSignedPod {
|
||||||
|
mt_max_depth: params.max_depth_mt_containers,
|
||||||
|
id,
|
||||||
|
signature,
|
||||||
|
kvs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +51,7 @@ impl PodSigner for MockSigner {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct MockSignedPod {
|
pub struct MockSignedPod {
|
||||||
|
mt_max_depth: usize,
|
||||||
id: PodId,
|
id: PodId,
|
||||||
signature: String,
|
signature: String,
|
||||||
kvs: HashMap<Key, Value>,
|
kvs: HashMap<Key, Value>,
|
||||||
|
|
@ -54,6 +59,7 @@ pub struct MockSignedPod {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct Data {
|
struct Data {
|
||||||
|
mt_max_depth: usize,
|
||||||
signature: String,
|
signature: String,
|
||||||
#[serde(serialize_with = "ordered_map")]
|
#[serde(serialize_with = "ordered_map")]
|
||||||
kvs: HashMap<Key, Value>,
|
kvs: HashMap<Key, Value>,
|
||||||
|
|
@ -67,6 +73,7 @@ impl MockSignedPod {
|
||||||
pub(crate) fn deserialize(id: PodId, data: serde_json::Value) -> Result<Box<dyn Pod>> {
|
pub(crate) fn deserialize(id: PodId, data: serde_json::Value) -> Result<Box<dyn Pod>> {
|
||||||
let data: Data = serde_json::from_value(data)?;
|
let data: Data = serde_json::from_value(data)?;
|
||||||
Ok(Box::new(Self {
|
Ok(Box::new(Self {
|
||||||
|
mt_max_depth: data.mt_max_depth,
|
||||||
id,
|
id,
|
||||||
signature: data.signature,
|
signature: data.signature,
|
||||||
kvs: data.kvs,
|
kvs: data.kvs,
|
||||||
|
|
@ -90,7 +97,7 @@ impl Pod for MockSignedPod {
|
||||||
fn verify(&self) -> Result<()> {
|
fn verify(&self) -> Result<()> {
|
||||||
// 1. Verify id
|
// 1. Verify id
|
||||||
let mt = MerkleTree::new(
|
let mt = MerkleTree::new(
|
||||||
MAX_DEPTH,
|
self.mt_max_depth,
|
||||||
&self
|
&self
|
||||||
.kvs
|
.kvs
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -153,6 +160,7 @@ impl Pod for MockSignedPod {
|
||||||
|
|
||||||
fn serialize_data(&self) -> serde_json::Value {
|
fn serialize_data(&self) -> serde_json::Value {
|
||||||
serde_json::to_value(Data {
|
serde_json::to_value(Data {
|
||||||
|
mt_max_depth: self.mt_max_depth,
|
||||||
signature: self.signature.clone(),
|
signature: self.signature.clone(),
|
||||||
kvs: self.kvs.clone(),
|
kvs: self.kvs.clone(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ use crate::{
|
||||||
},
|
},
|
||||||
serialize_bytes,
|
serialize_bytes,
|
||||||
},
|
},
|
||||||
constants::MAX_DEPTH,
|
|
||||||
middleware::{
|
middleware::{
|
||||||
containers::Dictionary, AnchoredKey, Hash, Key, Params, Pod, PodId, PodSigner, PodType,
|
containers::Dictionary, AnchoredKey, Hash, Key, Params, Pod, PodId, PodSigner, PodType,
|
||||||
RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE, SELF,
|
RawValue, Statement, Value, KEY_SIGNER, KEY_TYPE, SELF,
|
||||||
|
|
@ -142,7 +141,7 @@ impl Pod for SignedPod {
|
||||||
|
|
||||||
// 2. Verify id
|
// 2. Verify id
|
||||||
let mt = MerkleTree::new(
|
let mt = MerkleTree::new(
|
||||||
MAX_DEPTH,
|
self.dict.max_depth(),
|
||||||
&self
|
&self
|
||||||
.dict
|
.dict
|
||||||
.kvs()
|
.kvs()
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
pub const MAX_DEPTH: usize = 32;
|
|
||||||
|
|
@ -109,7 +109,7 @@ pub fn process_pest_tree(
|
||||||
available_batches,
|
available_batches,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
second_pass(&mut processing_ctx)
|
second_pass(&mut processing_ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pass 1: Iterates through top-level definitions, records custom predicate
|
/// Pass 1: Iterates through top-level definitions, records custom predicate
|
||||||
|
|
@ -266,18 +266,21 @@ enum StatementContext<'a> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn second_pass(ctx: &mut ProcessingContext) -> Result<PodlangOutput, ProcessorError> {
|
fn second_pass(
|
||||||
|
ctx: &mut ProcessingContext,
|
||||||
|
params: &Params,
|
||||||
|
) -> Result<PodlangOutput, ProcessorError> {
|
||||||
let mut cpb_builder =
|
let mut cpb_builder =
|
||||||
CustomPredicateBatchBuilder::new(ctx.params.clone(), "PodlangBatch".to_string());
|
CustomPredicateBatchBuilder::new(ctx.params.clone(), "PodlangBatch".to_string());
|
||||||
|
|
||||||
for pred_pair in &ctx.custom_predicate_pairs {
|
for pred_pair in &ctx.custom_predicate_pairs {
|
||||||
process_and_add_custom_predicate_to_batch(pred_pair, ctx, &mut cpb_builder)?;
|
process_and_add_custom_predicate_to_batch(params, pred_pair, ctx, &mut cpb_builder)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let custom_batch = cpb_builder.finish();
|
let custom_batch = cpb_builder.finish();
|
||||||
|
|
||||||
let request_templates = if let Some(req_pair) = &ctx.request_pair {
|
let request_templates = if let Some(req_pair) = &ctx.request_pair {
|
||||||
process_request_def(req_pair, ctx, &custom_batch)?
|
process_request_def(params, req_pair, ctx, &custom_batch)?
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
@ -288,10 +291,13 @@ fn second_pass(ctx: &mut ProcessingContext) -> Result<PodlangOutput, ProcessorEr
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pest_pair_to_builder_arg(arg_content_pair: &Pair<Rule>) -> Result<BuilderArg, ProcessorError> {
|
fn pest_pair_to_builder_arg(
|
||||||
|
params: &Params,
|
||||||
|
arg_content_pair: &Pair<Rule>,
|
||||||
|
) -> Result<BuilderArg, ProcessorError> {
|
||||||
match arg_content_pair.as_rule() {
|
match arg_content_pair.as_rule() {
|
||||||
Rule::literal_value => {
|
Rule::literal_value => {
|
||||||
let value = process_literal_value(arg_content_pair)?;
|
let value = process_literal_value(params, arg_content_pair)?;
|
||||||
Ok(BuilderArg::Literal(value))
|
Ok(BuilderArg::Literal(value))
|
||||||
}
|
}
|
||||||
Rule::wildcard => {
|
Rule::wildcard => {
|
||||||
|
|
@ -421,6 +427,7 @@ fn validate_and_build_statement_template(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_and_add_custom_predicate_to_batch(
|
fn process_and_add_custom_predicate_to_batch(
|
||||||
|
params: &Params,
|
||||||
pred_def_pair: &Pair<Rule>,
|
pred_def_pair: &Pair<Rule>,
|
||||||
processing_ctx: &ProcessingContext,
|
processing_ctx: &ProcessingContext,
|
||||||
cpb_builder: &mut CustomPredicateBatchBuilder,
|
cpb_builder: &mut CustomPredicateBatchBuilder,
|
||||||
|
|
@ -505,6 +512,7 @@ fn process_and_add_custom_predicate_to_batch(
|
||||||
.filter(|p| p.as_rule() == Rule::statement)
|
.filter(|p| p.as_rule() == Rule::statement)
|
||||||
{
|
{
|
||||||
let stb = process_statement_template(
|
let stb = process_statement_template(
|
||||||
|
params,
|
||||||
&stmt_pair,
|
&stmt_pair,
|
||||||
processing_ctx,
|
processing_ctx,
|
||||||
StatementContext::CustomPredicate,
|
StatementContext::CustomPredicate,
|
||||||
|
|
@ -526,6 +534,7 @@ fn process_and_add_custom_predicate_to_batch(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_request_def(
|
fn process_request_def(
|
||||||
|
params: &Params,
|
||||||
req_def_pair: &Pair<Rule>,
|
req_def_pair: &Pair<Rule>,
|
||||||
processing_ctx: &ProcessingContext,
|
processing_ctx: &ProcessingContext,
|
||||||
custom_batch: &Arc<CustomPredicateBatch>,
|
custom_batch: &Arc<CustomPredicateBatch>,
|
||||||
|
|
@ -545,6 +554,7 @@ fn process_request_def(
|
||||||
.filter(|p| p.as_rule() == Rule::statement)
|
.filter(|p| p.as_rule() == Rule::statement)
|
||||||
{
|
{
|
||||||
let built_stb = process_statement_template(
|
let built_stb = process_statement_template(
|
||||||
|
params,
|
||||||
&stmt_pair,
|
&stmt_pair,
|
||||||
processing_ctx,
|
processing_ctx,
|
||||||
StatementContext::Request {
|
StatementContext::Request {
|
||||||
|
|
@ -569,6 +579,7 @@ fn process_request_def(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_statement_template(
|
fn process_statement_template(
|
||||||
|
params: &Params,
|
||||||
stmt_pair: &Pair<Rule>,
|
stmt_pair: &Pair<Rule>,
|
||||||
processing_ctx: &ProcessingContext,
|
processing_ctx: &ProcessingContext,
|
||||||
mut context: StatementContext,
|
mut context: StatementContext,
|
||||||
|
|
@ -579,7 +590,7 @@ fn process_statement_template(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let stmt_name_str = name_pair.as_str();
|
let stmt_name_str = name_pair.as_str();
|
||||||
|
|
||||||
let builder_args = parse_statement_args(stmt_pair)?;
|
let builder_args = parse_statement_args(params, stmt_pair)?;
|
||||||
|
|
||||||
if let StatementContext::Request {
|
if let StatementContext::Request {
|
||||||
wildcard_names,
|
wildcard_names,
|
||||||
|
|
@ -640,7 +651,10 @@ fn process_statement_template(
|
||||||
Ok(stb.desugar())
|
Ok(stb.desugar())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_literal_value(lit_val_pair: &Pair<Rule>) -> Result<Value, ProcessorError> {
|
fn process_literal_value(
|
||||||
|
params: &Params,
|
||||||
|
lit_val_pair: &Pair<Rule>,
|
||||||
|
) -> Result<Value, ProcessorError> {
|
||||||
let inner_lit = lit_val_pair.clone().into_inner().next().unwrap();
|
let inner_lit = lit_val_pair.clone().into_inner().next().unwrap();
|
||||||
|
|
||||||
match inner_lit.as_rule() {
|
match inner_lit.as_rule() {
|
||||||
|
|
@ -684,10 +698,10 @@ fn process_literal_value(lit_val_pair: &Pair<Rule>) -> Result<Value, ProcessorEr
|
||||||
Rule::literal_array => {
|
Rule::literal_array => {
|
||||||
let elements: Result<Vec<Value>, ProcessorError> = inner_lit
|
let elements: Result<Vec<Value>, ProcessorError> = inner_lit
|
||||||
.into_inner()
|
.into_inner()
|
||||||
.map(|elem_pair| process_literal_value(&elem_pair))
|
.map(|elem_pair| process_literal_value(params, &elem_pair))
|
||||||
.collect();
|
.collect();
|
||||||
let middleware_array =
|
let middleware_array =
|
||||||
middleware::containers::Array::new(crate::constants::MAX_DEPTH, elements?)
|
middleware::containers::Array::new(params.max_depth_mt_containers, elements?)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
ProcessorError::Internal(format!("Failed to create Array: {}", e))
|
ProcessorError::Internal(format!("Failed to create Array: {}", e))
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -696,12 +710,13 @@ fn process_literal_value(lit_val_pair: &Pair<Rule>) -> Result<Value, ProcessorEr
|
||||||
Rule::literal_set => {
|
Rule::literal_set => {
|
||||||
let elements: Result<HashSet<Value>, ProcessorError> = inner_lit
|
let elements: Result<HashSet<Value>, ProcessorError> = inner_lit
|
||||||
.into_inner()
|
.into_inner()
|
||||||
.map(|elem_pair| process_literal_value(&elem_pair))
|
.map(|elem_pair| process_literal_value(params, &elem_pair))
|
||||||
.collect();
|
.collect();
|
||||||
let middleware_set =
|
let middleware_set =
|
||||||
middleware::containers::Set::new(crate::constants::MAX_DEPTH, elements?).map_err(
|
middleware::containers::Set::new(params.max_depth_mt_containers, elements?)
|
||||||
|e| ProcessorError::Internal(format!("Failed to create Set: {}", e)),
|
.map_err(|e| {
|
||||||
)?;
|
ProcessorError::Internal(format!("Failed to create Set: {}", e))
|
||||||
|
})?;
|
||||||
Ok(Value::from(middleware_set))
|
Ok(Value::from(middleware_set))
|
||||||
}
|
}
|
||||||
Rule::literal_dict => {
|
Rule::literal_dict => {
|
||||||
|
|
@ -712,12 +727,12 @@ fn process_literal_value(lit_val_pair: &Pair<Rule>) -> Result<Value, ProcessorEr
|
||||||
let key_pair = entry_inner.next().unwrap();
|
let key_pair = entry_inner.next().unwrap();
|
||||||
let val_pair = entry_inner.next().unwrap();
|
let val_pair = entry_inner.next().unwrap();
|
||||||
let key_str = parse_pest_string_literal(&key_pair)?;
|
let key_str = parse_pest_string_literal(&key_pair)?;
|
||||||
let val = process_literal_value(&val_pair)?;
|
let val = process_literal_value(params, &val_pair)?;
|
||||||
Ok((Key::new(key_str), val))
|
Ok((Key::new(key_str), val))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let middleware_dict =
|
let middleware_dict =
|
||||||
middleware::containers::Dictionary::new(crate::constants::MAX_DEPTH, pairs?)
|
middleware::containers::Dictionary::new(params.max_depth_mt_containers, pairs?)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
ProcessorError::Internal(format!("Failed to create Dictionary: {}", e))
|
ProcessorError::Internal(format!("Failed to create Dictionary: {}", e))
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -862,7 +877,10 @@ fn resolve_request_statement_builder(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_statement_args(stmt_pair: &Pair<Rule>) -> Result<Vec<BuilderArg>, ProcessorError> {
|
fn parse_statement_args(
|
||||||
|
params: &Params,
|
||||||
|
stmt_pair: &Pair<Rule>,
|
||||||
|
) -> Result<Vec<BuilderArg>, ProcessorError> {
|
||||||
let mut builder_args = Vec::new();
|
let mut builder_args = Vec::new();
|
||||||
let mut inner_stmt_pairs = stmt_pair.clone().into_inner();
|
let mut inner_stmt_pairs = stmt_pair.clone().into_inner();
|
||||||
|
|
||||||
|
|
@ -873,7 +891,7 @@ fn parse_statement_args(stmt_pair: &Pair<Rule>) -> Result<Vec<BuilderArg>, Proce
|
||||||
.filter(|p| p.as_rule() == Rule::statement_arg)
|
.filter(|p| p.as_rule() == Rule::statement_arg)
|
||||||
{
|
{
|
||||||
let arg_content_pair = arg_pair.into_inner().next().unwrap();
|
let arg_content_pair = arg_pair.into_inner().next().unwrap();
|
||||||
let builder_arg = pest_pair_to_builder_arg(&arg_content_pair)?;
|
let builder_arg = pest_pair_to_builder_arg(params, &arg_content_pair)?;
|
||||||
builder_args.push(builder_arg);
|
builder_args.push(builder_arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1103,7 +1121,7 @@ mod processor_tests {
|
||||||
let params = Params::default();
|
let params = Params::default();
|
||||||
let mut ctx = ProcessingContext::new(¶ms);
|
let mut ctx = ProcessingContext::new(¶ms);
|
||||||
first_pass(pairs, &mut ctx, &[])?;
|
first_pass(pairs, &mut ctx, &[])?;
|
||||||
let result = second_pass(&mut ctx);
|
let result = second_pass(&mut ctx, ¶ms);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
match result.err().unwrap() {
|
match result.err().unwrap() {
|
||||||
ProcessorError::UndefinedIdentifier { name, span: _ } => {
|
ProcessorError::UndefinedIdentifier { name, span: _ } => {
|
||||||
|
|
@ -1122,7 +1140,7 @@ mod processor_tests {
|
||||||
let params = Params::default();
|
let params = Params::default();
|
||||||
let mut ctx = ProcessingContext::new(¶ms);
|
let mut ctx = ProcessingContext::new(¶ms);
|
||||||
first_pass(pairs, &mut ctx, &[])?;
|
first_pass(pairs, &mut ctx, &[])?;
|
||||||
let result = second_pass(&mut ctx);
|
let result = second_pass(&mut ctx, ¶ms);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
match result.err().unwrap() {
|
match result.err().unwrap() {
|
||||||
ProcessorError::UndefinedIdentifier { name, span: _ } => {
|
ProcessorError::UndefinedIdentifier { name, span: _ } => {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
#![feature(mapped_lock_guards)]
|
#![feature(mapped_lock_guards)]
|
||||||
|
|
||||||
pub mod backends;
|
pub mod backends;
|
||||||
pub mod constants;
|
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
pub mod lang;
|
pub mod lang;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ impl Dictionary {
|
||||||
pub fn kvs(&self) -> &HashMap<Key, Value> {
|
pub fn kvs(&self) -> &HashMap<Key, Value> {
|
||||||
&self.kvs
|
&self.kvs
|
||||||
}
|
}
|
||||||
|
pub fn max_depth(&self) -> usize {
|
||||||
|
self.max_depth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Dictionary {
|
impl PartialEq for Dictionary {
|
||||||
|
|
@ -171,6 +174,9 @@ impl Set {
|
||||||
pub fn set(&self) -> &HashSet<Value> {
|
pub fn set(&self) -> &HashSet<Value> {
|
||||||
&self.set
|
&self.set
|
||||||
}
|
}
|
||||||
|
pub fn max_depth(&self) -> usize {
|
||||||
|
self.max_depth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Set {
|
impl PartialEq for Set {
|
||||||
|
|
@ -256,6 +262,9 @@ impl Array {
|
||||||
pub fn array(&self) -> &[Value] {
|
pub fn array(&self) -> &[Value] {
|
||||||
&self.array
|
&self.array
|
||||||
}
|
}
|
||||||
|
pub fn max_depth(&self) -> usize {
|
||||||
|
self.max_depth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Array {
|
impl PartialEq for Array {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue