ast: Fix PE regression for future.keywords.not negation inside every (#8781)

Fixing regression where `future.keywords.not` negated expressions nested
inside an `every` body would fail to plug variables for partial
evaluation.

E.g.

```rego
package test
import future.keywords.not

p if {
	x = input.foo
	every y in [1, 2] {
		not f(x, y)
	}
}
```

would fail to plug `x`:

```rego
every __local0__1, __local1__1 in [1, 2] {
	not data.test.f(x, __local1__)
}
x1 = input.foo
```

instead of the expected:

```rego
every __local0__1, __local1__1 in [1, 2] {
	not data.test.f(input.foo, __local1__1)
}
x1 = input.foo
```

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
This commit is contained in:
Johan Fylling
2026-06-15 08:05:39 +02:00
committed by GitHub
parent 966990ccbf
commit 3b8e2d595b
2 changed files with 110 additions and 12 deletions
+18 -12
View File
@@ -4210,18 +4210,7 @@ func (e *evalEvery) save(iter unifyIterator) error {
func (e *evalEvery) plug(expr *ast.Expr) *ast.Expr {
cpy := expr.Copy()
every := cpy.Terms.(*ast.Every)
for i := range every.Body {
switch t := every.Body[i].Terms.(type) {
case *ast.Term:
every.Body[i].Terms = e.e.bindings.PlugNamespaced(t, e.e.caller.bindings)
case []*ast.Term:
for j := 1; j < len(t); j++ { // don't plug operator, t[0]
t[j] = e.e.bindings.PlugNamespaced(t[j], e.e.caller.bindings)
}
case *ast.Every:
every.Body[i] = e.plug(every.Body[i])
}
}
e.plugBody(every.Body)
every.Key = e.e.bindings.PlugNamespaced(every.Key, e.e.caller.bindings)
every.Value = e.e.bindings.PlugNamespaced(every.Value, e.e.caller.bindings)
@@ -4230,6 +4219,23 @@ func (e *evalEvery) plug(expr *ast.Expr) *ast.Expr {
return cpy
}
func (e *evalEvery) plugBody(body ast.Body) {
for i := range body {
switch t := body[i].Terms.(type) {
case *ast.Term:
body[i].Terms = e.e.bindings.PlugNamespaced(t, e.e.caller.bindings)
case []*ast.Term:
for j := 1; j < len(t); j++ { // don't plug operator, t[0]
t[j] = e.e.bindings.PlugNamespaced(t[j], e.e.caller.bindings)
}
case *ast.Every:
body[i] = e.plug(body[i])
case *ast.Not:
e.plugBody(t.Body)
}
}
}
type evalNot struct {
e *eval
not *ast.Not
+92
View File
@@ -5898,6 +5898,98 @@ func TestTopDownPartialEvalNegation(t *testing.T) {
),
},
},
// Nesting inside every-body
{
note: "every+negation: implicit body, concrete-bound outer var",
notBodyOnly: true,
query: "data.test.p",
modules: []string{`package test
p if {
z = 42
every y in input.xs {
not f(z, y)
}
}
f(_, _) := true`},
wantQueries: []string{
`every __local0__1, __local1__1 in input.xs {
not data.test.f(42, __local1__1)
}`,
},
},
{
note: "every+negation: implicit body, unknown-bound outer var",
query: "data.test.p",
modules: []string{`package test
p if {
x = input.foo
every y in [1, 2] {
not f(x, y)
}
}
f(_, _) := false`},
wantQueries: []string{
`every __local0__1, __local1__1 in [1, 2] {
not data.test.f(input.foo, __local1__1)
}
x1 = input.foo`,
},
},
{
note: "every+negation: explicit body, concrete-bound outer var",
notBodyOnly: true,
query: "data.test.p",
modules: []string{`package test
p if {
z = 42
every y in input.xs {
not { f(z, y) }
}
}
f(_, _) := true`},
wantQueries: []string{
`every __local0__1, __local1__1 in input.xs {
not { data.test.f(42, __local1__1) }
}`,
},
},
{
note: "every+negation: explicit body, unknown-bound outer var",
notBodyOnly: true,
query: "data.test.p",
modules: []string{`package test
p if {
x = input.foo
every y in [1, 2] {
not { f(x, y) }
}
}
f(_, _) := false`},
wantQueries: []string{
`every __local0__1, __local1__1 in [1, 2] {
not { data.test.f(input.foo, __local1__1) }
}
x1 = input.foo`,
},
},
{
note: "every+negation: implicit body, comparison expression",
query: "data.test.p",
modules: []string{`package test
p if {
v = input.threshold
every y in [1, 2] {
not v == y
}
}`},
wantQueries: []string{
`every __local0__1, __local1__1 in [1, 2] {
not input.threshold = __local1__1
}
v1 = input.threshold`,
},
},
}
ctx := t.Context()