summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/cel-go/checker
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/authzed/cel-go/checker')
-rw-r--r--vendor/github.com/authzed/cel-go/checker/BUILD.bazel64
-rw-r--r--vendor/github.com/authzed/cel-go/checker/checker.go696
-rw-r--r--vendor/github.com/authzed/cel-go/checker/cost.go705
-rw-r--r--vendor/github.com/authzed/cel-go/checker/decls/BUILD.bazel19
-rw-r--r--vendor/github.com/authzed/cel-go/checker/decls/decls.go237
-rw-r--r--vendor/github.com/authzed/cel-go/checker/env.go284
-rw-r--r--vendor/github.com/authzed/cel-go/checker/errors.go88
-rw-r--r--vendor/github.com/authzed/cel-go/checker/format.go216
-rw-r--r--vendor/github.com/authzed/cel-go/checker/mapping.go49
-rw-r--r--vendor/github.com/authzed/cel-go/checker/options.go42
-rw-r--r--vendor/github.com/authzed/cel-go/checker/printer.go74
-rw-r--r--vendor/github.com/authzed/cel-go/checker/scopes.go147
-rw-r--r--vendor/github.com/authzed/cel-go/checker/types.go314
13 files changed, 2935 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/cel-go/checker/BUILD.bazel b/vendor/github.com/authzed/cel-go/checker/BUILD.bazel
new file mode 100644
index 0000000..145c3e7
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/BUILD.bazel
@@ -0,0 +1,64 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+package(
+ licenses = ["notice"], # Apache 2.0
+)
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "checker.go",
+ "cost.go",
+ "env.go",
+ "errors.go",
+ "format.go",
+ "mapping.go",
+ "options.go",
+ "printer.go",
+ "scopes.go",
+ "types.go",
+ ],
+ importpath = "github.com/authzed/cel-go/checker",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//checker/decls:go_default_library",
+ "//common:go_default_library",
+ "//common/ast:go_default_library",
+ "//common/containers:go_default_library",
+ "//common/debug:go_default_library",
+ "//common/decls:go_default_library",
+ "//common/operators:go_default_library",
+ "//common/overloads:go_default_library",
+ "//common/stdlib:go_default_library",
+ "//common/types:go_default_library",
+ "//common/types/pb:go_default_library",
+ "//common/types/ref:go_default_library",
+ "//parser:go_default_library",
+ "@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library",
+ "@org_golang_google_protobuf//proto:go_default_library",
+ "@org_golang_google_protobuf//types/known/emptypb:go_default_library",
+ "@org_golang_google_protobuf//types/known/structpb:go_default_library",
+ ],
+)
+
+go_test(
+ name = "go_default_test",
+ size = "small",
+ srcs = [
+ "checker_test.go",
+ "cost_test.go",
+ "env_test.go",
+ "format_test.go",
+ ],
+ embed = [
+ ":go_default_library",
+ ],
+ deps = [
+ "//common/types:go_default_library",
+ "//parser:go_default_library",
+ "//test:go_default_library",
+ "//test/proto2pb:go_default_library",
+ "//test/proto3pb:go_default_library",
+ "@org_golang_google_protobuf//proto:go_default_library",
+ ],
+)
diff --git a/vendor/github.com/authzed/cel-go/checker/checker.go b/vendor/github.com/authzed/cel-go/checker/checker.go
new file mode 100644
index 0000000..09d1e45
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/checker.go
@@ -0,0 +1,696 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package checker defines functions to type-checked a parsed expression
+// against a set of identifier and function declarations.
+package checker
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/authzed/cel-go/common"
+ "github.com/authzed/cel-go/common/ast"
+ "github.com/authzed/cel-go/common/containers"
+ "github.com/authzed/cel-go/common/decls"
+ "github.com/authzed/cel-go/common/operators"
+ "github.com/authzed/cel-go/common/types"
+ "github.com/authzed/cel-go/common/types/ref"
+)
+
+type checker struct {
+ *ast.AST
+ ast.ExprFactory
+ env *Env
+ errors *typeErrors
+ mappings *mapping
+ freeTypeVarCounter int
+}
+
+// Check performs type checking, giving a typed AST.
+//
+// The input is a parsed AST and an env which encapsulates type binding of variables,
+// declarations of built-in functions, descriptions of protocol buffers, and a registry for
+// errors.
+//
+// Returns a type-checked AST, which might not be usable if there are errors in the error
+// registry.
+func Check(parsed *ast.AST, source common.Source, env *Env) (*ast.AST, *common.Errors) {
+ errs := common.NewErrors(source)
+ typeMap := make(map[int64]*types.Type)
+ refMap := make(map[int64]*ast.ReferenceInfo)
+ c := checker{
+ AST: ast.NewCheckedAST(parsed, typeMap, refMap),
+ ExprFactory: ast.NewExprFactory(),
+ env: env,
+ errors: &typeErrors{errs: errs},
+ mappings: newMapping(),
+ freeTypeVarCounter: 0,
+ }
+ c.check(c.Expr())
+
+ // Walk over the final type map substituting any type parameters either by their bound value
+ // or by DYN.
+ for id, t := range c.TypeMap() {
+ c.SetType(id, substitute(c.mappings, t, true))
+ }
+ return c.AST, errs
+}
+
+func (c *checker) check(e ast.Expr) {
+ if e == nil {
+ return
+ }
+ switch e.Kind() {
+ case ast.LiteralKind:
+ literal := ref.Val(e.AsLiteral())
+ switch literal.Type() {
+ case types.BoolType, types.BytesType, types.DoubleType, types.IntType,
+ types.NullType, types.StringType, types.UintType:
+ c.setType(e, literal.Type().(*types.Type))
+ default:
+ c.errors.unexpectedASTType(e.ID(), c.location(e), "literal", literal.Type().TypeName())
+ }
+ case ast.IdentKind:
+ c.checkIdent(e)
+ case ast.SelectKind:
+ c.checkSelect(e)
+ case ast.CallKind:
+ c.checkCall(e)
+ case ast.ListKind:
+ c.checkCreateList(e)
+ case ast.MapKind:
+ c.checkCreateMap(e)
+ case ast.StructKind:
+ c.checkCreateStruct(e)
+ case ast.ComprehensionKind:
+ c.checkComprehension(e)
+ default:
+ c.errors.unexpectedASTType(e.ID(), c.location(e), "unspecified", reflect.TypeOf(e).Name())
+ }
+}
+
+func (c *checker) checkIdent(e ast.Expr) {
+ identName := e.AsIdent()
+ // Check to see if the identifier is declared.
+ if ident := c.env.LookupIdent(identName); ident != nil {
+ c.setType(e, ident.Type())
+ c.setReference(e, ast.NewIdentReference(ident.Name(), ident.Value()))
+ // Overwrite the identifier with its fully qualified name.
+ e.SetKindCase(c.NewIdent(e.ID(), ident.Name()))
+ return
+ }
+
+ c.setType(e, types.ErrorType)
+ c.errors.undeclaredReference(e.ID(), c.location(e), c.env.container.Name(), identName)
+}
+
+func (c *checker) checkSelect(e ast.Expr) {
+ sel := e.AsSelect()
+ // Before traversing down the tree, try to interpret as qualified name.
+ qname, found := containers.ToQualifiedName(e)
+ if found {
+ ident := c.env.LookupIdent(qname)
+ if ident != nil {
+ // We don't check for a TestOnly expression here since the `found` result is
+ // always going to be false for TestOnly expressions.
+
+ // Rewrite the node to be a variable reference to the resolved fully-qualified
+ // variable name.
+ c.setType(e, ident.Type())
+ c.setReference(e, ast.NewIdentReference(ident.Name(), ident.Value()))
+ e.SetKindCase(c.NewIdent(e.ID(), ident.Name()))
+ return
+ }
+ }
+
+ resultType := c.checkSelectField(e, sel.Operand(), sel.FieldName(), false)
+ if sel.IsTestOnly() {
+ resultType = types.BoolType
+ }
+ c.setType(e, substitute(c.mappings, resultType, false))
+}
+
+func (c *checker) checkOptSelect(e ast.Expr) {
+ // Collect metadata related to the opt select call packaged by the parser.
+ call := e.AsCall()
+ operand := call.Args()[0]
+ field := call.Args()[1]
+ fieldName, isString := maybeUnwrapString(field)
+ if !isString {
+ c.errors.notAnOptionalFieldSelection(field.ID(), c.location(field), field)
+ return
+ }
+
+ // Perform type-checking using the field selection logic.
+ resultType := c.checkSelectField(e, operand, fieldName, true)
+ c.setType(e, substitute(c.mappings, resultType, false))
+ c.setReference(e, ast.NewFunctionReference("select_optional_field"))
+}
+
+func (c *checker) checkSelectField(e, operand ast.Expr, field string, optional bool) *types.Type {
+ // Interpret as field selection, first traversing down the operand.
+ c.check(operand)
+ operandType := substitute(c.mappings, c.getType(operand), false)
+
+ // If the target type is 'optional', unwrap it for the sake of this check.
+ targetType, isOpt := maybeUnwrapOptional(operandType)
+
+ // Assume error type by default as most types do not support field selection.
+ resultType := types.ErrorType
+ switch targetType.Kind() {
+ case types.MapKind:
+ // Maps yield their value type as the selection result type.
+ resultType = targetType.Parameters()[1]
+ case types.StructKind:
+ // Objects yield their field type declaration as the selection result type, but only if
+ // the field is defined.
+ messageType := targetType
+ if fieldType, found := c.lookupFieldType(e.ID(), messageType.TypeName(), field); found {
+ resultType = fieldType
+ }
+ case types.TypeParamKind:
+ // Set the operand type to DYN to prevent assignment to a potentially incorrect type
+ // at a later point in type-checking. The isAssignable call will update the type
+ // substitutions for the type param under the covers.
+ c.isAssignable(types.DynType, targetType)
+ // Also, set the result type to DYN.
+ resultType = types.DynType
+ default:
+ // Dynamic / error values are treated as DYN type. Errors are handled this way as well
+ // in order to allow forward progress on the check.
+ if !isDynOrError(targetType) {
+ c.errors.typeDoesNotSupportFieldSelection(e.ID(), c.location(e), targetType)
+ }
+ resultType = types.DynType
+ }
+
+ // If the target type was optional coming in, then the result must be optional going out.
+ if isOpt || optional {
+ return types.NewOptionalType(resultType)
+ }
+ return resultType
+}
+
+func (c *checker) checkCall(e ast.Expr) {
+ // Note: similar logic exists within the `interpreter/planner.go`. If making changes here
+ // please consider the impact on planner.go and consolidate implementations or mirror code
+ // as appropriate.
+ call := e.AsCall()
+ fnName := call.FunctionName()
+ if fnName == operators.OptSelect {
+ c.checkOptSelect(e)
+ return
+ }
+
+ args := call.Args()
+ // Traverse arguments.
+ for _, arg := range args {
+ c.check(arg)
+ }
+
+ // Regular static call with simple name.
+ if !call.IsMemberFunction() {
+ // Check for the existence of the function.
+ fn := c.env.LookupFunction(fnName)
+ if fn == nil {
+ c.errors.undeclaredReference(e.ID(), c.location(e), c.env.container.Name(), fnName)
+ c.setType(e, types.ErrorType)
+ return
+ }
+ // Overwrite the function name with its fully qualified resolved name.
+ e.SetKindCase(c.NewCall(e.ID(), fn.Name(), args...))
+ // Check to see whether the overload resolves.
+ c.resolveOverloadOrError(e, fn, nil, args)
+ return
+ }
+
+ // If a receiver 'target' is present, it may either be a receiver function, or a namespaced
+ // function, but not both. Given a.b.c() either a.b.c is a function or c is a function with
+ // target a.b.
+ //
+ // Check whether the target is a namespaced function name.
+ target := call.Target()
+ qualifiedPrefix, maybeQualified := containers.ToQualifiedName(target)
+ if maybeQualified {
+ maybeQualifiedName := qualifiedPrefix + "." + fnName
+ fn := c.env.LookupFunction(maybeQualifiedName)
+ if fn != nil {
+ // The function name is namespaced and so preserving the target operand would
+ // be an inaccurate representation of the desired evaluation behavior.
+ // Overwrite with fully-qualified resolved function name sans receiver target.
+ e.SetKindCase(c.NewCall(e.ID(), fn.Name(), args...))
+ c.resolveOverloadOrError(e, fn, nil, args)
+ return
+ }
+ }
+
+ // Regular instance call.
+ c.check(target)
+ fn := c.env.LookupFunction(fnName)
+ // Function found, attempt overload resolution.
+ if fn != nil {
+ c.resolveOverloadOrError(e, fn, target, args)
+ return
+ }
+ // Function name not declared, record error.
+ c.setType(e, types.ErrorType)
+ c.errors.undeclaredReference(e.ID(), c.location(e), c.env.container.Name(), fnName)
+}
+
+func (c *checker) resolveOverloadOrError(
+ e ast.Expr, fn *decls.FunctionDecl, target ast.Expr, args []ast.Expr) {
+ // Attempt to resolve the overload.
+ resolution := c.resolveOverload(e, fn, target, args)
+ // No such overload, error noted in the resolveOverload call, type recorded here.
+ if resolution == nil {
+ c.setType(e, types.ErrorType)
+ return
+ }
+ // Overload found.
+ c.setType(e, resolution.Type)
+ c.setReference(e, resolution.Reference)
+}
+
+func (c *checker) resolveOverload(
+ call ast.Expr, fn *decls.FunctionDecl, target ast.Expr, args []ast.Expr) *overloadResolution {
+
+ var argTypes []*types.Type
+ if target != nil {
+ argTypes = append(argTypes, c.getType(target))
+ }
+ for _, arg := range args {
+ argTypes = append(argTypes, c.getType(arg))
+ }
+
+ var resultType *types.Type
+ var checkedRef *ast.ReferenceInfo
+ for _, overload := range fn.OverloadDecls() {
+ // Determine whether the overload is currently considered.
+ if c.env.isOverloadDisabled(overload.ID()) {
+ continue
+ }
+
+ // Ensure the call style for the overload matches.
+ if (target == nil && overload.IsMemberFunction()) ||
+ (target != nil && !overload.IsMemberFunction()) {
+ // not a compatible call style.
+ continue
+ }
+
+ // Alternative type-checking behavior when the logical operators are compacted into
+ // variadic AST representations.
+ if fn.Name() == operators.LogicalAnd || fn.Name() == operators.LogicalOr {
+ checkedRef = ast.NewFunctionReference(overload.ID())
+ for i, argType := range argTypes {
+ if !c.isAssignable(argType, types.BoolType) {
+ c.errors.typeMismatch(
+ args[i].ID(),
+ c.locationByID(args[i].ID()),
+ types.BoolType,
+ argType)
+ resultType = types.ErrorType
+ }
+ }
+ if isError(resultType) {
+ return nil
+ }
+ return newResolution(checkedRef, types.BoolType)
+ }
+
+ overloadType := newFunctionType(overload.ResultType(), overload.ArgTypes()...)
+ typeParams := overload.TypeParams()
+ if len(typeParams) != 0 {
+ // Instantiate overload's type with fresh type variables.
+ substitutions := newMapping()
+ for _, typePar := range typeParams {
+ substitutions.add(types.NewTypeParamType(typePar), c.newTypeVar())
+ }
+ overloadType = substitute(substitutions, overloadType, false)
+ }
+
+ candidateArgTypes := overloadType.Parameters()[1:]
+ if c.isAssignableList(argTypes, candidateArgTypes) {
+ if checkedRef == nil {
+ checkedRef = ast.NewFunctionReference(overload.ID())
+ } else {
+ checkedRef.AddOverload(overload.ID())
+ }
+
+ // First matching overload, determines result type.
+ fnResultType := substitute(c.mappings, overloadType.Parameters()[0], false)
+ if resultType == nil {
+ resultType = fnResultType
+ } else if !isDyn(resultType) && !fnResultType.IsExactType(resultType) {
+ resultType = types.DynType
+ }
+ }
+ }
+
+ if resultType == nil {
+ for i, argType := range argTypes {
+ argTypes[i] = substitute(c.mappings, argType, true)
+ }
+ c.errors.noMatchingOverload(call.ID(), c.location(call), fn.Name(), argTypes, target != nil)
+ return nil
+ }
+
+ return newResolution(checkedRef, resultType)
+}
+
+func (c *checker) checkCreateList(e ast.Expr) {
+ create := e.AsList()
+ var elemsType *types.Type
+ optionalIndices := create.OptionalIndices()
+ optionals := make(map[int32]bool, len(optionalIndices))
+ for _, optInd := range optionalIndices {
+ optionals[optInd] = true
+ }
+ for i, e := range create.Elements() {
+ c.check(e)
+ elemType := c.getType(e)
+ if optionals[int32(i)] {
+ var isOptional bool
+ elemType, isOptional = maybeUnwrapOptional(elemType)
+ if !isOptional && !isDyn(elemType) {
+ c.errors.typeMismatch(e.ID(), c.location(e), types.NewOptionalType(elemType), elemType)
+ }
+ }
+ elemsType = c.joinTypes(e, elemsType, elemType)
+ }
+ if elemsType == nil {
+ // If the list is empty, assign free type var to elem type.
+ elemsType = c.newTypeVar()
+ }
+ c.setType(e, types.NewListType(elemsType))
+}
+
+func (c *checker) checkCreateMap(e ast.Expr) {
+ mapVal := e.AsMap()
+ var mapKeyType *types.Type
+ var mapValueType *types.Type
+ for _, e := range mapVal.Entries() {
+ entry := e.AsMapEntry()
+ key := entry.Key()
+ c.check(key)
+ mapKeyType = c.joinTypes(key, mapKeyType, c.getType(key))
+
+ val := entry.Value()
+ c.check(val)
+ valType := c.getType(val)
+ if entry.IsOptional() {
+ var isOptional bool
+ valType, isOptional = maybeUnwrapOptional(valType)
+ if !isOptional && !isDyn(valType) {
+ c.errors.typeMismatch(val.ID(), c.location(val), types.NewOptionalType(valType), valType)
+ }
+ }
+ mapValueType = c.joinTypes(val, mapValueType, valType)
+ }
+ if mapKeyType == nil {
+ // If the map is empty, assign free type variables to typeKey and value type.
+ mapKeyType = c.newTypeVar()
+ mapValueType = c.newTypeVar()
+ }
+ c.setType(e, types.NewMapType(mapKeyType, mapValueType))
+}
+
+func (c *checker) checkCreateStruct(e ast.Expr) {
+ msgVal := e.AsStruct()
+ // Determine the type of the message.
+ resultType := types.ErrorType
+ ident := c.env.LookupIdent(msgVal.TypeName())
+ if ident == nil {
+ c.errors.undeclaredReference(
+ e.ID(), c.location(e), c.env.container.Name(), msgVal.TypeName())
+ c.setType(e, types.ErrorType)
+ return
+ }
+ // Ensure the type name is fully qualified in the AST.
+ typeName := ident.Name()
+ if msgVal.TypeName() != typeName {
+ e.SetKindCase(c.NewStruct(e.ID(), typeName, msgVal.Fields()))
+ msgVal = e.AsStruct()
+ }
+ c.setReference(e, ast.NewIdentReference(typeName, nil))
+ identKind := ident.Type().Kind()
+ if identKind != types.ErrorKind {
+ if identKind != types.TypeKind {
+ c.errors.notAType(e.ID(), c.location(e), ident.Type().DeclaredTypeName())
+ } else {
+ resultType = ident.Type().Parameters()[0]
+ // Backwards compatibility test between well-known types and message types
+ // In this context, the type is being instantiated by its protobuf name which
+ // is not ideal or recommended, but some users expect this to work.
+ if isWellKnownType(resultType) {
+ typeName = getWellKnownTypeName(resultType)
+ } else if resultType.Kind() == types.StructKind {
+ typeName = resultType.DeclaredTypeName()
+ } else {
+ c.errors.notAMessageType(e.ID(), c.location(e), resultType.DeclaredTypeName())
+ resultType = types.ErrorType
+ }
+ }
+ }
+ c.setType(e, resultType)
+
+ // Check the field initializers.
+ for _, f := range msgVal.Fields() {
+ field := f.AsStructField()
+ fieldName := field.Name()
+ value := field.Value()
+ c.check(value)
+
+ fieldType := types.ErrorType
+ ft, found := c.lookupFieldType(f.ID(), typeName, fieldName)
+ if found {
+ fieldType = ft
+ }
+
+ valType := c.getType(value)
+ if field.IsOptional() {
+ var isOptional bool
+ valType, isOptional = maybeUnwrapOptional(valType)
+ if !isOptional && !isDyn(valType) {
+ c.errors.typeMismatch(value.ID(), c.location(value), types.NewOptionalType(valType), valType)
+ }
+ }
+ if !c.isAssignable(fieldType, valType) {
+ c.errors.fieldTypeMismatch(f.ID(), c.locationByID(f.ID()), fieldName, fieldType, valType)
+ }
+ }
+}
+
+func (c *checker) checkComprehension(e ast.Expr) {
+ comp := e.AsComprehension()
+ c.check(comp.IterRange())
+ c.check(comp.AccuInit())
+ accuType := c.getType(comp.AccuInit())
+ rangeType := substitute(c.mappings, c.getType(comp.IterRange()), false)
+ var varType *types.Type
+
+ switch rangeType.Kind() {
+ case types.ListKind:
+ varType = rangeType.Parameters()[0]
+ case types.MapKind:
+ // Ranges over the keys.
+ varType = rangeType.Parameters()[0]
+ case types.DynKind, types.ErrorKind, types.TypeParamKind:
+ // Set the range type to DYN to prevent assignment to a potentially incorrect type
+ // at a later point in type-checking. The isAssignable call will update the type
+ // substitutions for the type param under the covers.
+ c.isAssignable(types.DynType, rangeType)
+ // Set the range iteration variable to type DYN as well.
+ varType = types.DynType
+ default:
+ c.errors.notAComprehensionRange(comp.IterRange().ID(), c.location(comp.IterRange()), rangeType)
+ varType = types.ErrorType
+ }
+
+ // Create a scope for the comprehension since it has a local accumulation variable.
+ // This scope will contain the accumulation variable used to compute the result.
+ c.env = c.env.enterScope()
+ c.env.AddIdents(decls.NewVariable(comp.AccuVar(), accuType))
+ // Create a block scope for the loop.
+ c.env = c.env.enterScope()
+ c.env.AddIdents(decls.NewVariable(comp.IterVar(), varType))
+ // Check the variable references in the condition and step.
+ c.check(comp.LoopCondition())
+ c.assertType(comp.LoopCondition(), types.BoolType)
+ c.check(comp.LoopStep())
+ c.assertType(comp.LoopStep(), accuType)
+ // Exit the loop's block scope before checking the result.
+ c.env = c.env.exitScope()
+ c.check(comp.Result())
+ // Exit the comprehension scope.
+ c.env = c.env.exitScope()
+ c.setType(e, substitute(c.mappings, c.getType(comp.Result()), false))
+}
+
+// Checks compatibility of joined types, and returns the most general common type.
+func (c *checker) joinTypes(e ast.Expr, previous, current *types.Type) *types.Type {
+ if previous == nil {
+ return current
+ }
+ if c.isAssignable(previous, current) {
+ return mostGeneral(previous, current)
+ }
+ if c.dynAggregateLiteralElementTypesEnabled() {
+ return types.DynType
+ }
+ c.errors.typeMismatch(e.ID(), c.location(e), previous, current)
+ return types.ErrorType
+}
+
+func (c *checker) dynAggregateLiteralElementTypesEnabled() bool {
+ return c.env.aggLitElemType == dynElementType
+}
+
+func (c *checker) newTypeVar() *types.Type {
+ id := c.freeTypeVarCounter
+ c.freeTypeVarCounter++
+ return types.NewTypeParamType(fmt.Sprintf("_var%d", id))
+}
+
+func (c *checker) isAssignable(t1, t2 *types.Type) bool {
+ subs := isAssignable(c.mappings, t1, t2)
+ if subs != nil {
+ c.mappings = subs
+ return true
+ }
+
+ return false
+}
+
+func (c *checker) isAssignableList(l1, l2 []*types.Type) bool {
+ subs := isAssignableList(c.mappings, l1, l2)
+ if subs != nil {
+ c.mappings = subs
+ return true
+ }
+
+ return false
+}
+
+func maybeUnwrapString(e ast.Expr) (string, bool) {
+ switch e.Kind() {
+ case ast.LiteralKind:
+ literal := e.AsLiteral()
+ switch v := literal.(type) {
+ case types.String:
+ return string(v), true
+ }
+ }
+ return "", false
+}
+
+func (c *checker) setType(e ast.Expr, t *types.Type) {
+ if old, found := c.TypeMap()[e.ID()]; found && !old.IsExactType(t) {
+ c.errors.incompatibleType(e.ID(), c.location(e), e, old, t)
+ return
+ }
+ c.SetType(e.ID(), t)
+}
+
+func (c *checker) getType(e ast.Expr) *types.Type {
+ return c.TypeMap()[e.ID()]
+}
+
+func (c *checker) setReference(e ast.Expr, r *ast.ReferenceInfo) {
+ if old, found := c.ReferenceMap()[e.ID()]; found && !old.Equals(r) {
+ c.errors.referenceRedefinition(e.ID(), c.location(e), e, old, r)
+ return
+ }
+ c.SetReference(e.ID(), r)
+}
+
+func (c *checker) assertType(e ast.Expr, t *types.Type) {
+ if !c.isAssignable(t, c.getType(e)) {
+ c.errors.typeMismatch(e.ID(), c.location(e), t, c.getType(e))
+ }
+}
+
+type overloadResolution struct {
+ Type *types.Type
+ Reference *ast.ReferenceInfo
+}
+
+func newResolution(r *ast.ReferenceInfo, t *types.Type) *overloadResolution {
+ return &overloadResolution{
+ Reference: r,
+ Type: t,
+ }
+}
+
+func (c *checker) location(e ast.Expr) common.Location {
+ return c.locationByID(e.ID())
+}
+
+func (c *checker) locationByID(id int64) common.Location {
+ return c.SourceInfo().GetStartLocation(id)
+}
+
+func (c *checker) lookupFieldType(exprID int64, structType, fieldName string) (*types.Type, bool) {
+ if _, found := c.env.provider.FindStructType(structType); !found {
+ // This should not happen, anyway, report an error.
+ c.errors.unexpectedFailedResolution(exprID, c.locationByID(exprID), structType)
+ return nil, false
+ }
+
+ if ft, found := c.env.provider.FindStructFieldType(structType, fieldName); found {
+ return ft.Type, found
+ }
+
+ c.errors.undefinedField(exprID, c.locationByID(exprID), fieldName)
+ return nil, false
+}
+
+func isWellKnownType(t *types.Type) bool {
+ switch t.Kind() {
+ case types.AnyKind, types.TimestampKind, types.DurationKind, types.DynKind, types.NullTypeKind:
+ return true
+ case types.BoolKind, types.BytesKind, types.DoubleKind, types.IntKind, types.StringKind, types.UintKind:
+ return t.IsAssignableType(types.NullType)
+ case types.ListKind:
+ return t.Parameters()[0] == types.DynType
+ case types.MapKind:
+ return t.Parameters()[0] == types.StringType && t.Parameters()[1] == types.DynType
+ }
+ return false
+}
+
+func getWellKnownTypeName(t *types.Type) string {
+ if name, found := wellKnownTypes[t.Kind()]; found {
+ return name
+ }
+ return ""
+}
+
+var (
+ wellKnownTypes = map[types.Kind]string{
+ types.AnyKind: "google.protobuf.Any",
+ types.BoolKind: "google.protobuf.BoolValue",
+ types.BytesKind: "google.protobuf.BytesValue",
+ types.DoubleKind: "google.protobuf.DoubleValue",
+ types.DurationKind: "google.protobuf.Duration",
+ types.DynKind: "google.protobuf.Value",
+ types.IntKind: "google.protobuf.Int64Value",
+ types.ListKind: "google.protobuf.ListValue",
+ types.NullTypeKind: "google.protobuf.NullValue",
+ types.MapKind: "google.protobuf.Struct",
+ types.StringKind: "google.protobuf.StringValue",
+ types.TimestampKind: "google.protobuf.Timestamp",
+ types.UintKind: "google.protobuf.UInt64Value",
+ }
+)
diff --git a/vendor/github.com/authzed/cel-go/checker/cost.go b/vendor/github.com/authzed/cel-go/checker/cost.go
new file mode 100644
index 0000000..e832726
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/cost.go
@@ -0,0 +1,705 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+import (
+ "math"
+
+ "github.com/authzed/cel-go/common"
+ "github.com/authzed/cel-go/common/ast"
+ "github.com/authzed/cel-go/common/overloads"
+ "github.com/authzed/cel-go/common/types"
+ "github.com/authzed/cel-go/parser"
+)
+
+// WARNING: Any changes to cost calculations in this file require a corresponding change in interpreter/runtimecost.go
+
+// CostEstimator estimates the sizes of variable length input data and the costs of functions.
+type CostEstimator interface {
+ // EstimateSize returns a SizeEstimate for the given AstNode, or nil if
+ // the estimator has no estimate to provide. The size is equivalent to the result of the CEL `size()` function:
+ // length of strings and bytes, number of map entries or number of list items.
+ // EstimateSize is only called for AstNodes where
+ // CEL does not know the size; EstimateSize is not called for values defined inline in CEL where the size
+ // is already obvious to CEL.
+ EstimateSize(element AstNode) *SizeEstimate
+ // EstimateCallCost returns the estimated cost of an invocation, or nil if
+ // the estimator has no estimate to provide.
+ EstimateCallCost(function, overloadID string, target *AstNode, args []AstNode) *CallEstimate
+}
+
+// CallEstimate includes a CostEstimate for the call, and an optional estimate of the result object size.
+// The ResultSize should only be provided if the call results in a map, list, string or bytes.
+type CallEstimate struct {
+ CostEstimate
+ ResultSize *SizeEstimate
+}
+
+// AstNode represents an AST node for the purpose of cost estimations.
+type AstNode interface {
+ // Path returns a field path through the provided type declarations to the type of the AstNode, or nil if the AstNode does not
+ // represent type directly reachable from the provided type declarations.
+ // The first path element is a variable. All subsequent path elements are one of: field name, '@items', '@keys', '@values'.
+ Path() []string
+ // Type returns the deduced type of the AstNode.
+ Type() *types.Type
+ // Expr returns the expression of the AstNode.
+ Expr() ast.Expr
+ // ComputedSize returns a size estimate of the AstNode derived from information available in the CEL expression.
+ // For constants and inline list and map declarations, the exact size is returned. For concatenated list, strings
+ // and bytes, the size is derived from the size estimates of the operands. nil is returned if there is no
+ // computed size available.
+ ComputedSize() *SizeEstimate
+}
+
+type astNode struct {
+ path []string
+ t *types.Type
+ expr ast.Expr
+ derivedSize *SizeEstimate
+}
+
+func (e astNode) Path() []string {
+ return e.path
+}
+
+func (e astNode) Type() *types.Type {
+ return e.t
+}
+
+func (e astNode) Expr() ast.Expr {
+ return e.expr
+}
+
+func (e astNode) ComputedSize() *SizeEstimate {
+ if e.derivedSize != nil {
+ return e.derivedSize
+ }
+ var v uint64
+ switch e.expr.Kind() {
+ case ast.LiteralKind:
+ switch ck := e.expr.AsLiteral().(type) {
+ case types.String:
+ // converting to runes here is an O(n) operation, but
+ // this is consistent with how size is computed at runtime,
+ // and how the language definition defines string size
+ v = uint64(len([]rune(ck)))
+ case types.Bytes:
+ v = uint64(len(ck))
+ case types.Bool, types.Double, types.Duration,
+ types.Int, types.Timestamp, types.Uint,
+ types.Null:
+ v = uint64(1)
+ default:
+ return nil
+ }
+ case ast.ListKind:
+ v = uint64(e.expr.AsList().Size())
+ case ast.MapKind:
+ v = uint64(e.expr.AsMap().Size())
+ default:
+ return nil
+ }
+
+ return &SizeEstimate{Min: v, Max: v}
+}
+
+// SizeEstimate represents an estimated size of a variable length string, bytes, map or list.
+type SizeEstimate struct {
+ Min, Max uint64
+}
+
+// Add adds to another SizeEstimate and returns the sum.
+// If add would result in an uint64 overflow, the result is math.MaxUint64.
+func (se SizeEstimate) Add(sizeEstimate SizeEstimate) SizeEstimate {
+ return SizeEstimate{
+ addUint64NoOverflow(se.Min, sizeEstimate.Min),
+ addUint64NoOverflow(se.Max, sizeEstimate.Max),
+ }
+}
+
+// Multiply multiplies by another SizeEstimate and returns the product.
+// If multiply would result in an uint64 overflow, the result is math.MaxUint64.
+func (se SizeEstimate) Multiply(sizeEstimate SizeEstimate) SizeEstimate {
+ return SizeEstimate{
+ multiplyUint64NoOverflow(se.Min, sizeEstimate.Min),
+ multiplyUint64NoOverflow(se.Max, sizeEstimate.Max),
+ }
+}
+
+// MultiplyByCostFactor multiplies a SizeEstimate by a cost factor and returns the CostEstimate with the
+// nearest integer of the result, rounded up.
+func (se SizeEstimate) MultiplyByCostFactor(costPerUnit float64) CostEstimate {
+ return CostEstimate{
+ multiplyByCostFactor(se.Min, costPerUnit),
+ multiplyByCostFactor(se.Max, costPerUnit),
+ }
+}
+
+// MultiplyByCost multiplies by the cost and returns the product.
+// If multiply would result in an uint64 overflow, the result is math.MaxUint64.
+func (se SizeEstimate) MultiplyByCost(cost CostEstimate) CostEstimate {
+ return CostEstimate{
+ multiplyUint64NoOverflow(se.Min, cost.Min),
+ multiplyUint64NoOverflow(se.Max, cost.Max),
+ }
+}
+
+// Union returns a SizeEstimate that encompasses both input the SizeEstimate.
+func (se SizeEstimate) Union(size SizeEstimate) SizeEstimate {
+ result := se
+ if size.Min < result.Min {
+ result.Min = size.Min
+ }
+ if size.Max > result.Max {
+ result.Max = size.Max
+ }
+ return result
+}
+
+// CostEstimate represents an estimated cost range and provides add and multiply operations
+// that do not overflow.
+type CostEstimate struct {
+ Min, Max uint64
+}
+
+// Add adds the costs and returns the sum.
+// If add would result in an uint64 overflow for the min or max, the value is set to math.MaxUint64.
+func (ce CostEstimate) Add(cost CostEstimate) CostEstimate {
+ return CostEstimate{
+ addUint64NoOverflow(ce.Min, cost.Min),
+ addUint64NoOverflow(ce.Max, cost.Max),
+ }
+}
+
+// Multiply multiplies by the cost and returns the product.
+// If multiply would result in an uint64 overflow, the result is math.MaxUint64.
+func (ce CostEstimate) Multiply(cost CostEstimate) CostEstimate {
+ return CostEstimate{
+ multiplyUint64NoOverflow(ce.Min, cost.Min),
+ multiplyUint64NoOverflow(ce.Max, cost.Max),
+ }
+}
+
+// MultiplyByCostFactor multiplies a CostEstimate by a cost factor and returns the CostEstimate with the
+// nearest integer of the result, rounded up.
+func (ce CostEstimate) MultiplyByCostFactor(costPerUnit float64) CostEstimate {
+ return CostEstimate{
+ multiplyByCostFactor(ce.Min, costPerUnit),
+ multiplyByCostFactor(ce.Max, costPerUnit),
+ }
+}
+
+// Union returns a CostEstimate that encompasses both input the CostEstimates.
+func (ce CostEstimate) Union(size CostEstimate) CostEstimate {
+ result := ce
+ if size.Min < result.Min {
+ result.Min = size.Min
+ }
+ if size.Max > result.Max {
+ result.Max = size.Max
+ }
+ return result
+}
+
+// addUint64NoOverflow adds non-negative ints. If the result is exceeds math.MaxUint64, math.MaxUint64
+// is returned.
+func addUint64NoOverflow(x, y uint64) uint64 {
+ if y > 0 && x > math.MaxUint64-y {
+ return math.MaxUint64
+ }
+ return x + y
+}
+
+// multiplyUint64NoOverflow multiplies non-negative ints. If the result is exceeds math.MaxUint64, math.MaxUint64
+// is returned.
+func multiplyUint64NoOverflow(x, y uint64) uint64 {
+ if y != 0 && x > math.MaxUint64/y {
+ return math.MaxUint64
+ }
+ return x * y
+}
+
+// multiplyByFactor multiplies an integer by a cost factor float and returns the nearest integer value, rounded up.
+func multiplyByCostFactor(x uint64, y float64) uint64 {
+ xFloat := float64(x)
+ if xFloat > 0 && y > 0 && xFloat > math.MaxUint64/y {
+ return math.MaxUint64
+ }
+ ceil := math.Ceil(xFloat * y)
+ if ceil >= doubleTwoTo64 {
+ return math.MaxUint64
+ }
+ return uint64(ceil)
+}
+
+var (
+ selectAndIdentCost = CostEstimate{Min: common.SelectAndIdentCost, Max: common.SelectAndIdentCost}
+ constCost = CostEstimate{Min: common.ConstCost, Max: common.ConstCost}
+
+ createListBaseCost = CostEstimate{Min: common.ListCreateBaseCost, Max: common.ListCreateBaseCost}
+ createMapBaseCost = CostEstimate{Min: common.MapCreateBaseCost, Max: common.MapCreateBaseCost}
+ createMessageBaseCost = CostEstimate{Min: common.StructCreateBaseCost, Max: common.StructCreateBaseCost}
+)
+
+type coster struct {
+ // exprPath maps from Expr Id to field path.
+ exprPath map[int64][]string
+ // iterRanges tracks the iterRange of each iterVar.
+ iterRanges iterRangeScopes
+ // computedSizes tracks the computed sizes of call results.
+ computedSizes map[int64]SizeEstimate
+ checkedAST *ast.AST
+ estimator CostEstimator
+ overloadEstimators map[string]FunctionEstimator
+ // presenceTestCost will either be a zero or one based on whether has() macros count against cost computations.
+ presenceTestCost CostEstimate
+}
+
+// Use a stack of iterVar -> iterRange Expr Ids to handle shadowed variable names.
+type iterRangeScopes map[string][]int64
+
+func (vs iterRangeScopes) push(varName string, expr ast.Expr) {
+ vs[varName] = append(vs[varName], expr.ID())
+}
+
+func (vs iterRangeScopes) pop(varName string) {
+ varStack := vs[varName]
+ vs[varName] = varStack[:len(varStack)-1]
+}
+
+func (vs iterRangeScopes) peek(varName string) (int64, bool) {
+ varStack := vs[varName]
+ if len(varStack) > 0 {
+ return varStack[len(varStack)-1], true
+ }
+ return 0, false
+}
+
+// CostOption configures flags which affect cost computations.
+type CostOption func(*coster) error
+
+// PresenceTestHasCost determines whether presence testing has a cost of one or zero.
+//
+// Defaults to presence test has a cost of one.
+func PresenceTestHasCost(hasCost bool) CostOption {
+ return func(c *coster) error {
+ if hasCost {
+ c.presenceTestCost = selectAndIdentCost
+ return nil
+ }
+ c.presenceTestCost = CostEstimate{Min: 0, Max: 0}
+ return nil
+ }
+}
+
+// FunctionEstimator provides a CallEstimate given the target and arguments for a specific function, overload pair.
+type FunctionEstimator func(estimator CostEstimator, target *AstNode, args []AstNode) *CallEstimate
+
+// OverloadCostEstimate binds a FunctionCoster to a specific function overload ID.
+//
+// When a OverloadCostEstimate is provided, it will override the cost calculation of the CostEstimator provided to
+// the Cost() call.
+func OverloadCostEstimate(overloadID string, functionCoster FunctionEstimator) CostOption {
+ return func(c *coster) error {
+ c.overloadEstimators[overloadID] = functionCoster
+ return nil
+ }
+}
+
+// Cost estimates the cost of the parsed and type checked CEL expression.
+func Cost(checked *ast.AST, estimator CostEstimator, opts ...CostOption) (CostEstimate, error) {
+ c := &coster{
+ checkedAST: checked,
+ estimator: estimator,
+ overloadEstimators: map[string]FunctionEstimator{},
+ exprPath: map[int64][]string{},
+ iterRanges: map[string][]int64{},
+ computedSizes: map[int64]SizeEstimate{},
+ presenceTestCost: CostEstimate{Min: 1, Max: 1},
+ }
+ for _, opt := range opts {
+ err := opt(c)
+ if err != nil {
+ return CostEstimate{}, err
+ }
+ }
+ return c.cost(checked.Expr()), nil
+}
+
+func (c *coster) cost(e ast.Expr) CostEstimate {
+ if e == nil {
+ return CostEstimate{}
+ }
+ var cost CostEstimate
+ switch e.Kind() {
+ case ast.LiteralKind:
+ cost = constCost
+ case ast.IdentKind:
+ cost = c.costIdent(e)
+ case ast.SelectKind:
+ cost = c.costSelect(e)
+ case ast.CallKind:
+ cost = c.costCall(e)
+ case ast.ListKind:
+ cost = c.costCreateList(e)
+ case ast.MapKind:
+ cost = c.costCreateMap(e)
+ case ast.StructKind:
+ cost = c.costCreateStruct(e)
+ case ast.ComprehensionKind:
+ cost = c.costComprehension(e)
+ default:
+ return CostEstimate{}
+ }
+ return cost
+}
+
+func (c *coster) costIdent(e ast.Expr) CostEstimate {
+ identName := e.AsIdent()
+ // build and track the field path
+ if iterRange, ok := c.iterRanges.peek(identName); ok {
+ switch c.checkedAST.GetType(iterRange).Kind() {
+ case types.ListKind:
+ c.addPath(e, append(c.exprPath[iterRange], "@items"))
+ case types.MapKind:
+ c.addPath(e, append(c.exprPath[iterRange], "@keys"))
+ }
+ } else {
+ c.addPath(e, []string{identName})
+ }
+
+ return selectAndIdentCost
+}
+
+func (c *coster) costSelect(e ast.Expr) CostEstimate {
+ sel := e.AsSelect()
+ var sum CostEstimate
+ if sel.IsTestOnly() {
+ // recurse, but do not add any cost
+ // this is equivalent to how evalTestOnly increments the runtime cost counter
+ // but does not add any additional cost for the qualifier, except here we do
+ // the reverse (ident adds cost)
+ sum = sum.Add(c.presenceTestCost)
+ sum = sum.Add(c.cost(sel.Operand()))
+ return sum
+ }
+ sum = sum.Add(c.cost(sel.Operand()))
+ targetType := c.getType(sel.Operand())
+ switch targetType.Kind() {
+ case types.MapKind, types.StructKind, types.TypeParamKind:
+ sum = sum.Add(selectAndIdentCost)
+ }
+
+ // build and track the field path
+ c.addPath(e, append(c.getPath(sel.Operand()), sel.FieldName()))
+
+ return sum
+}
+
+func (c *coster) costCall(e ast.Expr) CostEstimate {
+ call := e.AsCall()
+ args := call.Args()
+
+ var sum CostEstimate
+
+ argTypes := make([]AstNode, len(args))
+ argCosts := make([]CostEstimate, len(args))
+ for i, arg := range args {
+ argCosts[i] = c.cost(arg)
+ argTypes[i] = c.newAstNode(arg)
+ }
+
+ overloadIDs := c.checkedAST.GetOverloadIDs(e.ID())
+ if len(overloadIDs) == 0 {
+ return CostEstimate{}
+ }
+ var targetType AstNode
+ if call.IsMemberFunction() {
+ sum = sum.Add(c.cost(call.Target()))
+ targetType = c.newAstNode(call.Target())
+ }
+ // Pick a cost estimate range that covers all the overload cost estimation ranges
+ fnCost := CostEstimate{Min: uint64(math.MaxUint64), Max: 0}
+ var resultSize *SizeEstimate
+ for _, overload := range overloadIDs {
+ overloadCost := c.functionCost(call.FunctionName(), overload, &targetType, argTypes, argCosts)
+ fnCost = fnCost.Union(overloadCost.CostEstimate)
+ if overloadCost.ResultSize != nil {
+ if resultSize == nil {
+ resultSize = overloadCost.ResultSize
+ } else {
+ size := resultSize.Union(*overloadCost.ResultSize)
+ resultSize = &size
+ }
+ }
+ // build and track the field path for index operations
+ switch overload {
+ case overloads.IndexList:
+ if len(args) > 0 {
+ c.addPath(e, append(c.getPath(args[0]), "@items"))
+ }
+ case overloads.IndexMap:
+ if len(args) > 0 {
+ c.addPath(e, append(c.getPath(args[0]), "@values"))
+ }
+ }
+ }
+ if resultSize != nil {
+ c.computedSizes[e.ID()] = *resultSize
+ }
+ return sum.Add(fnCost)
+}
+
+func (c *coster) costCreateList(e ast.Expr) CostEstimate {
+ create := e.AsList()
+ var sum CostEstimate
+ for _, e := range create.Elements() {
+ sum = sum.Add(c.cost(e))
+ }
+ return sum.Add(createListBaseCost)
+}
+
+func (c *coster) costCreateMap(e ast.Expr) CostEstimate {
+ mapVal := e.AsMap()
+ var sum CostEstimate
+ for _, ent := range mapVal.Entries() {
+ entry := ent.AsMapEntry()
+ sum = sum.Add(c.cost(entry.Key()))
+ sum = sum.Add(c.cost(entry.Value()))
+ }
+ return sum.Add(createMapBaseCost)
+}
+
+func (c *coster) costCreateStruct(e ast.Expr) CostEstimate {
+ msgVal := e.AsStruct()
+ var sum CostEstimate
+ for _, ent := range msgVal.Fields() {
+ field := ent.AsStructField()
+ sum = sum.Add(c.cost(field.Value()))
+ }
+ return sum.Add(createMessageBaseCost)
+}
+
+func (c *coster) costComprehension(e ast.Expr) CostEstimate {
+ comp := e.AsComprehension()
+ var sum CostEstimate
+ sum = sum.Add(c.cost(comp.IterRange()))
+ sum = sum.Add(c.cost(comp.AccuInit()))
+
+ // Track the iterRange of each IterVar for field path construction
+ c.iterRanges.push(comp.IterVar(), comp.IterRange())
+ loopCost := c.cost(comp.LoopCondition())
+ stepCost := c.cost(comp.LoopStep())
+ c.iterRanges.pop(comp.IterVar())
+ sum = sum.Add(c.cost(comp.Result()))
+ rangeCnt := c.sizeEstimate(c.newAstNode(comp.IterRange()))
+
+ c.computedSizes[e.ID()] = rangeCnt
+
+ rangeCost := rangeCnt.MultiplyByCost(stepCost.Add(loopCost))
+ sum = sum.Add(rangeCost)
+
+ return sum
+}
+
+func (c *coster) sizeEstimate(t AstNode) SizeEstimate {
+ if l := t.ComputedSize(); l != nil {
+ return *l
+ }
+ if l := c.estimator.EstimateSize(t); l != nil {
+ return *l
+ }
+ // return an estimate of 1 for return types of set
+ // lengths, since strings/bytes/more complex objects could be of
+ // variable length
+ if isScalar(t.Type()) {
+ // TODO: since the logic for size estimation is split between
+ // ComputedSize and isScalar, changing one will likely require changing
+ // the other, so they should be merged in the future if possible
+ return SizeEstimate{Min: 1, Max: 1}
+ }
+ return SizeEstimate{Min: 0, Max: math.MaxUint64}
+}
+
+func (c *coster) functionCost(function, overloadID string, target *AstNode, args []AstNode, argCosts []CostEstimate) CallEstimate {
+ argCostSum := func() CostEstimate {
+ var sum CostEstimate
+ for _, a := range argCosts {
+ sum = sum.Add(a)
+ }
+ return sum
+ }
+ if len(c.overloadEstimators) != 0 {
+ if estimator, found := c.overloadEstimators[overloadID]; found {
+ if est := estimator(c.estimator, target, args); est != nil {
+ callEst := *est
+ return CallEstimate{CostEstimate: callEst.Add(argCostSum()), ResultSize: est.ResultSize}
+ }
+ }
+ }
+ if est := c.estimator.EstimateCallCost(function, overloadID, target, args); est != nil {
+ callEst := *est
+ return CallEstimate{CostEstimate: callEst.Add(argCostSum()), ResultSize: est.ResultSize}
+ }
+ switch overloadID {
+ // O(n) functions
+ case overloads.ExtFormatString:
+ if target != nil {
+ // ResultSize not calculated because we can't bound the max size.
+ return CallEstimate{CostEstimate: c.sizeEstimate(*target).MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum())}
+ }
+ case overloads.StringToBytes:
+ if len(args) == 1 {
+ sz := c.sizeEstimate(args[0])
+ // ResultSize max is when each char converts to 4 bytes.
+ return CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &SizeEstimate{Min: sz.Min, Max: sz.Max * 4}}
+ }
+ case overloads.BytesToString:
+ if len(args) == 1 {
+ sz := c.sizeEstimate(args[0])
+ // ResultSize min is when 4 bytes convert to 1 char.
+ return CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &SizeEstimate{Min: sz.Min / 4, Max: sz.Max}}
+ }
+ case overloads.ExtQuoteString:
+ if len(args) == 1 {
+ sz := c.sizeEstimate(args[0])
+ // ResultSize max is when each char is escaped. 2 quote chars always added.
+ return CallEstimate{CostEstimate: sz.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &SizeEstimate{Min: sz.Min + 2, Max: sz.Max*2 + 2}}
+ }
+ case overloads.StartsWithString, overloads.EndsWithString:
+ if len(args) == 1 {
+ return CallEstimate{CostEstimate: c.sizeEstimate(args[0]).MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum())}
+ }
+ case overloads.InList:
+ // If a list is composed entirely of constant values this is O(1), but we don't account for that here.
+ // We just assume all list containment checks are O(n).
+ if len(args) == 2 {
+ return CallEstimate{CostEstimate: c.sizeEstimate(args[1]).MultiplyByCostFactor(1).Add(argCostSum())}
+ }
+ // O(nm) functions
+ case overloads.MatchesString:
+ // https://swtch.com/~rsc/regexp/regexp1.html applies to RE2 implementation supported by CEL
+ if target != nil && len(args) == 1 {
+ // Add one to string length for purposes of cost calculation to prevent product of string and regex to be 0
+ // in case where string is empty but regex is still expensive.
+ strCost := c.sizeEstimate(*target).Add(SizeEstimate{Min: 1, Max: 1}).MultiplyByCostFactor(common.StringTraversalCostFactor)
+ // We don't know how many expressions are in the regex, just the string length (a huge
+ // improvement here would be to somehow get a count the number of expressions in the regex or
+ // how many states are in the regex state machine and use that to measure regex cost).
+ // For now, we're making a guess that each expression in a regex is typically at least 4 chars
+ // in length.
+ regexCost := c.sizeEstimate(args[0]).MultiplyByCostFactor(common.RegexStringLengthCostFactor)
+ return CallEstimate{CostEstimate: strCost.Multiply(regexCost).Add(argCostSum())}
+ }
+ case overloads.ContainsString:
+ if target != nil && len(args) == 1 {
+ strCost := c.sizeEstimate(*target).MultiplyByCostFactor(common.StringTraversalCostFactor)
+ substrCost := c.sizeEstimate(args[0]).MultiplyByCostFactor(common.StringTraversalCostFactor)
+ return CallEstimate{CostEstimate: strCost.Multiply(substrCost).Add(argCostSum())}
+ }
+ case overloads.LogicalOr, overloads.LogicalAnd:
+ lhs := argCosts[0]
+ rhs := argCosts[1]
+ // min cost is min of LHS for short circuited && or ||
+ argCost := CostEstimate{Min: lhs.Min, Max: lhs.Add(rhs).Max}
+ return CallEstimate{CostEstimate: argCost}
+ case overloads.Conditional:
+ size := c.sizeEstimate(args[1]).Union(c.sizeEstimate(args[2]))
+ conditionalCost := argCosts[0]
+ ifTrueCost := argCosts[1]
+ ifFalseCost := argCosts[2]
+ argCost := conditionalCost.Add(ifTrueCost.Union(ifFalseCost))
+ return CallEstimate{CostEstimate: argCost, ResultSize: &size}
+ case overloads.AddString, overloads.AddBytes, overloads.AddList:
+ if len(args) == 2 {
+ lhsSize := c.sizeEstimate(args[0])
+ rhsSize := c.sizeEstimate(args[1])
+ resultSize := lhsSize.Add(rhsSize)
+ switch overloadID {
+ case overloads.AddList:
+ // list concatenation is O(1), but we handle it here to track size
+ return CallEstimate{CostEstimate: CostEstimate{Min: 1, Max: 1}.Add(argCostSum()), ResultSize: &resultSize}
+ default:
+ return CallEstimate{CostEstimate: resultSize.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum()), ResultSize: &resultSize}
+ }
+ }
+ case overloads.LessString, overloads.GreaterString, overloads.LessEqualsString, overloads.GreaterEqualsString,
+ overloads.LessBytes, overloads.GreaterBytes, overloads.LessEqualsBytes, overloads.GreaterEqualsBytes,
+ overloads.Equals, overloads.NotEquals:
+ lhsCost := c.sizeEstimate(args[0])
+ rhsCost := c.sizeEstimate(args[1])
+ min := uint64(0)
+ smallestMax := lhsCost.Max
+ if rhsCost.Max < smallestMax {
+ smallestMax = rhsCost.Max
+ }
+ if smallestMax > 0 {
+ min = 1
+ }
+ // equality of 2 scalar values results in a cost of 1
+ return CallEstimate{CostEstimate: CostEstimate{Min: min, Max: smallestMax}.MultiplyByCostFactor(common.StringTraversalCostFactor).Add(argCostSum())}
+ }
+ // O(1) functions
+ // See CostTracker.costCall for more details about O(1) cost calculations
+
+ // Benchmarks suggest that most of the other operations take +/- 50% of a base cost unit
+ // which on an Intel xeon 2.20GHz CPU is 50ns.
+ return CallEstimate{CostEstimate: CostEstimate{Min: 1, Max: 1}.Add(argCostSum())}
+}
+
+func (c *coster) getType(e ast.Expr) *types.Type {
+ return c.checkedAST.GetType(e.ID())
+}
+
+func (c *coster) getPath(e ast.Expr) []string {
+ return c.exprPath[e.ID()]
+}
+
+func (c *coster) addPath(e ast.Expr, path []string) {
+ c.exprPath[e.ID()] = path
+}
+
+func (c *coster) newAstNode(e ast.Expr) *astNode {
+ path := c.getPath(e)
+ if len(path) > 0 && path[0] == parser.AccumulatorName {
+ // only provide paths to root vars; omit accumulator vars
+ path = nil
+ }
+ var derivedSize *SizeEstimate
+ if size, ok := c.computedSizes[e.ID()]; ok {
+ derivedSize = &size
+ }
+ return &astNode{
+ path: path,
+ t: c.getType(e),
+ expr: e,
+ derivedSize: derivedSize}
+}
+
+// isScalar returns true if the given type is known to be of a constant size at
+// compile time. isScalar will return false for strings (they are variable-width)
+// in addition to protobuf.Any and protobuf.Value (their size is not knowable at compile time).
+func isScalar(t *types.Type) bool {
+ switch t.Kind() {
+ case types.BoolKind, types.DoubleKind, types.DurationKind, types.IntKind, types.TimestampKind, types.UintKind:
+ return true
+ }
+ return false
+}
+
+var (
+ doubleTwoTo64 = math.Ldexp(1.0, 64)
+)
diff --git a/vendor/github.com/authzed/cel-go/checker/decls/BUILD.bazel b/vendor/github.com/authzed/cel-go/checker/decls/BUILD.bazel
new file mode 100644
index 0000000..2ee0e4d
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/decls/BUILD.bazel
@@ -0,0 +1,19 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+package(
+ default_visibility = ["//visibility:public"],
+ licenses = ["notice"], # Apache 2.0
+)
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "decls.go",
+ ],
+ importpath = "github.com/authzed/cel-go/checker/decls",
+ deps = [
+ "@org_golang_google_genproto_googleapis_api//expr/v1alpha1:go_default_library",
+ "@org_golang_google_protobuf//types/known/emptypb:go_default_library",
+ "@org_golang_google_protobuf//types/known/structpb:go_default_library",
+ ],
+)
diff --git a/vendor/github.com/authzed/cel-go/checker/decls/decls.go b/vendor/github.com/authzed/cel-go/checker/decls/decls.go
new file mode 100644
index 0000000..c0e5de4
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/decls/decls.go
@@ -0,0 +1,237 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package decls provides helpers for creating variable and function declarations.
+package decls
+
+import (
+ exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
+ emptypb "google.golang.org/protobuf/types/known/emptypb"
+ structpb "google.golang.org/protobuf/types/known/structpb"
+)
+
+var (
+ // Error type used to communicate issues during type-checking.
+ Error = &exprpb.Type{
+ TypeKind: &exprpb.Type_Error{
+ Error: &emptypb.Empty{}}}
+
+ // Dyn is a top-type used to represent any value.
+ Dyn = &exprpb.Type{
+ TypeKind: &exprpb.Type_Dyn{
+ Dyn: &emptypb.Empty{}}}
+)
+
+// Commonly used types.
+var (
+ Bool = NewPrimitiveType(exprpb.Type_BOOL)
+ Bytes = NewPrimitiveType(exprpb.Type_BYTES)
+ Double = NewPrimitiveType(exprpb.Type_DOUBLE)
+ Int = NewPrimitiveType(exprpb.Type_INT64)
+ Null = &exprpb.Type{
+ TypeKind: &exprpb.Type_Null{
+ Null: structpb.NullValue_NULL_VALUE}}
+ String = NewPrimitiveType(exprpb.Type_STRING)
+ Uint = NewPrimitiveType(exprpb.Type_UINT64)
+)
+
+// Well-known types.
+// TODO: Replace with an abstract type registry.
+var (
+ Any = NewWellKnownType(exprpb.Type_ANY)
+ Duration = NewWellKnownType(exprpb.Type_DURATION)
+ Timestamp = NewWellKnownType(exprpb.Type_TIMESTAMP)
+)
+
+// NewAbstractType creates an abstract type declaration which references a proto
+// message name and may also include type parameters.
+func NewAbstractType(name string, paramTypes ...*exprpb.Type) *exprpb.Type {
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_AbstractType_{
+ AbstractType: &exprpb.Type_AbstractType{
+ Name: name,
+ ParameterTypes: paramTypes}}}
+}
+
+// NewOptionalType constructs an abstract type indicating that the parameterized type
+// may be contained within the object.
+func NewOptionalType(paramType *exprpb.Type) *exprpb.Type {
+ return NewAbstractType("optional_type", paramType)
+}
+
+// NewFunctionType creates a function invocation contract, typically only used
+// by type-checking steps after overload resolution.
+func NewFunctionType(resultType *exprpb.Type,
+ argTypes ...*exprpb.Type) *exprpb.Type {
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_Function{
+ Function: &exprpb.Type_FunctionType{
+ ResultType: resultType,
+ ArgTypes: argTypes}}}
+}
+
+// NewFunction creates a named function declaration with one or more overloads.
+func NewFunction(name string,
+ overloads ...*exprpb.Decl_FunctionDecl_Overload) *exprpb.Decl {
+ return &exprpb.Decl{
+ Name: name,
+ DeclKind: &exprpb.Decl_Function{
+ Function: &exprpb.Decl_FunctionDecl{
+ Overloads: overloads}}}
+}
+
+// NewIdent creates a named identifier declaration with an optional literal
+// value.
+//
+// Literal values are typically only associated with enum identifiers.
+//
+// Deprecated: Use NewVar or NewConst instead.
+func NewIdent(name string, t *exprpb.Type, v *exprpb.Constant) *exprpb.Decl {
+ return &exprpb.Decl{
+ Name: name,
+ DeclKind: &exprpb.Decl_Ident{
+ Ident: &exprpb.Decl_IdentDecl{
+ Type: t,
+ Value: v}}}
+}
+
+// NewConst creates a constant identifier with a CEL constant literal value.
+func NewConst(name string, t *exprpb.Type, v *exprpb.Constant) *exprpb.Decl {
+ return NewIdent(name, t, v)
+}
+
+// NewVar creates a variable identifier.
+func NewVar(name string, t *exprpb.Type) *exprpb.Decl {
+ return NewIdent(name, t, nil)
+}
+
+// NewInstanceOverload creates a instance function overload contract.
+// First element of argTypes is instance.
+func NewInstanceOverload(id string, argTypes []*exprpb.Type,
+ resultType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload {
+ return &exprpb.Decl_FunctionDecl_Overload{
+ OverloadId: id,
+ ResultType: resultType,
+ Params: argTypes,
+ IsInstanceFunction: true}
+}
+
+// NewListType generates a new list with elements of a certain type.
+func NewListType(elem *exprpb.Type) *exprpb.Type {
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_ListType_{
+ ListType: &exprpb.Type_ListType{
+ ElemType: elem}}}
+}
+
+// NewMapType generates a new map with typed keys and values.
+func NewMapType(key *exprpb.Type, value *exprpb.Type) *exprpb.Type {
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_MapType_{
+ MapType: &exprpb.Type_MapType{
+ KeyType: key,
+ ValueType: value}}}
+}
+
+// NewObjectType creates an object type for a qualified type name.
+func NewObjectType(typeName string) *exprpb.Type {
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_MessageType{
+ MessageType: typeName}}
+}
+
+// NewOverload creates a function overload declaration which contains a unique
+// overload id as well as the expected argument and result types. Overloads
+// must be aggregated within a Function declaration.
+func NewOverload(id string, argTypes []*exprpb.Type,
+ resultType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload {
+ return &exprpb.Decl_FunctionDecl_Overload{
+ OverloadId: id,
+ ResultType: resultType,
+ Params: argTypes,
+ IsInstanceFunction: false}
+}
+
+// NewParameterizedInstanceOverload creates a parametric function instance overload type.
+func NewParameterizedInstanceOverload(id string,
+ argTypes []*exprpb.Type,
+ resultType *exprpb.Type,
+ typeParams []string) *exprpb.Decl_FunctionDecl_Overload {
+ return &exprpb.Decl_FunctionDecl_Overload{
+ OverloadId: id,
+ ResultType: resultType,
+ Params: argTypes,
+ TypeParams: typeParams,
+ IsInstanceFunction: true}
+}
+
+// NewParameterizedOverload creates a parametric function overload type.
+func NewParameterizedOverload(id string,
+ argTypes []*exprpb.Type,
+ resultType *exprpb.Type,
+ typeParams []string) *exprpb.Decl_FunctionDecl_Overload {
+ return &exprpb.Decl_FunctionDecl_Overload{
+ OverloadId: id,
+ ResultType: resultType,
+ Params: argTypes,
+ TypeParams: typeParams,
+ IsInstanceFunction: false}
+}
+
+// NewPrimitiveType creates a type for a primitive value. See the var declarations
+// for Int, Uint, etc.
+func NewPrimitiveType(primitive exprpb.Type_PrimitiveType) *exprpb.Type {
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_Primitive{
+ Primitive: primitive}}
+}
+
+// NewTypeType creates a new type designating a type.
+func NewTypeType(nested *exprpb.Type) *exprpb.Type {
+ if nested == nil {
+ // must set the nested field for a valid oneof option
+ nested = &exprpb.Type{}
+ }
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_Type{
+ Type: nested}}
+}
+
+// NewTypeParamType creates a type corresponding to a named, contextual parameter.
+func NewTypeParamType(name string) *exprpb.Type {
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_TypeParam{
+ TypeParam: name}}
+}
+
+// NewWellKnownType creates a type corresponding to a protobuf well-known type
+// value.
+func NewWellKnownType(wellKnown exprpb.Type_WellKnownType) *exprpb.Type {
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_WellKnown{
+ WellKnown: wellKnown}}
+}
+
+// NewWrapperType creates a wrapped primitive type instance. Wrapped types
+// are roughly equivalent to a nullable, or optionally valued type.
+func NewWrapperType(wrapped *exprpb.Type) *exprpb.Type {
+ primitive := wrapped.GetPrimitive()
+ if primitive == exprpb.Type_PRIMITIVE_TYPE_UNSPECIFIED {
+ // TODO: return an error
+ panic("Wrapped type must be a primitive")
+ }
+ return &exprpb.Type{
+ TypeKind: &exprpb.Type_Wrapper{
+ Wrapper: primitive}}
+}
diff --git a/vendor/github.com/authzed/cel-go/checker/env.go b/vendor/github.com/authzed/cel-go/checker/env.go
new file mode 100644
index 0000000..34daeff
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/env.go
@@ -0,0 +1,284 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/authzed/cel-go/common/containers"
+ "github.com/authzed/cel-go/common/decls"
+ "github.com/authzed/cel-go/common/overloads"
+ "github.com/authzed/cel-go/common/types"
+ "github.com/authzed/cel-go/parser"
+)
+
+type aggregateLiteralElementType int
+
+const (
+ dynElementType aggregateLiteralElementType = iota
+ homogenousElementType aggregateLiteralElementType = 1 << iota
+)
+
+var (
+ crossTypeNumericComparisonOverloads = map[string]struct{}{
+ // double <-> int | uint
+ overloads.LessDoubleInt64: {},
+ overloads.LessDoubleUint64: {},
+ overloads.LessEqualsDoubleInt64: {},
+ overloads.LessEqualsDoubleUint64: {},
+ overloads.GreaterDoubleInt64: {},
+ overloads.GreaterDoubleUint64: {},
+ overloads.GreaterEqualsDoubleInt64: {},
+ overloads.GreaterEqualsDoubleUint64: {},
+ // int <-> double | uint
+ overloads.LessInt64Double: {},
+ overloads.LessInt64Uint64: {},
+ overloads.LessEqualsInt64Double: {},
+ overloads.LessEqualsInt64Uint64: {},
+ overloads.GreaterInt64Double: {},
+ overloads.GreaterInt64Uint64: {},
+ overloads.GreaterEqualsInt64Double: {},
+ overloads.GreaterEqualsInt64Uint64: {},
+ // uint <-> double | int
+ overloads.LessUint64Double: {},
+ overloads.LessUint64Int64: {},
+ overloads.LessEqualsUint64Double: {},
+ overloads.LessEqualsUint64Int64: {},
+ overloads.GreaterUint64Double: {},
+ overloads.GreaterUint64Int64: {},
+ overloads.GreaterEqualsUint64Double: {},
+ overloads.GreaterEqualsUint64Int64: {},
+ }
+)
+
+// Env is the environment for type checking.
+//
+// The Env is comprised of a container, type provider, declarations, and other related objects
+// which can be used to assist with type-checking.
+type Env struct {
+ container *containers.Container
+ provider types.Provider
+ declarations *Scopes
+ aggLitElemType aggregateLiteralElementType
+ filteredOverloadIDs map[string]struct{}
+}
+
+// NewEnv returns a new *Env with the given parameters.
+func NewEnv(container *containers.Container, provider types.Provider, opts ...Option) (*Env, error) {
+ declarations := newScopes()
+ declarations.Push()
+
+ envOptions := &options{}
+ for _, opt := range opts {
+ if err := opt(envOptions); err != nil {
+ return nil, err
+ }
+ }
+ aggLitElemType := dynElementType
+ if envOptions.homogeneousAggregateLiterals {
+ aggLitElemType = homogenousElementType
+ }
+ filteredOverloadIDs := crossTypeNumericComparisonOverloads
+ if envOptions.crossTypeNumericComparisons {
+ filteredOverloadIDs = make(map[string]struct{})
+ }
+ if envOptions.validatedDeclarations != nil {
+ declarations = envOptions.validatedDeclarations.Copy()
+ }
+ return &Env{
+ container: container,
+ provider: provider,
+ declarations: declarations,
+ aggLitElemType: aggLitElemType,
+ filteredOverloadIDs: filteredOverloadIDs,
+ }, nil
+}
+
+// AddIdents configures the checker with a list of variable declarations.
+//
+// If there are overlapping declarations, the method will error.
+func (e *Env) AddIdents(declarations ...*decls.VariableDecl) error {
+ errMsgs := make([]errorMsg, 0)
+ for _, d := range declarations {
+ errMsgs = append(errMsgs, e.addIdent(d))
+ }
+ return formatError(errMsgs)
+}
+
+// AddFunctions configures the checker with a list of function declarations.
+//
+// If there are overlapping declarations, the method will error.
+func (e *Env) AddFunctions(declarations ...*decls.FunctionDecl) error {
+ errMsgs := make([]errorMsg, 0)
+ for _, d := range declarations {
+ errMsgs = append(errMsgs, e.setFunction(d)...)
+ }
+ return formatError(errMsgs)
+}
+
+// LookupIdent returns a Decl proto for typeName as an identifier in the Env.
+// Returns nil if no such identifier is found in the Env.
+func (e *Env) LookupIdent(name string) *decls.VariableDecl {
+ for _, candidate := range e.container.ResolveCandidateNames(name) {
+ if ident := e.declarations.FindIdent(candidate); ident != nil {
+ return ident
+ }
+
+ // Next try to import the name as a reference to a message type. If found,
+ // the declaration is added to the outest (global) scope of the
+ // environment, so next time we can access it faster.
+ if t, found := e.provider.FindStructType(candidate); found {
+ decl := decls.NewVariable(candidate, t)
+ e.declarations.AddIdent(decl)
+ return decl
+ }
+
+ if i, found := e.provider.FindIdent(candidate); found {
+ if t, ok := i.(*types.Type); ok {
+ decl := decls.NewVariable(candidate, types.NewTypeTypeWithParam(t))
+ e.declarations.AddIdent(decl)
+ return decl
+ }
+ }
+
+ // Next try to import this as an enum value by splitting the name in a type prefix and
+ // the enum inside.
+ if enumValue := e.provider.EnumValue(candidate); enumValue.Type() != types.ErrType {
+ decl := decls.NewConstant(candidate, types.IntType, enumValue)
+ e.declarations.AddIdent(decl)
+ return decl
+ }
+ }
+ return nil
+}
+
+// LookupFunction returns a Decl proto for typeName as a function in env.
+// Returns nil if no such function is found in env.
+func (e *Env) LookupFunction(name string) *decls.FunctionDecl {
+ for _, candidate := range e.container.ResolveCandidateNames(name) {
+ if fn := e.declarations.FindFunction(candidate); fn != nil {
+ return fn
+ }
+ }
+ return nil
+}
+
+// setFunction adds the function Decl to the Env.
+// Adds a function decl if one doesn't already exist, then adds all overloads from the Decl.
+// If overload overlaps with an existing overload, adds to the errors in the Env instead.
+func (e *Env) setFunction(fn *decls.FunctionDecl) []errorMsg {
+ errMsgs := make([]errorMsg, 0)
+ current := e.declarations.FindFunction(fn.Name())
+ if current != nil {
+ var err error
+ current, err = current.Merge(fn)
+ if err != nil {
+ return append(errMsgs, errorMsg(err.Error()))
+ }
+ } else {
+ current = fn
+ }
+ for _, overload := range current.OverloadDecls() {
+ for _, macro := range parser.AllMacros {
+ if macro.Function() == current.Name() &&
+ macro.IsReceiverStyle() == overload.IsMemberFunction() &&
+ macro.ArgCount() == len(overload.ArgTypes()) {
+ errMsgs = append(errMsgs, overlappingMacroError(current.Name(), macro.ArgCount()))
+ }
+ }
+ if len(errMsgs) > 0 {
+ return errMsgs
+ }
+ }
+ e.declarations.SetFunction(current)
+ return errMsgs
+}
+
+// addIdent adds the Decl to the declarations in the Env.
+// Returns a non-empty errorMsg if the identifier is already declared in the scope.
+func (e *Env) addIdent(decl *decls.VariableDecl) errorMsg {
+ current := e.declarations.FindIdentInScope(decl.Name())
+ if current != nil {
+ if current.DeclarationIsEquivalent(decl) {
+ return ""
+ }
+ return overlappingIdentifierError(decl.Name())
+ }
+ e.declarations.AddIdent(decl)
+ return ""
+}
+
+// isOverloadDisabled returns whether the overloadID is disabled in the current environment.
+func (e *Env) isOverloadDisabled(overloadID string) bool {
+ _, found := e.filteredOverloadIDs[overloadID]
+ return found
+}
+
+// validatedDeclarations returns a reference to the validated variable and function declaration scope stack.
+// must be copied before use.
+func (e *Env) validatedDeclarations() *Scopes {
+ return e.declarations
+}
+
+// enterScope creates a new Env instance with a new innermost declaration scope.
+func (e *Env) enterScope() *Env {
+ childDecls := e.declarations.Push()
+ return &Env{
+ declarations: childDecls,
+ container: e.container,
+ provider: e.provider,
+ aggLitElemType: e.aggLitElemType,
+ }
+}
+
+// exitScope creates a new Env instance with the nearest outer declaration scope.
+func (e *Env) exitScope() *Env {
+ parentDecls := e.declarations.Pop()
+ return &Env{
+ declarations: parentDecls,
+ container: e.container,
+ provider: e.provider,
+ aggLitElemType: e.aggLitElemType,
+ }
+}
+
+// errorMsg is a type alias meant to represent error-based return values which
+// may be accumulated into an error at a later point in execution.
+type errorMsg string
+
+func overlappingIdentifierError(name string) errorMsg {
+ return errorMsg(fmt.Sprintf("overlapping identifier for name '%s'", name))
+}
+
+func overlappingMacroError(name string, argCount int) errorMsg {
+ return errorMsg(fmt.Sprintf(
+ "overlapping macro for name '%s' with %d args", name, argCount))
+}
+
+func formatError(errMsgs []errorMsg) error {
+ errStrs := make([]string, 0)
+ if len(errMsgs) > 0 {
+ for i := 0; i < len(errMsgs); i++ {
+ if errMsgs[i] != "" {
+ errStrs = append(errStrs, string(errMsgs[i]))
+ }
+ }
+ }
+ if len(errStrs) > 0 {
+ return fmt.Errorf("%s", strings.Join(errStrs, "\n"))
+ }
+ return nil
+}
diff --git a/vendor/github.com/authzed/cel-go/checker/errors.go b/vendor/github.com/authzed/cel-go/checker/errors.go
new file mode 100644
index 0000000..04b559d
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/errors.go
@@ -0,0 +1,88 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+import (
+ "github.com/authzed/cel-go/common"
+ "github.com/authzed/cel-go/common/ast"
+ "github.com/authzed/cel-go/common/types"
+)
+
+// typeErrors is a specialization of Errors.
+type typeErrors struct {
+ errs *common.Errors
+}
+
+func (e *typeErrors) fieldTypeMismatch(id int64, l common.Location, name string, field, value *types.Type) {
+ e.errs.ReportErrorAtID(id, l, "expected type of field '%s' is '%s' but provided type is '%s'",
+ name, FormatCELType(field), FormatCELType(value))
+}
+
+func (e *typeErrors) incompatibleType(id int64, l common.Location, ex ast.Expr, prev, next *types.Type) {
+ e.errs.ReportErrorAtID(id, l,
+ "incompatible type already exists for expression: %v(%d) old:%v, new:%v", ex, ex.ID(), prev, next)
+}
+
+func (e *typeErrors) noMatchingOverload(id int64, l common.Location, name string, args []*types.Type, isInstance bool) {
+ signature := formatFunctionDeclType(nil, args, isInstance)
+ e.errs.ReportErrorAtID(id, l, "found no matching overload for '%s' applied to '%s'", name, signature)
+}
+
+func (e *typeErrors) notAComprehensionRange(id int64, l common.Location, t *types.Type) {
+ e.errs.ReportErrorAtID(id, l, "expression of type '%s' cannot be range of a comprehension (must be list, map, or dynamic)",
+ FormatCELType(t))
+}
+
+func (e *typeErrors) notAnOptionalFieldSelection(id int64, l common.Location, field ast.Expr) {
+ e.errs.ReportErrorAtID(id, l, "unsupported optional field selection: %v", field)
+}
+
+func (e *typeErrors) notAType(id int64, l common.Location, typeName string) {
+ e.errs.ReportErrorAtID(id, l, "'%s' is not a type", typeName)
+}
+
+func (e *typeErrors) notAMessageType(id int64, l common.Location, typeName string) {
+ e.errs.ReportErrorAtID(id, l, "'%s' is not a message type", typeName)
+}
+
+func (e *typeErrors) referenceRedefinition(id int64, l common.Location, ex ast.Expr, prev, next *ast.ReferenceInfo) {
+ e.errs.ReportErrorAtID(id, l,
+ "reference already exists for expression: %v(%d) old:%v, new:%v", ex, ex.ID(), prev, next)
+}
+
+func (e *typeErrors) typeDoesNotSupportFieldSelection(id int64, l common.Location, t *types.Type) {
+ e.errs.ReportErrorAtID(id, l, "type '%s' does not support field selection", FormatCELType(t))
+}
+
+func (e *typeErrors) typeMismatch(id int64, l common.Location, expected, actual *types.Type) {
+ e.errs.ReportErrorAtID(id, l, "expected type '%s' but found '%s'",
+ FormatCELType(expected), FormatCELType(actual))
+}
+
+func (e *typeErrors) undefinedField(id int64, l common.Location, field string) {
+ e.errs.ReportErrorAtID(id, l, "undefined field '%s'", field)
+}
+
+func (e *typeErrors) undeclaredReference(id int64, l common.Location, container string, name string) {
+ e.errs.ReportErrorAtID(id, l, "undeclared reference to '%s' (in container '%s')", name, container)
+}
+
+func (e *typeErrors) unexpectedFailedResolution(id int64, l common.Location, typeName string) {
+ e.errs.ReportErrorAtID(id, l, "unexpected failed resolution of '%s'", typeName)
+}
+
+func (e *typeErrors) unexpectedASTType(id int64, l common.Location, kind, typeName string) {
+ e.errs.ReportErrorAtID(id, l, "unexpected %s type: %v", kind, typeName)
+}
diff --git a/vendor/github.com/authzed/cel-go/checker/format.go b/vendor/github.com/authzed/cel-go/checker/format.go
new file mode 100644
index 0000000..65655a4
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/format.go
@@ -0,0 +1,216 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+import (
+ "fmt"
+ "strings"
+
+ chkdecls "github.com/authzed/cel-go/checker/decls"
+ "github.com/authzed/cel-go/common/types"
+
+ exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
+)
+
+const (
+ kindUnknown = iota + 1
+ kindError
+ kindFunction
+ kindDyn
+ kindPrimitive
+ kindWellKnown
+ kindWrapper
+ kindNull
+ kindAbstract
+ kindType
+ kindList
+ kindMap
+ kindObject
+ kindTypeParam
+)
+
+// FormatCheckedType converts a type message into a string representation.
+func FormatCheckedType(t *exprpb.Type) string {
+ switch kindOf(t) {
+ case kindDyn:
+ return "dyn"
+ case kindFunction:
+ return formatFunctionExprType(t.GetFunction().GetResultType(),
+ t.GetFunction().GetArgTypes(),
+ false)
+ case kindList:
+ return fmt.Sprintf("list(%s)", FormatCheckedType(t.GetListType().GetElemType()))
+ case kindObject:
+ return t.GetMessageType()
+ case kindMap:
+ return fmt.Sprintf("map(%s, %s)",
+ FormatCheckedType(t.GetMapType().GetKeyType()),
+ FormatCheckedType(t.GetMapType().GetValueType()))
+ case kindNull:
+ return "null"
+ case kindPrimitive:
+ switch t.GetPrimitive() {
+ case exprpb.Type_UINT64:
+ return "uint"
+ case exprpb.Type_INT64:
+ return "int"
+ }
+ return strings.Trim(strings.ToLower(t.GetPrimitive().String()), " ")
+ case kindType:
+ if t.GetType() == nil || t.GetType().GetTypeKind() == nil {
+ return "type"
+ }
+ return fmt.Sprintf("type(%s)", FormatCheckedType(t.GetType()))
+ case kindWellKnown:
+ switch t.GetWellKnown() {
+ case exprpb.Type_ANY:
+ return "any"
+ case exprpb.Type_DURATION:
+ return "duration"
+ case exprpb.Type_TIMESTAMP:
+ return "timestamp"
+ }
+ case kindWrapper:
+ return fmt.Sprintf("wrapper(%s)",
+ FormatCheckedType(chkdecls.NewPrimitiveType(t.GetWrapper())))
+ case kindError:
+ return "!error!"
+ case kindTypeParam:
+ return t.GetTypeParam()
+ case kindAbstract:
+ at := t.GetAbstractType()
+ params := at.GetParameterTypes()
+ paramStrs := make([]string, len(params))
+ for i, p := range params {
+ paramStrs[i] = FormatCheckedType(p)
+ }
+ return fmt.Sprintf("%s(%s)", at.GetName(), strings.Join(paramStrs, ", "))
+ }
+ return t.String()
+}
+
+type formatter func(any) string
+
+// FormatCELType formats a types.Type value to a string representation.
+//
+// The type formatting is identical to FormatCheckedType.
+func FormatCELType(t any) string {
+ dt := t.(*types.Type)
+ switch dt.Kind() {
+ case types.AnyKind:
+ return "any"
+ case types.DurationKind:
+ return "duration"
+ case types.ErrorKind:
+ return "!error!"
+ case types.NullTypeKind:
+ return "null"
+ case types.TimestampKind:
+ return "timestamp"
+ case types.TypeParamKind:
+ return dt.TypeName()
+ case types.OpaqueKind:
+ if dt.TypeName() == "function" {
+ // There is no explicit function type in the new types representation, so information like
+ // whether the function is a member function is absent.
+ return formatFunctionDeclType(dt.Parameters()[0], dt.Parameters()[1:], false)
+ }
+ case types.UnspecifiedKind:
+ return ""
+ }
+ if len(dt.Parameters()) == 0 {
+ return dt.DeclaredTypeName()
+ }
+ paramTypeNames := make([]string, 0, len(dt.Parameters()))
+ for _, p := range dt.Parameters() {
+ paramTypeNames = append(paramTypeNames, FormatCELType(p))
+ }
+ return fmt.Sprintf("%s(%s)", dt.TypeName(), strings.Join(paramTypeNames, ", "))
+}
+
+func formatExprType(t any) string {
+ if t == nil {
+ return ""
+ }
+ return FormatCheckedType(t.(*exprpb.Type))
+}
+
+func formatFunctionExprType(resultType *exprpb.Type, argTypes []*exprpb.Type, isInstance bool) string {
+ return formatFunctionInternal[*exprpb.Type](resultType, argTypes, isInstance, formatExprType)
+}
+
+func formatFunctionDeclType(resultType *types.Type, argTypes []*types.Type, isInstance bool) string {
+ return formatFunctionInternal[*types.Type](resultType, argTypes, isInstance, FormatCELType)
+}
+
+func formatFunctionInternal[T any](resultType T, argTypes []T, isInstance bool, format formatter) string {
+ result := ""
+ if isInstance {
+ target := argTypes[0]
+ argTypes = argTypes[1:]
+ result += format(target)
+ result += "."
+ }
+ result += "("
+ for i, arg := range argTypes {
+ if i > 0 {
+ result += ", "
+ }
+ result += format(arg)
+ }
+ result += ")"
+ rt := format(resultType)
+ if rt != "" {
+ result += " -> "
+ result += rt
+ }
+ return result
+}
+
+// kindOf returns the kind of the type as defined in the checked.proto.
+func kindOf(t *exprpb.Type) int {
+ if t == nil || t.TypeKind == nil {
+ return kindUnknown
+ }
+ switch t.GetTypeKind().(type) {
+ case *exprpb.Type_Error:
+ return kindError
+ case *exprpb.Type_Function:
+ return kindFunction
+ case *exprpb.Type_Dyn:
+ return kindDyn
+ case *exprpb.Type_Primitive:
+ return kindPrimitive
+ case *exprpb.Type_WellKnown:
+ return kindWellKnown
+ case *exprpb.Type_Wrapper:
+ return kindWrapper
+ case *exprpb.Type_Null:
+ return kindNull
+ case *exprpb.Type_Type:
+ return kindType
+ case *exprpb.Type_ListType_:
+ return kindList
+ case *exprpb.Type_MapType_:
+ return kindMap
+ case *exprpb.Type_MessageType:
+ return kindObject
+ case *exprpb.Type_TypeParam:
+ return kindTypeParam
+ case *exprpb.Type_AbstractType_:
+ return kindAbstract
+ }
+ return kindUnknown
+}
diff --git a/vendor/github.com/authzed/cel-go/checker/mapping.go b/vendor/github.com/authzed/cel-go/checker/mapping.go
new file mode 100644
index 0000000..d5b3bdf
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/mapping.go
@@ -0,0 +1,49 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+import (
+ "github.com/authzed/cel-go/common/types"
+)
+
+type mapping struct {
+ mapping map[string]*types.Type
+}
+
+func newMapping() *mapping {
+ return &mapping{
+ mapping: make(map[string]*types.Type),
+ }
+}
+
+func (m *mapping) add(from, to *types.Type) {
+ m.mapping[FormatCELType(from)] = to
+}
+
+func (m *mapping) find(from *types.Type) (*types.Type, bool) {
+ if r, found := m.mapping[FormatCELType(from)]; found {
+ return r, found
+ }
+ return nil, false
+}
+
+func (m *mapping) copy() *mapping {
+ c := newMapping()
+
+ for k, v := range m.mapping {
+ c.mapping[k] = v
+ }
+ return c
+}
diff --git a/vendor/github.com/authzed/cel-go/checker/options.go b/vendor/github.com/authzed/cel-go/checker/options.go
new file mode 100644
index 0000000..0560c38
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/options.go
@@ -0,0 +1,42 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+type options struct {
+ crossTypeNumericComparisons bool
+ homogeneousAggregateLiterals bool
+ validatedDeclarations *Scopes
+}
+
+// Option is a functional option for configuring the type-checker
+type Option func(*options) error
+
+// CrossTypeNumericComparisons toggles type-checker support for numeric comparisons across type
+// See https://github.com/google/cel-spec/wiki/proposal-210 for more details.
+func CrossTypeNumericComparisons(enabled bool) Option {
+ return func(opts *options) error {
+ opts.crossTypeNumericComparisons = enabled
+ return nil
+ }
+}
+
+// ValidatedDeclarations provides a references to validated declarations which will be copied
+// into new checker instances.
+func ValidatedDeclarations(env *Env) Option {
+ return func(opts *options) error {
+ opts.validatedDeclarations = env.validatedDeclarations()
+ return nil
+ }
+}
diff --git a/vendor/github.com/authzed/cel-go/checker/printer.go b/vendor/github.com/authzed/cel-go/checker/printer.go
new file mode 100644
index 0000000..deadde3
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/printer.go
@@ -0,0 +1,74 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+import (
+ "sort"
+
+ "github.com/authzed/cel-go/common/ast"
+ "github.com/authzed/cel-go/common/debug"
+)
+
+type semanticAdorner struct {
+ checked *ast.AST
+}
+
+var _ debug.Adorner = &semanticAdorner{}
+
+func (a *semanticAdorner) GetMetadata(elem any) string {
+ result := ""
+ e, isExpr := elem.(ast.Expr)
+ if !isExpr {
+ return result
+ }
+ t := a.checked.TypeMap()[e.ID()]
+ if t != nil {
+ result += "~"
+ result += FormatCELType(t)
+ }
+
+ switch e.Kind() {
+ case ast.IdentKind,
+ ast.CallKind,
+ ast.ListKind,
+ ast.StructKind,
+ ast.SelectKind:
+ if ref, found := a.checked.ReferenceMap()[e.ID()]; found {
+ if len(ref.OverloadIDs) == 0 {
+ result += "^" + ref.Name
+ } else {
+ sort.Strings(ref.OverloadIDs)
+ for i, overload := range ref.OverloadIDs {
+ if i == 0 {
+ result += "^"
+ } else {
+ result += "|"
+ }
+ result += overload
+ }
+ }
+ }
+ }
+
+ return result
+}
+
+// Print returns a string representation of the Expr message,
+// annotated with types from the CheckedExpr. The Expr must
+// be a sub-expression embedded in the CheckedExpr.
+func Print(e ast.Expr, checked *ast.AST) string {
+ a := &semanticAdorner{checked: checked}
+ return debug.ToAdornedDebugString(e, a)
+}
diff --git a/vendor/github.com/authzed/cel-go/checker/scopes.go b/vendor/github.com/authzed/cel-go/checker/scopes.go
new file mode 100644
index 0000000..4bb7f4f
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/scopes.go
@@ -0,0 +1,147 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+import (
+ "github.com/authzed/cel-go/common/decls"
+)
+
+// Scopes represents nested Decl sets where the Scopes value contains a Groups containing all
+// identifiers in scope and an optional parent representing outer scopes.
+// Each Groups value is a mapping of names to Decls in the ident and function namespaces.
+// Lookups are performed such that bindings in inner scopes shadow those in outer scopes.
+type Scopes struct {
+ parent *Scopes
+ scopes *Group
+}
+
+// newScopes creates a new, empty Scopes.
+// Some operations can't be safely performed until a Group is added with Push.
+func newScopes() *Scopes {
+ return &Scopes{
+ scopes: newGroup(),
+ }
+}
+
+// Copy creates a copy of the current Scopes values, including a copy of its parent if non-nil.
+func (s *Scopes) Copy() *Scopes {
+ cpy := newScopes()
+ if s == nil {
+ return cpy
+ }
+ if s.parent != nil {
+ cpy.parent = s.parent.Copy()
+ }
+ cpy.scopes = s.scopes.copy()
+ return cpy
+}
+
+// Push creates a new Scopes value which references the current Scope as its parent.
+func (s *Scopes) Push() *Scopes {
+ return &Scopes{
+ parent: s,
+ scopes: newGroup(),
+ }
+}
+
+// Pop returns the parent Scopes value for the current scope, or the current scope if the parent
+// is nil.
+func (s *Scopes) Pop() *Scopes {
+ if s.parent != nil {
+ return s.parent
+ }
+ // TODO: Consider whether this should be an error / panic.
+ return s
+}
+
+// AddIdent adds the ident Decl in the current scope.
+// Note: If the name collides with an existing identifier in the scope, the Decl is overwritten.
+func (s *Scopes) AddIdent(decl *decls.VariableDecl) {
+ s.scopes.idents[decl.Name()] = decl
+}
+
+// FindIdent finds the first ident Decl with a matching name in Scopes, or nil if one cannot be
+// found.
+// Note: The search is performed from innermost to outermost.
+func (s *Scopes) FindIdent(name string) *decls.VariableDecl {
+ if ident, found := s.scopes.idents[name]; found {
+ return ident
+ }
+ if s.parent != nil {
+ return s.parent.FindIdent(name)
+ }
+ return nil
+}
+
+// FindIdentInScope finds the first ident Decl with a matching name in the current Scopes value, or
+// nil if one does not exist.
+// Note: The search is only performed on the current scope and does not search outer scopes.
+func (s *Scopes) FindIdentInScope(name string) *decls.VariableDecl {
+ if ident, found := s.scopes.idents[name]; found {
+ return ident
+ }
+ return nil
+}
+
+// SetFunction adds the function Decl to the current scope.
+// Note: Any previous entry for a function in the current scope with the same name is overwritten.
+func (s *Scopes) SetFunction(fn *decls.FunctionDecl) {
+ s.scopes.functions[fn.Name()] = fn
+}
+
+// FindFunction finds the first function Decl with a matching name in Scopes.
+// The search is performed from innermost to outermost.
+// Returns nil if no such function in Scopes.
+func (s *Scopes) FindFunction(name string) *decls.FunctionDecl {
+ if fn, found := s.scopes.functions[name]; found {
+ return fn
+ }
+ if s.parent != nil {
+ return s.parent.FindFunction(name)
+ }
+ return nil
+}
+
+// Group is a set of Decls that is pushed on or popped off a Scopes as a unit.
+// Contains separate namespaces for identifier and function Decls.
+// (Should be named "Scope" perhaps?)
+type Group struct {
+ idents map[string]*decls.VariableDecl
+ functions map[string]*decls.FunctionDecl
+}
+
+// copy creates a new Group instance with a shallow copy of the variables and functions.
+// If callers need to mutate the exprpb.Decl definitions for a Function, they should copy-on-write.
+func (g *Group) copy() *Group {
+ cpy := &Group{
+ idents: make(map[string]*decls.VariableDecl, len(g.idents)),
+ functions: make(map[string]*decls.FunctionDecl, len(g.functions)),
+ }
+ for n, id := range g.idents {
+ cpy.idents[n] = id
+ }
+ for n, fn := range g.functions {
+ cpy.functions[n] = fn
+ }
+ return cpy
+}
+
+// newGroup creates a new Group with empty maps for identifiers and functions.
+func newGroup() *Group {
+ return &Group{
+ idents: make(map[string]*decls.VariableDecl),
+ functions: make(map[string]*decls.FunctionDecl),
+ }
+}
diff --git a/vendor/github.com/authzed/cel-go/checker/types.go b/vendor/github.com/authzed/cel-go/checker/types.go
new file mode 100644
index 0000000..e94d8c7
--- /dev/null
+++ b/vendor/github.com/authzed/cel-go/checker/types.go
@@ -0,0 +1,314 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package checker
+
+import (
+ "github.com/authzed/cel-go/common/types"
+)
+
+// isDyn returns true if the input t is either type DYN or a well-known ANY message.
+func isDyn(t *types.Type) bool {
+ // Note: object type values that are well-known and map to a DYN value in practice
+ // are sanitized prior to being added to the environment.
+ switch t.Kind() {
+ case types.DynKind, types.AnyKind:
+ return true
+ default:
+ return false
+ }
+}
+
+// isDynOrError returns true if the input is either an Error, DYN, or well-known ANY message.
+func isDynOrError(t *types.Type) bool {
+ return isError(t) || isDyn(t)
+}
+
+func isError(t *types.Type) bool {
+ return t.Kind() == types.ErrorKind
+}
+
+func isOptional(t *types.Type) bool {
+ if t.Kind() == types.OpaqueKind {
+ return t.TypeName() == "optional_type"
+ }
+ return false
+}
+
+func maybeUnwrapOptional(t *types.Type) (*types.Type, bool) {
+ if isOptional(t) {
+ return t.Parameters()[0], true
+ }
+ return t, false
+}
+
+// isEqualOrLessSpecific checks whether one type is equal or less specific than the other one.
+// A type is less specific if it matches the other type using the DYN type.
+func isEqualOrLessSpecific(t1, t2 *types.Type) bool {
+ kind1, kind2 := t1.Kind(), t2.Kind()
+ // The first type is less specific.
+ if isDyn(t1) || kind1 == types.TypeParamKind {
+ return true
+ }
+ // The first type is not less specific.
+ if isDyn(t2) || kind2 == types.TypeParamKind {
+ return false
+ }
+ // Types must be of the same kind to be equal.
+ if kind1 != kind2 {
+ return false
+ }
+
+ // With limited exceptions for ANY and JSON values, the types must agree and be equivalent in
+ // order to return true.
+ switch kind1 {
+ case types.OpaqueKind:
+ if t1.TypeName() != t2.TypeName() ||
+ len(t1.Parameters()) != len(t2.Parameters()) {
+ return false
+ }
+ for i, p1 := range t1.Parameters() {
+ if !isEqualOrLessSpecific(p1, t2.Parameters()[i]) {
+ return false
+ }
+ }
+ return true
+ case types.ListKind:
+ return isEqualOrLessSpecific(t1.Parameters()[0], t2.Parameters()[0])
+ case types.MapKind:
+ return isEqualOrLessSpecific(t1.Parameters()[0], t2.Parameters()[0]) &&
+ isEqualOrLessSpecific(t1.Parameters()[1], t2.Parameters()[1])
+ case types.TypeKind:
+ return true
+ default:
+ return t1.IsExactType(t2)
+ }
+}
+
+// / internalIsAssignable returns true if t1 is assignable to t2.
+func internalIsAssignable(m *mapping, t1, t2 *types.Type) bool {
+ // Process type parameters.
+ kind1, kind2 := t1.Kind(), t2.Kind()
+ if kind2 == types.TypeParamKind {
+ // If t2 is a valid type substitution for t1, return true.
+ valid, t2HasSub := isValidTypeSubstitution(m, t1, t2)
+ if valid {
+ return true
+ }
+ // If t2 is not a valid type sub for t1, and already has a known substitution return false
+ // since it is not possible for t1 to be a substitution for t2.
+ if !valid && t2HasSub {
+ return false
+ }
+ // Otherwise, fall through to check whether t1 is a possible substitution for t2.
+ }
+ if kind1 == types.TypeParamKind {
+ // Return whether t1 is a valid substitution for t2. If not, do no additional checks as the
+ // possible type substitutions have been searched in both directions.
+ valid, _ := isValidTypeSubstitution(m, t2, t1)
+ return valid
+ }
+
+ // Next check for wildcard types.
+ if isDynOrError(t1) || isDynOrError(t2) {
+ return true
+ }
+ // Preserve the nullness checks of the legacy type-checker.
+ if kind1 == types.NullTypeKind {
+ return internalIsAssignableNull(t2)
+ }
+ if kind2 == types.NullTypeKind {
+ return internalIsAssignableNull(t1)
+ }
+
+ // Test for when the types do not need to agree, but are more specific than dyn.
+ switch kind1 {
+ case types.BoolKind, types.BytesKind, types.DoubleKind, types.IntKind, types.StringKind, types.UintKind,
+ types.AnyKind, types.DurationKind, types.TimestampKind,
+ types.StructKind:
+ // Test whether t2 is assignable from t1. The order of this check won't usually matter;
+ // however, there may be cases where type capabilities are expanded beyond what is supported
+ // in the current common/types package. For example, an interface designation for a group of
+ // Struct types.
+ return t2.IsAssignableType(t1)
+ case types.TypeKind:
+ return kind2 == types.TypeKind
+ case types.OpaqueKind, types.ListKind, types.MapKind:
+ return t1.Kind() == t2.Kind() && t1.TypeName() == t2.TypeName() &&
+ internalIsAssignableList(m, t1.Parameters(), t2.Parameters())
+ default:
+ return false
+ }
+}
+
+// isValidTypeSubstitution returns whether t2 (or its type substitution) is a valid type
+// substitution for t1, and whether t2 has a type substitution in mapping m.
+//
+// The type t2 is a valid substitution for t1 if any of the following statements is true
+// - t2 has a type substitution (t2sub) equal to t1
+// - t2 has a type substitution (t2sub) assignable to t1
+// - t2 does not occur within t1.
+func isValidTypeSubstitution(m *mapping, t1, t2 *types.Type) (valid, hasSub bool) {
+ // Early return if the t1 and t2 are the same instance.
+ kind1, kind2 := t1.Kind(), t2.Kind()
+ if kind1 == kind2 && t1.IsExactType(t2) {
+ return true, true
+ }
+ if t2Sub, found := m.find(t2); found {
+ // Early return if t1 and t2Sub are the same instance as otherwise the mapping
+ // might mark a type as being a subtitution for itself.
+ if kind1 == t2Sub.Kind() && t1.IsExactType(t2Sub) {
+ return true, true
+ }
+ // If the types are compatible, pick the more general type and return true
+ if internalIsAssignable(m, t1, t2Sub) {
+ t2New := mostGeneral(t1, t2Sub)
+ // only update the type reference map if the target type does not occur within it.
+ if notReferencedIn(m, t2, t2New) {
+ m.add(t2, t2New)
+ }
+ // acknowledge the type agreement, and that the substitution is already tracked.
+ return true, true
+ }
+ return false, true
+ }
+ if notReferencedIn(m, t2, t1) {
+ m.add(t2, t1)
+ return true, false
+ }
+ return false, false
+}
+
+// internalIsAssignableList returns true if the element types at each index in the list are
+// assignable from l1[i] to l2[i]. The list lengths must also agree for the lists to be
+// assignable.
+func internalIsAssignableList(m *mapping, l1, l2 []*types.Type) bool {
+ if len(l1) != len(l2) {
+ return false
+ }
+ for i, t1 := range l1 {
+ if !internalIsAssignable(m, t1, l2[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// internalIsAssignableNull returns true if the type is nullable.
+func internalIsAssignableNull(t *types.Type) bool {
+ return isLegacyNullable(t) || t.IsAssignableType(types.NullType)
+}
+
+// isLegacyNullable preserves the null-ness compatibility of the original type-checker implementation.
+func isLegacyNullable(t *types.Type) bool {
+ switch t.Kind() {
+ case types.OpaqueKind, types.StructKind, types.AnyKind, types.DurationKind, types.TimestampKind:
+ return true
+ }
+ return false
+}
+
+// isAssignable returns an updated type substitution mapping if t1 is assignable to t2.
+func isAssignable(m *mapping, t1, t2 *types.Type) *mapping {
+ mCopy := m.copy()
+ if internalIsAssignable(mCopy, t1, t2) {
+ return mCopy
+ }
+ return nil
+}
+
+// isAssignableList returns an updated type substitution mapping if l1 is assignable to l2.
+func isAssignableList(m *mapping, l1, l2 []*types.Type) *mapping {
+ mCopy := m.copy()
+ if internalIsAssignableList(mCopy, l1, l2) {
+ return mCopy
+ }
+ return nil
+}
+
+// mostGeneral returns the more general of two types which are known to unify.
+func mostGeneral(t1, t2 *types.Type) *types.Type {
+ if isEqualOrLessSpecific(t1, t2) {
+ return t1
+ }
+ return t2
+}
+
+// notReferencedIn checks whether the type doesn't appear directly or transitively within the other
+// type. This is a standard requirement for type unification, commonly referred to as the "occurs
+// check".
+func notReferencedIn(m *mapping, t, withinType *types.Type) bool {
+ if t.IsExactType(withinType) {
+ return false
+ }
+ withinKind := withinType.Kind()
+ switch withinKind {
+ case types.TypeParamKind:
+ wtSub, found := m.find(withinType)
+ if !found {
+ return true
+ }
+ return notReferencedIn(m, t, wtSub)
+ case types.OpaqueKind, types.ListKind, types.MapKind, types.TypeKind:
+ for _, pt := range withinType.Parameters() {
+ if !notReferencedIn(m, t, pt) {
+ return false
+ }
+ }
+ return true
+ default:
+ return true
+ }
+}
+
+// substitute replaces all direct and indirect occurrences of bound type parameters. Unbound type
+// parameters are replaced by DYN if typeParamToDyn is true.
+func substitute(m *mapping, t *types.Type, typeParamToDyn bool) *types.Type {
+ if tSub, found := m.find(t); found {
+ return substitute(m, tSub, typeParamToDyn)
+ }
+ kind := t.Kind()
+ if typeParamToDyn && kind == types.TypeParamKind {
+ return types.DynType
+ }
+ switch kind {
+ case types.OpaqueKind:
+ return types.NewOpaqueType(t.TypeName(), substituteParams(m, t.Parameters(), typeParamToDyn)...)
+ case types.ListKind:
+ return types.NewListType(substitute(m, t.Parameters()[0], typeParamToDyn))
+ case types.MapKind:
+ return types.NewMapType(substitute(m, t.Parameters()[0], typeParamToDyn),
+ substitute(m, t.Parameters()[1], typeParamToDyn))
+ case types.TypeKind:
+ if len(t.Parameters()) > 0 {
+ tParam := t.Parameters()[0]
+ return types.NewTypeTypeWithParam(substitute(m, tParam, typeParamToDyn))
+ }
+ return t
+ default:
+ return t
+ }
+}
+
+func substituteParams(m *mapping, typeParams []*types.Type, typeParamToDyn bool) []*types.Type {
+ subParams := make([]*types.Type, len(typeParams))
+ for i, tp := range typeParams {
+ subParams[i] = substitute(m, tp, typeParamToDyn)
+ }
+ return subParams
+}
+
+func newFunctionType(resultType *types.Type, argTypes ...*types.Type) *types.Type {
+ return types.NewOpaqueType("function", append([]*types.Type{resultType}, argTypes...)...)
+}