mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-08-23 22:26:49 +00:00
Bumps [github.com/google/cel-go](https://github.com/google/cel-go) from 0.27.0 to 0.28.0. - [Release notes](https://github.com/google/cel-go/releases) - [Commits](https://github.com/google/cel-go/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: github.com/google/cel-go dependency-version: 0.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
376 lines
12 KiB
Go
376 lines
12 KiB
Go
// 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/google/cel-go/common/containers"
|
|
"github.com/google/cel-go/common/decls"
|
|
"github.com/google/cel-go/common/overloads"
|
|
"github.com/google/cel-go/common/types"
|
|
"github.com/google/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{}
|
|
jsonFieldNames bool
|
|
}
|
|
|
|
// 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,
|
|
jsonFieldNames: envOptions.jsonFieldNames,
|
|
}, 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)
|
|
}
|
|
|
|
// newAttrResolution creates a new attribute resolution value.
|
|
func newAttrResolution(ident *decls.VariableDecl, requiresDisambiguation bool) *attributeResolution {
|
|
return &attributeResolution{
|
|
VariableDecl: ident,
|
|
requiresDisambiguation: requiresDisambiguation,
|
|
}
|
|
}
|
|
|
|
// attributeResolution wraps an existing variable and denotes whether disambiguation is needed
|
|
// during variable resolution.
|
|
type attributeResolution struct {
|
|
*decls.VariableDecl
|
|
|
|
// requiresDisambiguation indicates the variable name should be dot-prefixed.
|
|
requiresDisambiguation bool
|
|
}
|
|
|
|
// resolveSimpleIdent determines the resolved attribute for a single identifier.
|
|
func (e *Env) resolveSimpleIdent(name string) *attributeResolution {
|
|
local := e.lookupLocalIdent(name)
|
|
if local != nil && !strings.HasPrefix(name, ".") {
|
|
return newAttrResolution(local, false)
|
|
}
|
|
for _, candidate := range e.container.ResolveCandidateNames(name) {
|
|
if ident := e.lookupGlobalIdent(candidate); ident != nil {
|
|
return newAttrResolution(ident, local != nil)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// resolveQualifiedIdent determines the resolved attribute for a qualified identifier.
|
|
func (e *Env) resolveQualifiedIdent(qualifiers ...string) *attributeResolution {
|
|
if len(qualifiers) == 1 {
|
|
return e.resolveSimpleIdent(qualifiers[0])
|
|
}
|
|
local := e.lookupLocalIdent(qualifiers[0])
|
|
if local != nil && !strings.HasPrefix(qualifiers[0], ".") {
|
|
// this should resolve through a field selection rather than a qualified identifier
|
|
return nil
|
|
}
|
|
// The qualifiers are concatenated together to indicate the qualified name to search
|
|
// for as a global identifier. Since select expressions are resolved from leaf to root
|
|
// if the fully concatenated string doesn't match a global identifier, indicate that
|
|
// no variable was found to continue the traversal up to the next simpler name.
|
|
varName := strings.Join(qualifiers, ".")
|
|
for _, candidate := range e.container.ResolveCandidateNames(varName) {
|
|
if ident := e.lookupGlobalIdent(candidate); ident != nil {
|
|
return newAttrResolution(ident, local != nil)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// resolveTypeIdent 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) resolveTypeIdent(name string) *decls.VariableDecl {
|
|
for _, candidate := range e.container.ResolveCandidateNames(name) {
|
|
// Try to import the name as a reference to a message type.
|
|
if i, found := e.provider.FindIdent(candidate); found {
|
|
if t, ok := i.(*types.Type); ok {
|
|
return decls.NewVariable(candidate, types.NewTypeTypeWithParam(t))
|
|
}
|
|
}
|
|
// Next, try to find the struct type.
|
|
if t, found := e.provider.FindStructType(candidate); found {
|
|
return decls.NewVariable(candidate, t)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// lookupLocalIdent finds the variable candidate in a local scope, returning nil if
|
|
// the candidate variable name is not a local variable.
|
|
func (e *Env) lookupLocalIdent(candidate string) *decls.VariableDecl {
|
|
return e.declarations.FindLocalIdent(candidate)
|
|
}
|
|
|
|
// lookupGlobalIdent finds a candidate variable name in the root scope, returning
|
|
// nil if the identifier is not in the global scope.
|
|
func (e *Env) lookupGlobalIdent(candidate string) *decls.VariableDecl {
|
|
// Try to resolve the global identifier first.
|
|
if ident := e.declarations.FindGlobalIdent(candidate); ident != nil {
|
|
return ident
|
|
}
|
|
// Next try to import the name as a reference to a message type.
|
|
if i, found := e.provider.FindIdent(candidate); found {
|
|
if t, ok := i.(*types.Type); ok {
|
|
return decls.NewVariable(candidate, types.NewTypeTypeWithParam(t))
|
|
}
|
|
}
|
|
if t, found := e.provider.FindStructType(candidate); found {
|
|
return decls.NewVariable(candidate, t)
|
|
}
|
|
// 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 {
|
|
return decls.NewConstant(candidate, types.IntType, enumValue)
|
|
}
|
|
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
|
|
}
|
|
|
|
func maybeMergeConstant(a *decls.VariableDecl, b *decls.VariableDecl) (*decls.VariableDecl, errorMsg) {
|
|
if b.Value() != nil {
|
|
if a.Value() == nil {
|
|
return b, ""
|
|
}
|
|
eq, ok := a.Value().Equal(b.Value()).Value().(bool)
|
|
if ok && eq {
|
|
return a, ""
|
|
}
|
|
return nil, constantConflictError(b.Name())
|
|
}
|
|
return a, ""
|
|
}
|
|
|
|
// 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) {
|
|
decl, errMsg := maybeMergeConstant(current, decl)
|
|
if errMsg != "" {
|
|
return errMsg
|
|
}
|
|
e.declarations.AddIdent(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 constantConflictError(name string) errorMsg {
|
|
return errorMsg(fmt.Sprintf("conflicting constant definitions for name '%s'", name))
|
|
}
|
|
|
|
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
|
|
}
|