about summary refs log tree commit diff
path: root/tvix
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-08-27T14·16+0300
committertazjin <tazjin@tvl.su>2022-09-04T17·40+0000
commita52db148203824484caa82a23fe88f0a137274ee (patch)
tree9f8d66d36b5e792add5961e99b65dacf268e1cb0 /tvix
parent00aeb6dfaf97c515b155387fc7de27429cf86f74 (diff)
feat(tvix/eval): detect illegally shadowed variables r/4637
Nix does not allow things like `let a = 1; a = 2; in a`, but doing it
across depths is allowed.

Change-Id: I6a259f8b01a254b433b58c736e245c9c764641b6
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6301
Tested-by: BuildkiteCI
Reviewed-by: sterni <sternenseemann@systemli.org>
Diffstat (limited to 'tvix')
-rw-r--r--tvix/eval/src/compiler/mod.rs31
-rw-r--r--tvix/eval/src/errors.rs3
2 files changed, 34 insertions, 0 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs
index 38eaddddc1..eefcbfcd65 100644
--- a/tvix/eval/src/compiler/mod.rs
+++ b/tvix/eval/src/compiler/mod.rs
@@ -54,6 +54,14 @@ impl Depth {
             Depth::At(ours) => *ours > theirs,
         }
     }
+
+    /// Does this variable live below the other given depth?
+    fn below(&self, theirs: usize) -> bool {
+        match self {
+            Depth::Unitialised => false,
+            Depth::At(ours) => *ours < theirs,
+        }
+    }
 }
 
 /// Represents a single local already known to the compiler.
@@ -1013,6 +1021,26 @@ impl Compiler {
             self.scope_mut().poison(global_ident, depth);
         }
 
+        let mut shadowed = false;
+        for local in self.scope().locals.iter().rev() {
+            if local.depth.below(self.scope().scope_depth) {
+                // Shadowing identifiers from higher scopes is allowed.
+                break;
+            }
+
+            if local.name == name {
+                shadowed = true;
+                break;
+            }
+        }
+
+        if shadowed {
+            self.emit_error(
+                node.clone(),
+                ErrorKind::VariableAlreadyDefined(name.clone()),
+            );
+        }
+
         self.scope_mut().locals.push(Local {
             name,
             depth: Depth::At(depth),
@@ -1022,6 +1050,9 @@ impl Compiler {
         });
     }
 
+    /// Declare a local variable that occupies a stack slot and should
+    /// be accounted for, but is not directly accessible by users
+    /// (e.g. attribute sets used for `with`).
     fn declare_phantom(&mut self) {
         let depth = self.scope().scope_depth;
         self.scope_mut().locals.push(Local {
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index 96217cabaf..49a051c16e 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -32,6 +32,9 @@ pub enum ErrorKind {
     // Unknown variable in dynamic scope (with, rec, ...).
     UnknownDynamicVariable(String),
 
+    // User is defining the same variable twice at the same depth.
+    VariableAlreadyDefined(String),
+
     // Attempt to call something that is not callable.
     NotCallable,