Skip to content

Commit 0431567

Browse files
authored
General refs in rule heads (#5913)
* Adding support for multiple variables at arbitrary locations in rule refs * Updating type-checker to handle general ref heads Fixes: #5993 Fixes: #5994 Signed-off-by: Johan Fylling <[email protected]>
1 parent 9bf5478 commit 0431567

22 files changed

+2648
-166
lines changed

ast/check.go

+36-17
Original file line numberDiff line numberDiff line change
@@ -231,37 +231,30 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
231231

232232
f := types.NewFunction(args, cpy.Get(rule.Head.Value))
233233

234-
// Union with existing.
235-
exist := env.tree.Get(path)
236-
tpe = types.Or(exist, f)
237-
234+
tpe = f
238235
} else {
239236
switch rule.Head.RuleKind() {
240237
case SingleValue:
241238
typeV := cpy.Get(rule.Head.Value)
242-
if last := path[len(path)-1]; !last.IsGround() {
243-
244-
// e.g. store object[string: whatever] at data.p.q.r, not data.p.q.r[x]
239+
if !path.IsGround() {
240+
// e.g. store object[string: whatever] at data.p.q.r, not data.p.q.r[x] or data.p.q.r[x].y[z]
241+
objPath := path.DynamicSuffix()
245242
path = path.GroundPrefix()
246243

247-
typeK := cpy.Get(last)
248-
if typeK != nil && typeV != nil {
249-
exist := env.tree.Get(path)
250-
typeV = types.Or(types.Values(exist), typeV)
251-
typeK = types.Or(types.Keys(exist), typeK)
252-
tpe = types.NewObject(nil, types.NewDynamicProperty(typeK, typeV))
244+
var err error
245+
tpe, err = nestedObject(cpy, objPath, typeV)
246+
if err != nil {
247+
tc.err([]*Error{NewError(TypeErr, rule.Head.Location, err.Error())})
248+
tpe = nil
253249
}
254250
} else {
255251
if typeV != nil {
256-
exist := env.tree.Get(path)
257-
tpe = types.Or(typeV, exist)
252+
tpe = typeV
258253
}
259254
}
260255
case MultiValue:
261256
typeK := cpy.Get(rule.Head.Key)
262257
if typeK != nil {
263-
exist := env.tree.Get(path)
264-
typeK = types.Or(types.Keys(exist), typeK)
265258
tpe = types.NewSet(typeK)
266259
}
267260
}
@@ -272,6 +265,32 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
272265
}
273266
}
274267

268+
// nestedObject creates a nested structure of object types, where each term on path corresponds to a level in the
269+
// nesting. Each term in the path only contributes to the dynamic portion of its corresponding object.
270+
func nestedObject(env *TypeEnv, path Ref, tpe types.Type) (types.Type, error) {
271+
if len(path) == 0 {
272+
return tpe, nil
273+
}
274+
275+
k := path[0]
276+
typeV, err := nestedObject(env, path[1:], tpe)
277+
if err != nil {
278+
return nil, err
279+
}
280+
if typeV == nil {
281+
return nil, nil
282+
}
283+
284+
var dynamicProperty *types.DynamicProperty
285+
typeK := env.Get(k)
286+
if typeK == nil {
287+
return nil, nil
288+
}
289+
dynamicProperty = types.NewDynamicProperty(typeK, typeV)
290+
291+
return types.NewObject(nil, dynamicProperty), nil
292+
}
293+
275294
func (tc *typeChecker) checkExpr(env *TypeEnv, expr *Expr) *Error {
276295
if err := tc.checkExprWith(env, expr, 0); err != nil {
277296
return err

ast/check_test.go

+108
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,16 @@ func TestCheckInferenceRules(t *testing.T) {
358358
{`overlap`, `p.q2.a = input.a { true }`},
359359
{`overlap`, `p.q2[56] = input.a { true }`},
360360
}
361+
ruleset3 := [][2]string{
362+
{`simple`, `p.q[r][s] = 42 { x = ["a", "b"]; r = x[s] }`},
363+
{`mixed`, `p.q[r].s[t] = 42 { x = ["a", "b"]; r = x[t] }`},
364+
{`overrides`, `p.q[r] = "foo" { x = ["a", "b"]; r = x[_] }`},
365+
{`overrides`, `p.q.r[s] = 42 { x = ["a", "b"]; x[s] }`},
366+
{`overrides`, `p.q[r].s = true { x = [true, false]; r = x[_] }`},
367+
{`overrides_static`, `p.q[r].a = "foo" { r = "bar"; s = "baz" }`},
368+
{`overrides_static`, `p.q[r].b = 42 { r = "bar" }`},
369+
{`overrides_static`, `p.q[r].c = true { r = "bar" }`},
370+
}
361371

362372
tests := []struct {
363373
note string
@@ -549,6 +559,104 @@ func TestCheckInferenceRules(t *testing.T) {
549559
types.NewDynamicProperty(types.Any{types.N, types.S}, types.Any{types.B, types.N, types.S}),
550560
),
551561
},
562+
{
563+
note: "general ref-rules, only vars in obj-path, complete obj access",
564+
rules: ruleset3,
565+
ref: "data.simple.p.q",
566+
expected: types.NewObject(
567+
[]*types.StaticProperty{},
568+
types.NewDynamicProperty(types.S,
569+
types.NewObject(
570+
[]*types.StaticProperty{},
571+
types.NewDynamicProperty(types.N, types.N),
572+
),
573+
),
574+
),
575+
},
576+
{
577+
note: "general ref-rules, only vars in obj-path, intermediate obj access",
578+
rules: ruleset3,
579+
ref: "data.simple.p.q.b",
580+
expected: types.NewObject(
581+
[]*types.StaticProperty{},
582+
types.NewDynamicProperty(types.N, types.N),
583+
),
584+
},
585+
{
586+
note: "general ref-rules, only vars in obj-path, leaf access",
587+
rules: ruleset3,
588+
ref: "data.simple.p.q.b[1]",
589+
expected: types.N,
590+
},
591+
{
592+
note: "general ref-rules, vars and constants in obj-path, complete obj access",
593+
rules: ruleset3,
594+
ref: "data.mixed.p.q",
595+
expected: types.NewObject(
596+
[]*types.StaticProperty{},
597+
types.NewDynamicProperty(types.S,
598+
types.NewObject(nil,
599+
types.NewDynamicProperty(types.S, types.NewObject(nil,
600+
types.NewDynamicProperty(types.N, types.N))),
601+
),
602+
),
603+
),
604+
},
605+
{
606+
note: "general ref-rules, key overrides, complete obj access",
607+
rules: ruleset3,
608+
ref: "data.overrides.p.q",
609+
expected: types.NewObject(nil, types.NewDynamicProperty(
610+
types.Or(types.B, types.S),
611+
types.Any{
612+
types.S,
613+
types.NewObject(nil, types.NewDynamicProperty(
614+
types.Any{types.N, types.S},
615+
types.Any{types.B, types.N})),
616+
},
617+
),
618+
),
619+
},
620+
{
621+
note: "general ref-rules, multiple static key overrides, complete obj access",
622+
rules: ruleset3,
623+
ref: "data.overrides_static.p.q",
624+
expected: types.NewObject(
625+
[]*types.StaticProperty{},
626+
types.NewDynamicProperty(types.S,
627+
types.NewObject(
628+
nil,
629+
types.NewDynamicProperty(types.S, types.Any{types.B, types.N, types.S}),
630+
),
631+
),
632+
),
633+
},
634+
{
635+
note: "general ref-rules, multiple static key overrides, intermediate obj access",
636+
rules: ruleset3,
637+
ref: "data.overrides_static.p.q.foo",
638+
expected: types.NewObject(nil,
639+
types.NewDynamicProperty(types.S, types.Any{types.B, types.N, types.S}),
640+
),
641+
},
642+
{
643+
note: "general ref-rules, multiple static key overrides, leaf access (a)",
644+
rules: ruleset3,
645+
ref: "data.overrides_static.p.q.foo.a",
646+
expected: types.Any{types.B, types.N, types.S}, // Dynamically build object types don't have static properties, so even though we "know" the 'a' key has a string value, we've lost this information.
647+
},
648+
{
649+
note: "general ref-rules, multiple static key overrides, leaf access (b)",
650+
rules: ruleset3,
651+
ref: "data.overrides_static.p.q.bar.b",
652+
expected: types.Any{types.B, types.N, types.S},
653+
},
654+
{
655+
note: "general ref-rules, multiple static key overrides, leaf access (c)",
656+
rules: ruleset3,
657+
ref: "data.overrides_static.p.q.baz.c",
658+
expected: types.Any{types.B, types.N, types.S},
659+
},
552660
}
553661

554662
for _, tc := range tests {

0 commit comments

Comments
 (0)