planner: Add not-body support to planner (#8458)

Fixes: #8392

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
This commit is contained in:
Johan Fylling
2026-03-31 11:30:22 +02:00
committed by GitHub
parent 670d2e2556
commit c850487e06
6 changed files with 39 additions and 20 deletions
+11 -3
View File
@@ -637,7 +637,7 @@ func (p *Planner) planQuery(q ast.Body, index int, iter planiter) error {
func (p *Planner) planExpr(e *ast.Expr, iter planiter) error {
switch {
case e.Negated:
case e.IsNegated():
return p.planNot(e, iter)
case len(e.With) > 0:
@@ -661,8 +661,16 @@ func (p *Planner) planNot(e *ast.Expr, iter planiter) error {
prev := p.curr
p.curr = not.Block
if err := p.planExpr(e.Complement(), func() error { return nil }); err != nil {
return err
if n, ok := e.Terms.(*ast.Not); ok {
for _, be := range n.Body {
if err := p.planExpr(be, func() error { return nil }); err != nil {
return err
}
}
} else {
if err := p.planExpr(e.Complement(), func() error { return nil }); err != nil {
return err
}
}
p.curr = prev
+19 -1
View File
@@ -66,6 +66,15 @@ func TestPlannerHelloWorld(t *testing.T) {
note: "negation",
queries: []string{"not input.x.y = 1"},
},
{
note: "negation, not-body",
queries: []string{"data.test.p = x"},
modules: []string{`
package test
import future.keywords.not
p if { not input.x.y = 1 }
`},
},
{
note: "not and known vars", // https://github.com/open-policy-agent/opa/issues/3279
queries: []string{`x = "foo"; not data.tenants[x]`},
@@ -396,7 +405,16 @@ q = 2`,
modules := make([]*ast.Module, len(tc.modules))
for i := range modules {
file := fmt.Sprintf("module-%d.rego", i)
opts := ast.ParserOptions{AllFutureKeywords: true}
// TODO: drop once future.keywords.not is enabled by default
caps := ast.CapabilitiesForThisVersion()
caps.FutureKeywords = append(caps.FutureKeywords, "not")
opts := ast.ParserOptions{
AllFutureKeywords: true,
Capabilities: caps,
}
m, err := ast.ParseModuleWithOpts(file, tc.modules[i], opts)
if err != nil {
t.Fatal(err)
@@ -1,11 +1,3 @@
# Exception Format is <test name>: <reason>
"data/toplevel integer": "https://github.com/open-policy-agent/opa/issues/3711"
"data/nested integer": "https://github.com/open-policy-agent/opa/issues/3711"
"negation/not-body: negated call with vars, call succeeds": "No planner support"
"negation/not-body: negated call with vars, call fails": "No planner support"
"negation/not-body: negated call with vars, undefined operands": "No planner support"
"negation/not-body: negated call with vars, inside comprehension, call succeeds": "No planner support"
"negation/not-body: negated call with vars, inside comprehension, undefined operands": "No planner support"
"negation/not-body: negated call with vars, inside every, call succeeds": "No planner support"
"negation/not-body: negated call with vars, inside every, undefined operands": "No planner support"
"negation/not-body: negated and non-negated unification on same var, calls, rearranged": "No planner support"
@@ -74,9 +74,13 @@ func TestWasmE2E(t *testing.T) {
t.Setenv(k, v)
}
caps := ast.CapabilitiesForThisVersion()
caps.FutureKeywords = append(caps.FutureKeywords, "not")
opts := []func(*rego.Rego){
rego.Query(tc.Query),
rego.SetRegoVersion(regoVersion),
rego.Capabilities(caps),
}
for i := range tc.Modules {
opts = append(opts, rego.Module(fmt.Sprintf("module-%d.rego", i), tc.Modules[i]))