From dba0e11ba76c6b8400d688471eb458479c2d8b32 Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Mon, 7 Aug 2023 16:23:28 -0400 Subject: [PATCH] Add other shadowing cases --- crates/aiken-lang/src/gen_uplc/builder.rs | 131 ++++++++++++++-------- 1 file changed, 86 insertions(+), 45 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index 25c83ae5..71d6fea0 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -583,6 +583,63 @@ pub fn erase_opaque_type_operations( } } +/// Determine whether this air_tree node introduces any shadowing over `potential_matches` +pub fn find_introduced_variables(air_tree: &AirTree) -> Vec { + match air_tree { + AirTree::Statement { statement: AirStatement::Let { name, .. }, .. } => vec![name.clone()], + AirTree::Statement { statement: AirStatement::TupleGuard { indices, .. }, .. } | + AirTree::Expression(AirExpression::TupleClause { indices, .. }) => { + indices.iter().map(|(_, name)| name).cloned().collect() + }, + AirTree::Expression(AirExpression::Fn { params, .. }) => { + params.iter().map(|name| name).cloned().collect() + }, + AirTree::Statement { statement: AirStatement::ListAccessor { names, .. }, ..} => { + names.clone() + } + AirTree::Statement { statement: AirStatement::ListExpose { tail, tail_head_names, .. }, .. } => { + let mut ret = vec![]; + if let Some((_, head)) = tail { + ret.push(head.clone()) + } + + for name in tail_head_names.iter().map(|(_, head)| head) { + ret.push(name.clone()); + } + ret + }, + AirTree::Statement { statement: AirStatement::TupleAccessor { names, .. }, .. } => { + names.clone() + }, + AirTree::Statement { statement: AirStatement::FieldsExpose { indices, .. }, ..} => { + indices.iter().map(|(_, name, _)| name).cloned().collect() + } + _ => vec![] + } +} + +/// Determine whether a function is recursive, and if so, get the arguments +pub fn is_recursive_function_call<'a>(air_tree: &'a AirTree, func_key: &FunctionAccessKey, variant: &String) -> (bool, Option<&'a Vec>) { + if let AirTree::Expression(AirExpression::Call { func, args, .. }) = air_tree { + if let AirTree::Expression(AirExpression::Var { + constructor: + ValueConstructor { + variant: ValueConstructorVariant::ModuleFn { name, module, .. }, + .. + }, + variant_name, + .. + }) = func.as_ref() { + if name == &func_key.function_name + && module == &func_key.module_name + && variant == variant_name { + return (true, Some(args)) + } + } + } + return (false, None) +} + pub fn identify_recursive_static_params( air_tree: &mut AirTree, tree_path: &TreePath, @@ -592,54 +649,38 @@ pub fn identify_recursive_static_params( shadowed_parameters: &mut HashMap, potential_recursive_statics: &mut Vec ) { - match air_tree { - AirTree::Statement { statement: AirStatement::Let { name, .. }, .. } => { - if potential_recursive_statics.contains(name) { - shadowed_parameters.insert(name.clone(), tree_path.clone()); - } - }, - AirTree::Expression(AirExpression::Call { func, args, .. }) => { - if let AirTree::Expression(AirExpression::Var { - constructor: - ValueConstructor { - variant: ValueConstructorVariant::ModuleFn { name, module, .. }, - .. - }, - variant_name, - .. - }) = func.as_ref() { - if name == &func_key.function_name - && module == &func_key.module_name - && variant == variant_name - { - for (param, arg) in func_params.iter().zip(args) { - if let Some((idx, _)) = potential_recursive_statics.iter().find_position(|&p| p == param) { - // Check if we pass something different in this recursive call site - // by different, we mean - // - a variable that is bound to a different name - // - a variable with the same name, but that was shadowed in an ancestor scope - // - any other type of expression - let param_is_different = match arg { - AirTree::Expression(AirExpression::Var { name, .. }) => { - // "shadowed in an ancestor scope" means "the definition scope is a prefix of our scope" - name != param || if let Some(p) = shadowed_parameters.get(param) { - p.common_ancestor(tree_path) == *p - } else { - false - } - }, - _ => true - }; - // If so, then we disqualify this parameter from being a recursive static parameter - if param_is_different { - potential_recursive_statics.remove(idx); - } + // Find whether any of the potential recursive statics get shadowed (because even if we pass in the same referenced name, it might not be static) + for introduced_variable in find_introduced_variables(air_tree) { + if potential_recursive_statics.contains(&introduced_variable) { + shadowed_parameters.insert(introduced_variable, tree_path.clone()); + } + } + // Otherwise, if this is a recursive call site, disqualify anything that is different (or the same, but shadowed) + if let (true, Some(args)) = is_recursive_function_call(air_tree, func_key, variant) { + for (param, arg) in func_params.iter().zip(args) { + if let Some((idx, _)) = potential_recursive_statics.iter().find_position(|&p| p == param) { + // Check if we pass something different in this recursive call site + // by different, we mean + // - a variable that is bound to a different name + // - a variable with the same name, but that was shadowed in an ancestor scope + // - any other type of expression + let param_is_different = match arg { + AirTree::Expression(AirExpression::Var { name, .. }) => { + // "shadowed in an ancestor scope" means "the definition scope is a prefix of our scope" + name != param || if let Some(p) = shadowed_parameters.get(param) { + p.common_ancestor(tree_path) == *p + } else { + false } - } + }, + _ => true + }; + // If so, then we disqualify this parameter from being a recursive static parameter + if param_is_different { + potential_recursive_statics.remove(idx); } } - }, - _ => () + } } }