fixes:
fix: rearrange clauses and fill in gaps now handles nested patterns in a uniform way fix: discards in records was being sorted incorrectly leading to type issues chore: remove some filter maps in cases where None is impossible anyway chore: some refactoring on a couple functions to clean up
This commit is contained in:
parent
9c29f4f26b
commit
8f0cf289b4
|
@ -825,13 +825,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
clause_then_stack = clause_guard_stack;
|
clause_then_stack = clause_guard_stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
match clause_properties {
|
// deal with clause pattern and then itself
|
||||||
ClauseProperties::ConstrClause {
|
|
||||||
original_subject_name,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let subject_name = original_subject_name.clone();
|
|
||||||
|
|
||||||
self.when_pattern(
|
self.when_pattern(
|
||||||
&clause.pattern,
|
&clause.pattern,
|
||||||
&mut clause_pattern_stack,
|
&mut clause_pattern_stack,
|
||||||
|
@ -840,6 +834,13 @@ impl<'a> CodeGenerator<'a> {
|
||||||
clause_properties,
|
clause_properties,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
match clause_properties {
|
||||||
|
ClauseProperties::ConstrClause {
|
||||||
|
original_subject_name,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let subject_name = original_subject_name.clone();
|
||||||
|
|
||||||
if clause.pattern.is_var() || clause.pattern.is_discard() {
|
if clause.pattern.is_var() || clause.pattern.is_discard() {
|
||||||
ir_stack.wrap_clause(clause_pattern_stack);
|
ir_stack.wrap_clause(clause_pattern_stack);
|
||||||
} else {
|
} else {
|
||||||
|
@ -883,64 +884,55 @@ impl<'a> CodeGenerator<'a> {
|
||||||
current_index,
|
current_index,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let (current_clause_index, has_tail) =
|
let original_subject_name = original_subject_name.clone();
|
||||||
if let Pattern::List { elements, tail, .. } = &clause.pattern {
|
|
||||||
(elements.len(), tail.is_some())
|
|
||||||
} else if let Pattern::Assign { pattern, .. } = &clause.pattern {
|
|
||||||
let Pattern::List { elements, tail, .. } = pattern.as_ref() else {
|
|
||||||
unreachable!("{:#?}", pattern)
|
|
||||||
};
|
|
||||||
|
|
||||||
(elements.len(), tail.is_some())
|
|
||||||
} else {
|
|
||||||
unreachable!("{:#?}", &clause.pattern)
|
|
||||||
};
|
|
||||||
|
|
||||||
let prev_index = *current_index;
|
let prev_index = *current_index;
|
||||||
|
|
||||||
|
let elements_count_and_has_tail =
|
||||||
|
builder::get_list_elements_len_and_tail(&clause.pattern);
|
||||||
|
|
||||||
|
if let Some((current_clause_index, has_tail)) = elements_count_and_has_tail {
|
||||||
let subject_name = if current_clause_index == 0 {
|
let subject_name = if current_clause_index == 0 {
|
||||||
original_subject_name.clone()
|
original_subject_name.clone()
|
||||||
} else {
|
} else {
|
||||||
format!("__tail_{}", current_clause_index - 1)
|
format!("__tail_{}", current_clause_index - 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.when_pattern(
|
// If current clause has already exposed all needed list items then no need to expose the
|
||||||
&clause.pattern,
|
// same items again.
|
||||||
&mut clause_pattern_stack,
|
if current_clause_index as i64 - i64::from(has_tail) == prev_index {
|
||||||
clause_then_stack,
|
ir_stack.wrap_clause(clause_pattern_stack);
|
||||||
subject_type,
|
} else {
|
||||||
clause_properties,
|
let next_elements_count_and_has_tail = if index == clauses.len() - 1 {
|
||||||
);
|
|
||||||
|
|
||||||
let next_tail = if index == clauses.len() - 1 {
|
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let next_list_size = if let Pattern::List { elements, .. } =
|
builder::get_list_elements_len_and_tail(
|
||||||
&clauses[index + 1].pattern
|
&clauses
|
||||||
{
|
.get(index + 1)
|
||||||
elements.len()
|
.unwrap_or_else(|| {
|
||||||
} else if let Pattern::Assign { pattern, .. } = &clauses[index + 1].pattern
|
unreachable!(
|
||||||
{
|
"We checked length how are we out of bounds"
|
||||||
let Pattern::List { elements, .. } = pattern.as_ref() else {
|
)
|
||||||
unreachable!("{:#?}", pattern)
|
})
|
||||||
};
|
.pattern,
|
||||||
elements.len()
|
)
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if next_list_size == current_clause_index {
|
let next_tail = if let Some((next_elements_len, _)) =
|
||||||
|
next_elements_count_and_has_tail
|
||||||
|
{
|
||||||
|
if next_elements_len == current_clause_index {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(format!("__tail_{current_clause_index}"))
|
Some(format!("__tail_{current_clause_index}"))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let minus_tail = has_tail as i64;
|
//mutate current index if we use list clause
|
||||||
|
*current_index = current_clause_index as i64;
|
||||||
|
|
||||||
if current_clause_index as i64 - minus_tail == prev_index {
|
|
||||||
ir_stack.wrap_clause(clause_pattern_stack);
|
|
||||||
} else {
|
|
||||||
ir_stack.list_clause(
|
ir_stack.list_clause(
|
||||||
subject_type.clone(),
|
subject_type.clone(),
|
||||||
subject_name,
|
subject_name,
|
||||||
|
@ -949,12 +941,9 @@ impl<'a> CodeGenerator<'a> {
|
||||||
clause_pattern_stack,
|
clause_pattern_stack,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
let ClauseProperties::ListClause { current_index, .. } = clause_properties else {
|
ir_stack.wrap_clause(clause_pattern_stack);
|
||||||
unreachable!()
|
}
|
||||||
};
|
|
||||||
|
|
||||||
*current_index = current_clause_index as i64;
|
|
||||||
}
|
}
|
||||||
ClauseProperties::TupleClause {
|
ClauseProperties::TupleClause {
|
||||||
original_subject_name,
|
original_subject_name,
|
||||||
|
@ -964,14 +953,6 @@ impl<'a> CodeGenerator<'a> {
|
||||||
let prev_defined_tuple_indices = defined_tuple_indices.clone();
|
let prev_defined_tuple_indices = defined_tuple_indices.clone();
|
||||||
let subject_name = original_subject_name.clone();
|
let subject_name = original_subject_name.clone();
|
||||||
|
|
||||||
self.when_pattern(
|
|
||||||
&clause.pattern,
|
|
||||||
&mut clause_pattern_stack,
|
|
||||||
clause_then_stack,
|
|
||||||
subject_type,
|
|
||||||
clause_properties,
|
|
||||||
);
|
|
||||||
|
|
||||||
let current_defined_tuple_indices = match clause_properties {
|
let current_defined_tuple_indices = match clause_properties {
|
||||||
ClauseProperties::TupleClause {
|
ClauseProperties::TupleClause {
|
||||||
defined_tuple_indices,
|
defined_tuple_indices,
|
||||||
|
@ -1215,7 +1196,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
})
|
})
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
||||||
if tail.is_some() && !elements.is_empty() {
|
if tail.is_some() && !tail_head_names.is_empty() {
|
||||||
let tail_var = if elements.len() == 1 {
|
let tail_var = if elements.len() == 1 {
|
||||||
clause_properties.original_subject_name().clone()
|
clause_properties.original_subject_name().clone()
|
||||||
} else {
|
} else {
|
||||||
|
@ -1234,7 +1215,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
tail,
|
tail,
|
||||||
nested_pattern,
|
nested_pattern,
|
||||||
);
|
);
|
||||||
} else if !elements.is_empty() {
|
} else if !tail_head_names.is_empty() {
|
||||||
pattern_stack.list_expose(
|
pattern_stack.list_expose(
|
||||||
tipo.clone().into(),
|
tipo.clone().into(),
|
||||||
tail_head_names,
|
tail_head_names,
|
||||||
|
@ -1255,13 +1236,13 @@ impl<'a> CodeGenerator<'a> {
|
||||||
} => {
|
} => {
|
||||||
let data_type = builder::lookup_data_type_by_tipo(&self.data_types, tipo).unwrap();
|
let data_type = builder::lookup_data_type_by_tipo(&self.data_types, tipo).unwrap();
|
||||||
|
|
||||||
let (_, constructor_type) = data_type
|
let constructor_type = data_type
|
||||||
.constructors
|
.constructors
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.find(|dt| &dt.name == constr_name)
|
||||||
.find(|(_, dt)| &dt.name == constr_name)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut nested_pattern = pattern_stack.empty_with_scope();
|
let mut nested_pattern = pattern_stack.empty_with_scope();
|
||||||
|
|
||||||
if *is_record {
|
if *is_record {
|
||||||
let field_map = match constructor {
|
let field_map = match constructor {
|
||||||
PatternConstructor::Record { field_map, .. } => field_map.clone().unwrap(),
|
PatternConstructor::Record { field_map, .. } => field_map.clone().unwrap(),
|
||||||
|
@ -1278,13 +1259,16 @@ impl<'a> CodeGenerator<'a> {
|
||||||
|
|
||||||
let arguments_index = arguments
|
let arguments_index = arguments
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|item| {
|
.enumerate()
|
||||||
|
.map(|(index, item)| {
|
||||||
let label = item.label.clone().unwrap_or_default();
|
let label = item.label.clone().unwrap_or_default();
|
||||||
|
|
||||||
let field_index = field_map
|
let field_index = field_map
|
||||||
.fields
|
.fields
|
||||||
.get(&label)
|
.get(&label)
|
||||||
.map(|(index, _)| index)
|
.map(|(index, _)| index)
|
||||||
.unwrap_or(&0);
|
.unwrap_or(&index);
|
||||||
|
|
||||||
let var_name = self.nested_pattern_ir_and_label(
|
let var_name = self.nested_pattern_ir_and_label(
|
||||||
&item.value,
|
&item.value,
|
||||||
&mut nested_pattern,
|
&mut nested_pattern,
|
||||||
|
@ -1301,8 +1285,8 @@ impl<'a> CodeGenerator<'a> {
|
||||||
);
|
);
|
||||||
|
|
||||||
var_name.map_or(
|
var_name.map_or(
|
||||||
Some((label.clone(), "_".to_string(), *field_index)),
|
(label.clone(), "_".to_string(), *field_index),
|
||||||
|var_name| Some((label, var_name, *field_index)),
|
|var_name| (label, var_name, *field_index),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.sorted_by(|item1, item2| item1.2.cmp(&item2.2))
|
.sorted_by(|item1, item2| item1.2.cmp(&item2.2))
|
||||||
|
@ -1335,7 +1319,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
let arguments_index = arguments
|
let arguments_index = arguments
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(index, item)| {
|
.map(|(index, item)| {
|
||||||
let var_name = self.nested_pattern_ir_and_label(
|
let var_name = self.nested_pattern_ir_and_label(
|
||||||
&item.value,
|
&item.value,
|
||||||
&mut nested_pattern,
|
&mut nested_pattern,
|
||||||
|
@ -1343,9 +1327,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
*clause_properties.is_final_clause(),
|
*clause_properties.is_final_clause(),
|
||||||
);
|
);
|
||||||
|
|
||||||
var_name.map_or(Some(("_".to_string(), index)), |var_name| {
|
var_name.map_or(("_".to_string(), index), |var_name| (var_name, index))
|
||||||
Some((var_name, index))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<(String, usize)>>();
|
.collect::<Vec<(String, usize)>>();
|
||||||
|
|
||||||
|
@ -1878,7 +1860,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
let arguments_index = arguments
|
let arguments_index = arguments
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(index, item)| {
|
.map(|(index, item)| {
|
||||||
let label = item.label.clone().unwrap_or_default();
|
let label = item.label.clone().unwrap_or_default();
|
||||||
|
|
||||||
let field_index = if let Some(field_map) = &field_map {
|
let field_index = if let Some(field_map) = &field_map {
|
||||||
|
@ -1896,11 +1878,10 @@ impl<'a> CodeGenerator<'a> {
|
||||||
&assignment_properties,
|
&assignment_properties,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Note the stacks mutation here
|
||||||
stacks.merge(nested_pattern);
|
stacks.merge(nested_pattern);
|
||||||
|
|
||||||
name.map_or(Some(("_".to_string(), field_index)), |name| {
|
name.map_or(("_".to_string(), field_index), |name| (name, field_index))
|
||||||
Some((name, field_index))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.sorted_by(|item1, item2| item1.1.cmp(&item2.1))
|
.sorted_by(|item1, item2| item1.1.cmp(&item2.1))
|
||||||
.collect::<Vec<(String, usize)>>();
|
.collect::<Vec<(String, usize)>>();
|
||||||
|
|
|
@ -281,51 +281,61 @@ pub fn rearrange_clauses(clauses: Vec<TypedClause>) -> Vec<TypedClause> {
|
||||||
let mut sorted_clauses = clauses;
|
let mut sorted_clauses = clauses;
|
||||||
|
|
||||||
// if we have a list sort clauses so we can plug holes for cases not covered by clauses
|
// if we have a list sort clauses so we can plug holes for cases not covered by clauses
|
||||||
// TODO: while having 10000000 element list is impossible to destructure in plutus budget,
|
// Now we sort by elements + tail if possible and otherwise leave an index in place if var or discard
|
||||||
// let's sort clauses by a safer manner
|
// This is a stable sort. i.e. matching elements amounts will remain in user given order.
|
||||||
// TODO: how shall tails be weighted? Since any clause after will not run
|
sorted_clauses = sorted_clauses
|
||||||
sorted_clauses.sort_by(|clause1, clause2| {
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.sorted_by(|(index1, clause1), (index2, clause2)| {
|
||||||
let clause1_len = match &clause1.pattern {
|
let clause1_len = match &clause1.pattern {
|
||||||
Pattern::List { elements, tail, .. } => {
|
Pattern::List { elements, tail, .. } => {
|
||||||
elements.len() * 3
|
Some(elements.len() + usize::from(tail.is_some()))
|
||||||
+ usize::from(tail.is_some())
|
|
||||||
+ usize::from(clause1.guard.is_some())
|
|
||||||
}
|
}
|
||||||
_ => 10000000,
|
_ if clause1.guard.is_none() => Some(100000),
|
||||||
};
|
_ => None,
|
||||||
let clause2_len = match &clause2.pattern {
|
|
||||||
Pattern::List { elements, tail, .. } => elements.len() + usize::from(tail.is_some()),
|
|
||||||
_ => 10000001,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
clause1_len.cmp(&clause2_len)
|
let clause2_len = match &clause2.pattern {
|
||||||
});
|
Pattern::List { elements, tail, .. } => {
|
||||||
|
Some(elements.len() + usize::from(tail.is_some()))
|
||||||
|
}
|
||||||
|
_ if clause2.guard.is_none() => Some(100001),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(clause1_len) = clause1_len {
|
||||||
|
if let Some(clause2_len) = clause2_len {
|
||||||
|
return clause1_len.cmp(&clause2_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index1.cmp(index2)
|
||||||
|
})
|
||||||
|
.map(|(_, item)| item)
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
let mut elems_len = 0;
|
let mut elems_len = 0;
|
||||||
let mut final_clauses = sorted_clauses.clone();
|
let mut final_clauses = sorted_clauses.clone();
|
||||||
let mut holes_to_fill = vec![];
|
let mut holes_to_fill = vec![];
|
||||||
let mut assign_plug_in_name = None;
|
|
||||||
let mut last_clause_index = 0;
|
let mut last_clause_index = 0;
|
||||||
let mut last_clause_set = false;
|
let mut last_clause_set = false;
|
||||||
|
|
||||||
// If we have a catch all, use that. Otherwise use todo which will result in error
|
// If we have a catch all, use that. Otherwise use todo which will result in error
|
||||||
// TODO: fill in todo label with description
|
// TODO: fill in todo label with description
|
||||||
let plug_in_then = if sorted_clauses[sorted_clauses.len() - 1].guard.is_none() {
|
let plug_in_then = |index: usize, last_clause: &TypedClause| {
|
||||||
match &sorted_clauses[sorted_clauses.len() - 1].pattern {
|
if last_clause.guard.is_none() {
|
||||||
Pattern::Var { name, .. } => {
|
match &last_clause.pattern {
|
||||||
assign_plug_in_name = Some(name);
|
Pattern::Var { .. } | Pattern::Discard { .. } => last_clause.clone().then,
|
||||||
sorted_clauses[sorted_clauses.len() - 1].clone().then
|
|
||||||
}
|
|
||||||
Pattern::Discard { .. } => sorted_clauses[sorted_clauses.len() - 1].clone().then,
|
|
||||||
_ => {
|
_ => {
|
||||||
let tipo = sorted_clauses[sorted_clauses.len() - 1].then.tipo();
|
let tipo = last_clause.then.tipo();
|
||||||
|
|
||||||
TypedExpr::Trace {
|
TypedExpr::Trace {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
tipo: tipo.clone(),
|
tipo: tipo.clone(),
|
||||||
text: Box::new(TypedExpr::String {
|
text: Box::new(TypedExpr::String {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
tipo: crate::builtins::string(),
|
tipo: crate::builtins::string(),
|
||||||
value: "Clause not filled".to_string(),
|
value: format!("Clause hole found for {index} elements."),
|
||||||
}),
|
}),
|
||||||
then: Box::new(TypedExpr::ErrorTerm {
|
then: Box::new(TypedExpr::ErrorTerm {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
|
@ -335,20 +345,29 @@ pub fn rearrange_clauses(clauses: Vec<TypedClause>) -> Vec<TypedClause> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let tipo = sorted_clauses[sorted_clauses.len() - 1].then.tipo();
|
let tipo = last_clause.then.tipo();
|
||||||
|
|
||||||
TypedExpr::Trace {
|
TypedExpr::Trace {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
tipo: tipo.clone(),
|
tipo: tipo.clone(),
|
||||||
text: Box::new(TypedExpr::String {
|
text: Box::new(TypedExpr::String {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
tipo: crate::builtins::string(),
|
tipo: crate::builtins::string(),
|
||||||
value: "Clause not filled".to_string(),
|
value: format!("Clause hole found for {index} elements."),
|
||||||
}),
|
}),
|
||||||
then: Box::new(TypedExpr::ErrorTerm {
|
then: Box::new(TypedExpr::ErrorTerm {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
tipo,
|
tipo,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let last_clause = &sorted_clauses[sorted_clauses.len() - 1];
|
||||||
|
let assign_plug_in_name = if let Pattern::Var { name, .. } = &last_clause.pattern {
|
||||||
|
Some(name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
for (index, clause) in sorted_clauses.iter().enumerate() {
|
for (index, clause) in sorted_clauses.iter().enumerate() {
|
||||||
|
@ -379,7 +398,7 @@ pub fn rearrange_clauses(clauses: Vec<TypedClause>) -> Vec<TypedClause> {
|
||||||
.into(),
|
.into(),
|
||||||
},
|
},
|
||||||
guard: None,
|
guard: None,
|
||||||
then: plug_in_then.clone(),
|
then: plug_in_then(elems_len, last_clause),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TypedClause {
|
TypedClause {
|
||||||
|
@ -390,7 +409,7 @@ pub fn rearrange_clauses(clauses: Vec<TypedClause>) -> Vec<TypedClause> {
|
||||||
tail: None,
|
tail: None,
|
||||||
},
|
},
|
||||||
guard: None,
|
guard: None,
|
||||||
then: plug_in_then.clone(),
|
then: plug_in_then(elems_len, last_clause),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -440,7 +459,7 @@ pub fn rearrange_clauses(clauses: Vec<TypedClause>) -> Vec<TypedClause> {
|
||||||
location: Span::empty(),
|
location: Span::empty(),
|
||||||
},
|
},
|
||||||
guard: None,
|
guard: None,
|
||||||
then: plug_in_then.clone(),
|
then: plug_in_then(index + 1, last_clause),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -461,6 +480,24 @@ pub fn rearrange_clauses(clauses: Vec<TypedClause>) -> Vec<TypedClause> {
|
||||||
final_clauses
|
final_clauses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If the pattern is a list the return the number of elements and if it has a tail
|
||||||
|
/// Otherwise return None
|
||||||
|
pub fn get_list_elements_len_and_tail(
|
||||||
|
pattern: &Pattern<PatternConstructor, Arc<Type>>,
|
||||||
|
) -> Option<(usize, bool)> {
|
||||||
|
if let Pattern::List { elements, tail, .. } = &pattern {
|
||||||
|
Some((elements.len(), tail.is_some()))
|
||||||
|
} else if let Pattern::Assign { pattern, .. } = &pattern {
|
||||||
|
if let Pattern::List { elements, tail, .. } = pattern.as_ref() {
|
||||||
|
Some((elements.len(), tail.is_some()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn list_access_to_uplc(
|
pub fn list_access_to_uplc(
|
||||||
names: &[String],
|
names: &[String],
|
||||||
|
|
Loading…
Reference in New Issue