diff options
author | Vincent Ambo <mail@tazj.in> | 2022-09-05T02·12+0300 |
---|---|---|
committer | tazjin <tazjin@tvl.su> | 2022-09-11T10·32+0000 |
commit | f86327beac05de96e01ede78a3cfd4d57e8319a5 (patch) | |
tree | 912631720e44fdef955eaed1bec38c21f8656b86 /tvix/eval | |
parent | 41b6586ee155a7712e8856b8a03aac41d602d8ba (diff) |
feat(tvix/eval): implement "formals" function parameters r/4790
The comment explains how this works fairly well. Note that this does not yet have the ability to check "closed formals", i.e. without an ellipsis Tvix will *NOT* fail if unexpected attribute set keys are provided. Change-Id: I0d2b77e893243093d2789baa57f876d35d0a32ff Reviewed-on: https://cl.tvl.fyi/c/depot/+/6463 Reviewed-by: sterni <sternenseemann@systemli.org> Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/eval')
-rw-r--r-- | tvix/eval/src/compiler/mod.rs | 98 | ||||
-rw-r--r-- | tvix/eval/src/warnings.rs | 4 |
2 files changed, 101 insertions, 1 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs index 314ae86d43e9..3fd43db53c93 100644 --- a/tvix/eval/src/compiler/mod.rs +++ b/tvix/eval/src/compiler/mod.rs @@ -873,6 +873,101 @@ impl Compiler<'_, '_> { self.end_scope(&node); } + /// Compiles pattern function arguments, such as `{ a, b }: ...`. + /// + /// These patterns are treated as a special case of locals binding + /// where the attribute set itself is placed on the first stack + /// slot of the call frame (either as a phantom, or named in case + /// of an `@` binding), and the function call sets up the rest of + /// the stack as if the parameters were rewritten into a `let` + /// binding. + /// + /// For example: + /// + /// ```nix + /// ({ a, b ? 2, c ? a * b, ... }@args: <body>) { a = 10; } + /// ``` + /// + /// would be compiled similarly to a binding such as + /// + /// ```nix + /// let args = { a = 10; }; + /// in let a = args.a; + /// b = args.a or 2; + /// c = args.c or a * b; + /// in <body> + /// ``` + /// + /// The only tricky bit being that bindings have to fail if too + /// many arguments are provided. This is done by emitting a + /// special instruction that checks the set of keys from a + /// constant containing the expected keys. + fn compile_param_pattern(&mut self, pattern: ast::Pattern) { + let span = self.span_for(&pattern); + let set_idx = match pattern.pat_bind() { + Some(name) => self.declare_local(&name, name.ident().unwrap().to_string()), + None => self.scope_mut().declare_phantom(span), + }; + + // At call time, the attribute set is already at the top of + // the stack. + self.scope_mut().mark_initialised(set_idx); + self.emit_force(&pattern); + + // Similar to `let ... in ...`, we now do multiple passes over + // the bindings to first declare them, then populate them, and + // then finalise any necessary recursion into the scope. + let mut entries: Vec<(LocalIdx, ast::PatEntry)> = vec![]; + let mut indices: Vec<LocalIdx> = vec![]; + + for entry in pattern.pat_entries() { + let ident = entry.ident().unwrap(); + let idx = self.declare_local(&ident, ident.to_string()); + entries.push((idx, entry)); + indices.push(idx); + } + + // For each of the bindings, push the set on the stack and + // attempt to select from it. + let stack_idx = self.scope().stack_index(set_idx); + for (idx, entry) in entries.into_iter() { + self.push_op(OpCode::OpGetLocal(stack_idx), &pattern); + self.emit_literal_ident(&entry.ident().unwrap()); + + // Use the same mechanism as `compile_select_or` if a + // default value was provided, or simply select otherwise. + if let Some(default_expr) = entry.default() { + self.push_op(OpCode::OpAttrsTrySelect, &entry.ident().unwrap()); + + let jump_to_default = + self.push_op(OpCode::OpJumpIfNotFound(JumpOffset(0)), &default_expr); + + let jump_over_default = self.push_op(OpCode::OpJump(JumpOffset(0)), &default_expr); + + self.patch_jump(jump_to_default); + self.compile(idx, default_expr); + self.patch_jump(jump_over_default); + } else { + self.push_op(OpCode::OpAttrsSelect, &entry.ident().unwrap()); + } + + self.scope_mut().mark_initialised(idx); + } + + for idx in indices { + if self.scope()[idx].needs_finaliser { + let stack_idx = self.scope().stack_index(idx); + self.push_op(OpCode::OpFinalise(stack_idx), &pattern); + } + } + + // TODO: strictly check if all keys have been consumed if + // there is no ellipsis. + if pattern.ellipsis_token().is_none() { + self.emit_warning(span, WarningKind::NotImplemented("closed formals")); + } + } + fn compile_lambda(&mut self, outer_slot: LocalIdx, node: ast::Lambda) { self.new_context(); let span = self.span_for(&node); @@ -881,7 +976,8 @@ impl Compiler<'_, '_> { // Compile the function itself match node.param().unwrap() { - ast::Param::Pattern(_) => todo!("formals function definitions"), + ast::Param::Pattern(pat) => self.compile_param_pattern(pat), + ast::Param::IdentParam(param) => { let name = param .ident() diff --git a/tvix/eval/src/warnings.rs b/tvix/eval/src/warnings.rs index faf786d848d3..3bae2554cff1 100644 --- a/tvix/eval/src/warnings.rs +++ b/tvix/eval/src/warnings.rs @@ -7,6 +7,10 @@ pub enum WarningKind { UselessInherit, UnusedBinding, ShadowedGlobal(&'static str), + + /// Tvix internal warning for features triggered by users that are + /// not actually implemented yet. + NotImplemented(&'static str), } #[derive(Debug)] |