diff --git a/ast/compile.go b/ast/compile.go index 4c268dd15a..c51951c383 100644 --- a/ast/compile.go +++ b/ast/compile.go @@ -618,7 +618,7 @@ func (c *Compiler) buildRuleIndices() { return false } index := newBaseDocEqIndex(func(ref Ref) bool { - return len(c.GetRules(ref.GroundPrefix())) > 0 + return isVirtual(c.RuleTree, ref.GroundPrefix()) }) if rules := extractRules(node.Values); index.Build(rules) { c.ruleIndices.Put(rules[0].Path(), index) @@ -3229,6 +3229,19 @@ func isDataRef(term *Term) bool { return false } +func isVirtual(node *TreeNode, ref Ref) bool { + for i := 0; i < len(ref); i++ { + child := node.Child(ref[i].Value) + if child == nil { + return false + } else if len(child.Values) > 0 { + return true + } + node = child + } + return true +} + func safetyErrorSlice(unsafe unsafeVars) (result Errors) { if len(unsafe) == 0 { diff --git a/ast/compile_test.go b/ast/compile_test.go index 72050d5808..98649278f8 100644 --- a/ast/compile_test.go +++ b/ast/compile_test.go @@ -98,6 +98,10 @@ s[2] { true }`, if user.Hide { t.Fatalf("Expected user.system node to be visible") } + + if !isVirtual(tree, MustParseRef("data.a.b.empty")) { + t.Fatal("Expected data.a.b.empty to be virtual") + } } func TestCompilerEmpty(t *testing.T) { diff --git a/topdown/topdown_test.go b/topdown/topdown_test.go index f41a059262..011fc91245 100644 --- a/topdown/topdown_test.go +++ b/topdown/topdown_test.go @@ -756,6 +756,28 @@ p[x] = y { data.enum_errors.a[x] = y }`, assertTopDownWithPath(t, compiler, store, "enumerate virtual errors", []string{"enum_errors", "caller", "p"}, `{}`, fmt.Errorf("divide by zero")) } +func TestTopDownFix1863(t *testing.T) { + + compiler := ast.MustCompileModules(map[string]string{ + "test1.rego": ` + package a.b + + # this module is empty + `, + "test2.rego": ` + package x + + p = data.a.b # p should be defined (an empty object) + `, + }) + + store := inmem.New() + + assertTopDownWithPath(t, compiler, store, "is defined", []string{}, ``, `{"a": {"b": {}}, "x": {"p": {}}}`) + assertTopDownWithPath(t, compiler, store, "is defined", []string{"x"}, ``, `{"p": {}}`) + assertTopDownWithPath(t, compiler, store, "is defined", []string{"x", "p"}, ``, `{}`) +} + func TestTopDownNestedReferences(t *testing.T) { tests := []struct { note string