-
Notifications
You must be signed in to change notification settings - Fork 117
fix(type system): Fix function closures not correctly updating TypeState
#1399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fac788a
94bde99
f785ceb
9ad6d11
1d77fc4
5296ad7
6105d06
0f01b67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Function closures now correctly update type state of the program. | ||
|
|
||
| authors: zettroke |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -500,30 +500,38 @@ impl<'a> Builder<'a> { | |
| state: &mut TypeState, | ||
| ) -> Result<(Option<Closure>, bool), FunctionCallError> { | ||
| // Check if we have a closure we need to compile. | ||
| if let Some((variables, input)) = self.closure.clone() { | ||
| if let Some((variables, input)) = &self.closure { | ||
| // TODO: This assumes the closure will run exactly once, which is incorrect. | ||
| // see: https://github.com/vectordotdev/vector/issues/13782 | ||
|
|
||
| let block = closure_block.expect("closure must contain block"); | ||
|
|
||
| let mut variables_types = vec![]; | ||
| // At this point, we've compiled the block, so we can remove the | ||
| // closure variables from the compiler's local environment. | ||
| for ident in &variables { | ||
| match locals.remove_variable(ident) { | ||
| Some(details) => state.local.insert_variable(ident.clone(), details), | ||
| None => { | ||
| state.local.remove_variable(ident); | ||
| } | ||
| for ident in variables { | ||
| let variable_details = state | ||
| .local | ||
| .remove_variable(ident) | ||
| .expect("Closure variable must be present"); | ||
| variables_types.push(variable_details); | ||
|
|
||
| // If outer scope has this variable, restore its state | ||
| if let Some(details) = locals.remove_variable(ident) { | ||
| state.local.insert_variable(ident.clone(), details); | ||
| } | ||
| } | ||
|
|
||
| let (block_span, (block, block_type_def)) = block.take(); | ||
| let (block_span, (block, block_type_def)) = closure_block | ||
| .ok_or(FunctionCallError::MissingClosure { | ||
| call_span: Span::default(), // TODO can we provide a better span? | ||
| example: None, | ||
| })? | ||
| .take(); | ||
|
|
||
| let closure_fallible = block_type_def.is_fallible(); | ||
|
|
||
| // Check the type definition of the resulting block.This needs to match | ||
| // whatever is configured by the closure input type. | ||
| let expected_kind = input.output.into_kind(); | ||
| let expected_kind = input.clone().output.into_kind(); | ||
| let found_kind = block_type_def | ||
| .kind() | ||
| .union(block_type_def.returns().clone()); | ||
|
|
@@ -536,7 +544,7 @@ impl<'a> Builder<'a> { | |
| }); | ||
| } | ||
|
|
||
| let fnclosure = Closure::new(variables, block, block_type_def); | ||
| let fnclosure = Closure::new(variables.clone(), variables_types, block, block_type_def); | ||
| self.list.set_closure(fnclosure.clone()); | ||
|
|
||
| // closure = Some(fnclosure); | ||
|
|
@@ -699,6 +707,28 @@ impl Expression for FunctionCall { | |
|
|
||
| let mut expr_result = self.expr.apply_type_info(&mut state); | ||
|
|
||
| // Closure can change state of locals in our `state`, so we need to update it. | ||
| if let Some(closure) = &self.closure { | ||
| // To get correct `type_info()` from closure we need to add closure arguments into current state | ||
| let mut closure_state = state.clone(); | ||
| dbg!(&closure.variables_types); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Useful? React with 👍 / 👎. |
||
| for (ident, details) in closure | ||
| .variables | ||
| .iter() | ||
| .cloned() | ||
| .zip(closure.variables_types.iter().cloned()) | ||
| { | ||
| closure_state.local.insert_variable(ident, details); | ||
| } | ||
| let mut closure_info = closure.block.type_info(&closure_state); | ||
| // Interaction with closure arguments can't affect parent state, so remove them before merge | ||
| for ident in &closure.variables { | ||
| closure_info.state.local.remove_variable(ident); | ||
| } | ||
|
|
||
| state = state.merge(closure_info.state); | ||
| } | ||
|
|
||
| // If one of the arguments only partially matches the function type | ||
| // definition, then we mark the entire function as fallible. | ||
| // | ||
|
|
@@ -1247,9 +1277,10 @@ impl DiagnosticMessage for FunctionCallError { | |
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use crate::compiler::{Category, FunctionExpression, value::kind}; | ||
|
|
||
| use super::*; | ||
| use crate::compiler::{Category, Compiler, FunctionExpression, value::kind}; | ||
| use crate::parser::parse; | ||
| use crate::stdlib::ForEach; | ||
|
|
||
| #[derive(Clone, Debug)] | ||
| struct Fn; | ||
|
|
@@ -1436,4 +1467,27 @@ mod tests { | |
|
|
||
| assert_eq!(Ok(expected), params); | ||
| } | ||
|
|
||
| #[test] | ||
| fn closure_type_state() { | ||
| let program = parse( | ||
| r#" | ||
| v = "" | ||
|
|
||
| for_each({}) -> |key, value| { | ||
| v = 0 | ||
| } | ||
| "#, | ||
| ) | ||
| .unwrap(); | ||
|
|
||
| let fns = vec![Box::new(ForEach) as Box<dyn Function>]; | ||
| let mut compiler = Compiler::new(&fns, CompileConfig::default()); | ||
|
|
||
| let mut state = TypeState::default(); | ||
| compiler.compile_root_exprs(program, &mut state); | ||
| let var = state.local.variable(&Ident::new("v")).unwrap(); | ||
|
|
||
| assert_eq!(var.type_def.kind(), &Kind::bytes().or_integer()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
expectintroduces a hard compiler panic when closure parameters are duplicated, instead of returning a normal diagnostic. The parser allows repeated closure identifiers, and_is normalized to an empty identifier, so a closure like|_, _|(common for ignored args) stores one local binding but attempts to remove it twice; the second removal isNoneand this line aborts compilation.Useful? React with 👍 / 👎.