324 lines
6.6 KiB
Go
324 lines
6.6 KiB
Go
// Copyright 2017 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ast
|
|
|
|
import (
|
|
"github.com/open-policy-agent/opa/types"
|
|
"github.com/open-policy-agent/opa/util"
|
|
)
|
|
|
|
// TypeEnv contains type info for static analysis such as type checking.
|
|
type TypeEnv struct {
|
|
tree *typeTreeNode
|
|
next *TypeEnv
|
|
}
|
|
|
|
// NewTypeEnv returns an empty TypeEnv.
|
|
func NewTypeEnv() *TypeEnv {
|
|
return &TypeEnv{
|
|
tree: newTypeTree(),
|
|
}
|
|
}
|
|
|
|
// Get returns the type of x.
|
|
func (env *TypeEnv) Get(x interface{}) types.Type {
|
|
|
|
if term, ok := x.(*Term); ok {
|
|
x = term.Value
|
|
}
|
|
|
|
switch x := x.(type) {
|
|
|
|
// Scalars.
|
|
case Null:
|
|
return types.NewNull()
|
|
case Boolean:
|
|
return types.NewBoolean()
|
|
case Number:
|
|
return types.NewNumber()
|
|
case String:
|
|
return types.NewString()
|
|
|
|
// Composites.
|
|
case Array:
|
|
static := make([]types.Type, len(x))
|
|
for i := range static {
|
|
tpe := env.Get(x[i].Value)
|
|
static[i] = tpe
|
|
}
|
|
|
|
var dynamic types.Type
|
|
if len(static) == 0 {
|
|
dynamic = types.A
|
|
}
|
|
|
|
return types.NewArray(static, dynamic)
|
|
|
|
case Object:
|
|
static := []*types.StaticProperty{}
|
|
var dynamic *types.DynamicProperty
|
|
|
|
x.Foreach(func(k, v *Term) {
|
|
if IsConstant(k.Value) {
|
|
kjson, err := JSON(k.Value)
|
|
if err == nil {
|
|
tpe := env.Get(v)
|
|
static = append(static, types.NewStaticProperty(kjson, tpe))
|
|
return
|
|
}
|
|
}
|
|
// Can't handle it as a static property, fallback to dynamic
|
|
typeK := env.Get(k.Value)
|
|
typeV := env.Get(v.Value)
|
|
dynamic = types.NewDynamicProperty(typeK, typeV)
|
|
})
|
|
|
|
if len(static) == 0 && dynamic == nil {
|
|
dynamic = types.NewDynamicProperty(types.A, types.A)
|
|
}
|
|
|
|
return types.NewObject(static, dynamic)
|
|
|
|
case Set:
|
|
var tpe types.Type
|
|
x.Foreach(func(elem *Term) {
|
|
other := env.Get(elem.Value)
|
|
tpe = types.Or(tpe, other)
|
|
})
|
|
if tpe == nil {
|
|
tpe = types.A
|
|
}
|
|
return types.NewSet(tpe)
|
|
|
|
// Comprehensions.
|
|
case *ArrayComprehension:
|
|
checker := newTypeChecker()
|
|
cpy, errs := checker.CheckBody(env, x.Body)
|
|
if len(errs) == 0 {
|
|
return types.NewArray(nil, cpy.Get(x.Term))
|
|
}
|
|
return nil
|
|
case *ObjectComprehension:
|
|
checker := newTypeChecker()
|
|
cpy, errs := checker.CheckBody(env, x.Body)
|
|
if len(errs) == 0 {
|
|
return types.NewObject(nil, types.NewDynamicProperty(cpy.Get(x.Key), cpy.Get(x.Value)))
|
|
}
|
|
return nil
|
|
case *SetComprehension:
|
|
checker := newTypeChecker()
|
|
cpy, errs := checker.CheckBody(env, x.Body)
|
|
if len(errs) == 0 {
|
|
return types.NewSet(cpy.Get(x.Term))
|
|
}
|
|
return nil
|
|
|
|
// Refs.
|
|
case Ref:
|
|
return env.getRef(x)
|
|
|
|
// Vars.
|
|
case Var:
|
|
if node := env.tree.Child(x); node != nil {
|
|
return node.Value()
|
|
}
|
|
if env.next != nil {
|
|
return env.next.Get(x)
|
|
}
|
|
return nil
|
|
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
func (env *TypeEnv) getRef(ref Ref) types.Type {
|
|
|
|
node := env.tree.Child(ref[0].Value)
|
|
if node == nil {
|
|
return env.getRefFallback(ref)
|
|
}
|
|
|
|
return env.getRefRec(node, ref, ref[1:])
|
|
}
|
|
|
|
func (env *TypeEnv) getRefFallback(ref Ref) types.Type {
|
|
|
|
if env.next != nil {
|
|
return env.next.Get(ref)
|
|
}
|
|
|
|
if RootDocumentNames.Contains(ref[0]) {
|
|
return types.A
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (env *TypeEnv) getRefRec(node *typeTreeNode, ref, tail Ref) types.Type {
|
|
if len(tail) == 0 {
|
|
return env.getRefRecExtent(node)
|
|
}
|
|
|
|
if node.Leaf() {
|
|
return selectRef(node.Value(), tail)
|
|
}
|
|
|
|
if !IsConstant(tail[0].Value) {
|
|
return selectRef(env.getRefRecExtent(node), tail)
|
|
}
|
|
|
|
child := node.Child(tail[0].Value)
|
|
if child == nil {
|
|
return env.getRefFallback(ref)
|
|
}
|
|
|
|
return env.getRefRec(child, ref, tail[1:])
|
|
}
|
|
|
|
func (env *TypeEnv) getRefRecExtent(node *typeTreeNode) types.Type {
|
|
|
|
if node.Leaf() {
|
|
return node.Value()
|
|
}
|
|
|
|
children := []*types.StaticProperty{}
|
|
|
|
node.Children().Iter(func(k, v util.T) bool {
|
|
key := k.(Value)
|
|
child := v.(*typeTreeNode)
|
|
|
|
tpe := env.getRefRecExtent(child)
|
|
// TODO(tsandall): handle non-string keys?
|
|
if s, ok := key.(String); ok {
|
|
children = append(children, types.NewStaticProperty(string(s), tpe))
|
|
}
|
|
return false
|
|
})
|
|
|
|
// TODO(tsandall): for now, these objects can have any dynamic properties
|
|
// because we don't have schema for base docs. Once schemas are supported
|
|
// we can improve this.
|
|
return types.NewObject(children, types.NewDynamicProperty(types.S, types.A))
|
|
}
|
|
|
|
func (env *TypeEnv) wrap() *TypeEnv {
|
|
cpy := *env
|
|
cpy.next = env
|
|
cpy.tree = newTypeTree()
|
|
return &cpy
|
|
}
|
|
|
|
// typeTreeNode is used to store type information in a tree.
|
|
type typeTreeNode struct {
|
|
key Value
|
|
value types.Type
|
|
children *util.HashMap
|
|
}
|
|
|
|
func newTypeTree() *typeTreeNode {
|
|
return &typeTreeNode{
|
|
key: nil,
|
|
value: nil,
|
|
children: util.NewHashMap(valueEq, valueHash),
|
|
}
|
|
}
|
|
|
|
func (n *typeTreeNode) Child(key Value) *typeTreeNode {
|
|
value, ok := n.children.Get(key)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return value.(*typeTreeNode)
|
|
}
|
|
|
|
func (n *typeTreeNode) Children() *util.HashMap {
|
|
return n.children
|
|
}
|
|
|
|
func (n *typeTreeNode) Get(path Ref) types.Type {
|
|
curr := n
|
|
for _, term := range path {
|
|
child, ok := curr.children.Get(term.Value)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
curr = child.(*typeTreeNode)
|
|
}
|
|
return curr.Value()
|
|
}
|
|
|
|
func (n *typeTreeNode) Leaf() bool {
|
|
return n.value != nil
|
|
}
|
|
|
|
func (n *typeTreeNode) PutOne(key Value, tpe types.Type) {
|
|
c, ok := n.children.Get(key)
|
|
|
|
var child *typeTreeNode
|
|
if !ok {
|
|
child = newTypeTree()
|
|
child.key = key
|
|
n.children.Put(key, child)
|
|
} else {
|
|
child = c.(*typeTreeNode)
|
|
}
|
|
|
|
child.value = tpe
|
|
}
|
|
|
|
func (n *typeTreeNode) Put(path Ref, tpe types.Type) {
|
|
curr := n
|
|
for _, term := range path {
|
|
c, ok := curr.children.Get(term.Value)
|
|
|
|
var child *typeTreeNode
|
|
if !ok {
|
|
child = newTypeTree()
|
|
child.key = term.Value
|
|
curr.children.Put(child.key, child)
|
|
} else {
|
|
child = c.(*typeTreeNode)
|
|
}
|
|
|
|
curr = child
|
|
}
|
|
curr.value = tpe
|
|
}
|
|
|
|
func (n *typeTreeNode) Value() types.Type {
|
|
return n.value
|
|
}
|
|
|
|
// selectConstant returns the attribute of the type referred to by the term. If
|
|
// the attribute type cannot be determined, nil is returned.
|
|
func selectConstant(tpe types.Type, term *Term) types.Type {
|
|
x, err := JSON(term.Value)
|
|
if err == nil {
|
|
return types.Select(tpe, x)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// selectRef returns the type of the nested attribute referred to by ref. If
|
|
// the attribute type cannot be determined, nil is returned. If the ref
|
|
// contains vars or refs, then the returned type will be a union of the
|
|
// possible types.
|
|
func selectRef(tpe types.Type, ref Ref) types.Type {
|
|
|
|
if tpe == nil || len(ref) == 0 {
|
|
return tpe
|
|
}
|
|
|
|
head, tail := ref[0], ref[1:]
|
|
|
|
switch head.Value.(type) {
|
|
case Var, Ref, Array, Object, Set:
|
|
return selectRef(types.Values(tpe), tail)
|
|
default:
|
|
return selectRef(selectConstant(tpe, head), tail)
|
|
}
|
|
}
|