It's because you are doing things in the language that you have admitted are outside of your comfort zone. That's one way to learn...
Correct, because iterators do not have a push
method. Perhaps you want to chain
a second iterator onto the first?
Looking over the actual code, I think you just want to push to the Vec
that you got the iterator from. You may not need the iterator at all. But what you are asking the compiler to do is something that it has trouble with: analysis of lifetimes with conditional control flow across functions.
The best solution I've found so far (not sure if it works, but it does type-check) is extracting the "get a mutable reference to the vector of properties" into its own function so that the reference can be reused in two places, and also move the "push a new property" to the caller:
diff --git a/native/mishka_parser_nif/src/parser/ast.rs b/native/mishka_parser_nif/src/parser/ast.rs
index 5446968..a097068 100644
--- a/native/mishka_parser_nif/src/parser/ast.rs
+++ b/native/mishka_parser_nif/src/parser/ast.rs
@@ -34,10 +34,10 @@ pub fn source_to_ast<'a>(file_path: &str, allocator: &'a Allocator) -> Result<Pr
Ok(parse_result.program)
}
-pub fn is_module_imported_from_ast<'a>(
+pub fn is_module_imported_from_ast(
file_path: &str,
module_name: &str,
- allocator: &'a Allocator,
+ allocator: &Allocator,
) -> Result<bool, String> {
let program = source_to_ast(file_path, allocator)?;
@@ -52,10 +52,10 @@ pub fn is_module_imported_from_ast<'a>(
Ok(false)
}
-pub fn insert_import_to_ast<'a>(
+pub fn insert_import_to_ast(
file_path: &str,
import_lines: &str,
- allocator: &'a Allocator,
+ allocator: &Allocator,
) -> Result<String, String> {
let mut program = source_to_ast(file_path, allocator)?;
@@ -167,45 +167,33 @@ pub fn find_live_socket_node_from_ast<'a>(program: &'a Program<'a>) -> Result<bo
}
#[allow(dead_code)]
-pub fn extend_hook_object_to_ast<'a>(
- file_path: &str,
- allocator: &'a Allocator,
-) -> Result<String, String> {
+pub fn extend_hook_object_to_ast(file_path: &str, allocator: &Allocator) -> Result<String, String> {
let mut program = source_to_ast(file_path, allocator)?;
- let hooks_result = find_hooks_property(&mut program, allocator)?;
- if let Some(hooks_property) = hooks_result {
+ if !has_live_socket(&program) {
+ return Err("liveSocket not found.".to_string());
+ }
+
+ let maybe_properties = get_properties(&mut program.body);
+ if let Some(properties) = maybe_properties {
+ let hooks_property = match find_hooks_property(properties) {
+ Some(prop) => prop,
+ None => {
+ let new_hooks_property = create_hooks(allocator);
+
+ properties.push(new_hooks_property);
+
+ get_property_by_key(properties.last_mut().unwrap(), "hooks").unwrap()
+ }
+ };
+
if let Expression::ObjectExpression(obj_expr) = hooks_property {
- let new_property = ObjectPropertyKind::ObjectProperty(OXCBox::new_in(
- ObjectProperty {
- span: Span::default(),
- kind: PropertyKind::Init,
- key: PropertyKey::StaticIdentifier(OXCBox::new_in(
- IdentifierName {
- span: Span::default(),
- name: Atom::from("OXCTestHook"),
- },
- allocator,
- )),
- value: Expression::Identifier(OXCBox::new_in(
- IdentifierReference {
- span: Span::default(),
- name: Atom::from("OXCTestHook"),
- reference_id: Cell::new(None),
- },
- allocator,
- )),
- method: false,
- shorthand: true,
- computed: false,
- },
- allocator,
- ));
+ let new_property = create_test_hook(allocator);
obj_expr.properties.push(new_property);
}
} else {
- return Err("liveSocket not found in the AST".to_string());
+ return Err("properties not found in the AST".to_string());
}
let codegen = Codegen::new();
@@ -213,74 +201,39 @@ pub fn extend_hook_object_to_ast<'a>(
Ok(generated_code)
}
-fn find_hooks_property<'short, 'long>(
- program: &'short mut Program<'long>,
- allocator: &'short Allocator,
-) -> Result<Option<&'short mut Expression<'long>>, String> {
- let live_socket_found = program.body.iter_mut().any(|node| {
- if let Statement::VariableDeclaration(var_decl) = node {
- var_decl
- .declarations
- .iter()
- .any(|decl| is_target_variable(decl, "liveSocket"))
- } else {
- false
- }
- });
-
- if !live_socket_found {
- return Err("liveSocket not found.".to_string());
- }
-
- let result = program.body.iter_mut().find_map(|node| {
- let var_decl = match node {
- Statement::VariableDeclaration(var_decl) => var_decl,
- _ => return None,
- };
-
- var_decl.declarations.iter_mut().find_map(|decl| {
- let obj_expr = get_new_expression(decl)?;
-
- obj_expr.arguments.iter_mut().find_map(|arg| {
- let obj_expr_inner = get_object_expression(arg)?;
- let mut iter = obj_expr_inner.properties.iter_mut();
- if let Some(expr) = iter.find_map(|prop| get_property_by_key(prop, "hooks")) {
- return Some(expr);
- }
+fn has_live_socket(program: &Program<'_>) -> bool {
+ program.body.iter().any(|node| match node {
+ Statement::VariableDeclaration(var_decl) => var_decl
+ .declarations
+ .iter()
+ .any(|decl| is_target_variable(decl, "liveSocket")),
+ _ => false,
+ })
+}
- let new_hooks_property = ObjectPropertyKind::ObjectProperty(OXCBox::new_in(
- ObjectProperty {
- span: Span::default(),
- kind: PropertyKind::Init,
- key: PropertyKey::StaticIdentifier(OXCBox::new_in(
- IdentifierName {
- span: Span::default(),
- name: Atom::from("hooks"),
- },
- allocator,
- )),
- value: Expression::ObjectExpression(OXCBox::new_in(
- ObjectExpression {
- span: Span::default(),
- properties: OXCVec::new_in(allocator),
- trailing_comma: None,
- },
- allocator,
- )),
- method: false,
- shorthand: false,
- computed: false,
- },
- allocator,
- ));
-
- iter.push(new_hooks_property)
- // .find_map(|prop| get_property_by_key(prop, "hooks"))
+fn get_properties<'short, 'long>(
+ body: &'short mut OXCVec<'long, Statement<'long>>,
+) -> Option<&'short mut OXCVec<'long, ObjectPropertyKind<'long>>> {
+ body.iter_mut().find_map(|node| match node {
+ Statement::VariableDeclaration(var_decl) => {
+ var_decl.declarations.iter_mut().find_map(|decl| {
+ let obj_expr = get_new_expression(decl)?;
+ obj_expr.arguments.iter_mut().find_map(|arg| {
+ let obj_expr_inner = get_object_expression(arg)?;
+ Some(&mut obj_expr_inner.properties)
+ })
})
- })
- });
+ }
+ _ => None,
+ })
+}
- Ok(result)
+fn find_hooks_property<'short, 'long>(
+ properties: &'short mut OXCVec<'long, ObjectPropertyKind<'long>>,
+) -> Option<&'short mut Expression<'long>> {
+ properties
+ .iter_mut()
+ .find_map(|prop| get_property_by_key(prop, "hooks"))
}
fn is_target_variable(decl: &VariableDeclarator, name: &str) -> bool {
@@ -323,6 +276,62 @@ fn get_property_by_key<'short, 'long>(
}
}
+fn create_test_hook(allocator: &Allocator) -> ObjectPropertyKind {
+ ObjectPropertyKind::ObjectProperty(OXCBox::new_in(
+ ObjectProperty {
+ span: Span::default(),
+ kind: PropertyKind::Init,
+ key: PropertyKey::StaticIdentifier(OXCBox::new_in(
+ IdentifierName {
+ span: Span::default(),
+ name: Atom::from("OXCTestHook"),
+ },
+ allocator,
+ )),
+ value: Expression::Identifier(OXCBox::new_in(
+ IdentifierReference {
+ span: Span::default(),
+ name: Atom::from("OXCTestHook"),
+ reference_id: Cell::new(None),
+ },
+ allocator,
+ )),
+ method: false,
+ shorthand: true,
+ computed: false,
+ },
+ allocator,
+ ))
+}
+
+fn create_hooks(allocator: &Allocator) -> ObjectPropertyKind {
+ ObjectPropertyKind::ObjectProperty(OXCBox::new_in(
+ ObjectProperty {
+ span: Span::default(),
+ kind: PropertyKind::Init,
+ key: PropertyKey::StaticIdentifier(OXCBox::new_in(
+ IdentifierName {
+ span: Span::default(),
+ name: Atom::from("hooks"),
+ },
+ allocator,
+ )),
+ value: Expression::ObjectExpression(OXCBox::new_in(
+ ObjectExpression {
+ span: Span::default(),
+ properties: OXCVec::new_in(allocator),
+ trailing_comma: None,
+ },
+ allocator,
+ )),
+ method: false,
+ shorthand: false,
+ computed: false,
+ },
+ allocator,
+ ))
+}
+
// TODO: delete an object from hook
#[cfg(test)]
mod tests {
Bare with me, this is a long diff. (Sorry, I kind of rewrote half of it, But there are good reasons.)
First, I took the liberty to remove the unnecessary parts (eliding lifetimes, removing the allocator reference now that find_hooks_property()
no longer allocates, etc). But I did not fix all of the lints. Run cargo clippy
to find those.
Secondly, I inlined the problematic control flow part into the caller. Because extend_hook_object_to_ast()
doesn't need to return a reference at all, there are no borrow errors of the "Problem case #3" flavor.
By inlining the push lower in the stack, we can get away with dropping the borrow returned by find_hooks_property()
and create a new one after the push
has completed. NLL is able to see all of the borrows since we're no longer doing conditional control flow across a function call.
It uses a few unwrap
s, but this is fine because these are guaranteed to be populated because of the push()
.
To bring it all together, I split your find_hooks_property()
into three distinct functions. I also moved the creation of new objects into their own functions to clean up the call sites. Now extend_hook_object_to_ast()
is super simple:
- Calls
has_live_socket()
to return an error if needed.
- Calls
get_properties()
to walk the AST.
- Calls
find_hooks_property()
to get a reference if one exists, or else a new "hooks" property is created and pushed to the properties
reference received on step 2.
- Finally, create the "test hook" if the returned expression is an
ObjectExpression
.
This is my best interpretation of what was originally wanted. It's crucial that the find-or-push operation is done in multiple stages so that the compiler can reason about the references.
BTW, you might be trying to do things a bit too cleverly. Pushing to a vector while holding a reference to it will never work. The allocation may need to be resized, which could in turn move everything to a new location in memory. The borrowing errors prevent that safety hazard.