| // 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 interpreter |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "github.com/google/cel-go/common/ast" |
| "github.com/google/cel-go/common/containers" |
| "github.com/google/cel-go/common/functions" |
| "github.com/google/cel-go/common/operators" |
| "github.com/google/cel-go/common/types" |
| ) |
| |
| // newPlanner creates an interpretablePlanner which references a Dispatcher, TypeProvider, |
| // TypeAdapter, Container, and CheckedExpr value. These pieces of data are used to resolve |
| // functions, types, and namespaced identifiers at plan time rather than at runtime since |
| // it only needs to be done once and may be semi-expensive to compute. |
| func newPlanner(disp Dispatcher, |
| provider types.Provider, |
| adapter types.Adapter, |
| attrFactory AttributeFactory, |
| cont *containers.Container, |
| exprAST *ast.AST) *planner { |
| return &planner{ |
| disp: disp, |
| provider: provider, |
| adapter: adapter, |
| attrFactory: attrFactory, |
| container: cont, |
| refMap: exprAST.ReferenceMap(), |
| typeMap: exprAST.TypeMap(), |
| decorators: make([]InterpretableDecorator, 0), |
| observers: make([]StatefulObserver, 0), |
| } |
| } |
| |
| // planner is an implementation of the interpretablePlanner interface. |
| type planner struct { |
| disp Dispatcher |
| provider types.Provider |
| adapter types.Adapter |
| attrFactory AttributeFactory |
| container *containers.Container |
| refMap map[int64]*ast.ReferenceInfo |
| typeMap map[int64]*types.Type |
| decorators []InterpretableDecorator |
| observers []StatefulObserver |
| } |
| |
| type planBuilder struct { |
| *planner |
| |
| localVars map[string]int |
| } |
| |
| // Plan implements the interpretablePlanner interface. This implementation of the Plan method also |
| // applies decorators to each Interpretable generated as part of the overall plan. Decorators are |
| // useful for layering functionality into the evaluation that is not natively understood by CEL, |
| // such as state-tracking, expression re-write, and possibly efficient thread-safe memoization of |
| // repeated expressions. |
| func (p *planner) Plan(expr ast.Expr) (Interpretable, error) { |
| pb := &planBuilder{planner: p, localVars: make(map[string]int)} |
| i, err := pb.plan(expr) |
| if err != nil { |
| return nil, err |
| } |
| if len(p.observers) == 0 { |
| return i, nil |
| } |
| return &ObservableInterpretable{Interpretable: i, observers: p.observers}, nil |
| } |
| |
| func (p *planBuilder) plan(expr ast.Expr) (Interpretable, error) { |
| switch expr.Kind() { |
| case ast.CallKind: |
| return p.decorate(p.planCall(expr)) |
| case ast.IdentKind: |
| return p.decorate(p.planIdent(expr)) |
| case ast.LiteralKind: |
| return p.decorate(p.planConst(expr)) |
| case ast.SelectKind: |
| return p.decorate(p.planSelect(expr)) |
| case ast.ListKind: |
| return p.decorate(p.planCreateList(expr)) |
| case ast.MapKind: |
| return p.decorate(p.planCreateMap(expr)) |
| case ast.StructKind: |
| return p.decorate(p.planCreateStruct(expr)) |
| case ast.ComprehensionKind: |
| return p.decorate(p.planComprehension(expr)) |
| } |
| return nil, fmt.Errorf("unsupported expr: %v", expr) |
| } |
| |
| // decorate applies the InterpretableDecorator functions to the given Interpretable. |
| // Both the Interpretable and error generated by a Plan step are accepted as arguments |
| // for convenience. |
| func (p *planBuilder) decorate(i Interpretable, err error) (Interpretable, error) { |
| if err != nil { |
| return nil, err |
| } |
| for _, dec := range p.decorators { |
| i, err = dec(i) |
| if err != nil { |
| return nil, err |
| } |
| } |
| return i, nil |
| } |
| |
| // planIdent creates an Interpretable that resolves an identifier from an Activation. |
| func (p *planBuilder) planIdent(expr ast.Expr) (Interpretable, error) { |
| // Establish whether the identifier is in the reference map. |
| if identRef, found := p.refMap[expr.ID()]; found { |
| return p.planCheckedIdent(expr.ID(), identRef) |
| } |
| // Create the possible attribute list for the unresolved reference. |
| ident := expr.AsIdent() |
| if p.isLocalVar(ident) { |
| return &evalAttr{ |
| adapter: p.adapter, |
| attr: p.attrFactory.AbsoluteAttribute(expr.ID(), ident), |
| }, nil |
| } |
| return &evalAttr{ |
| adapter: p.adapter, |
| attr: p.attrFactory.MaybeAttribute(expr.ID(), ident), |
| }, nil |
| } |
| |
| func (p *planBuilder) planCheckedIdent(id int64, identRef *ast.ReferenceInfo) (Interpretable, error) { |
| // Plan a constant reference if this is the case for this simple identifier. |
| if identRef.Value != nil { |
| return NewConstValue(id, identRef.Value), nil |
| } |
| |
| // Check to see whether the type map indicates this is a type name. All types should be |
| // registered with the provider. |
| cType := p.typeMap[id] |
| if cType.Kind() == types.TypeKind { |
| cVal, found := p.provider.FindIdent(identRef.Name) |
| if !found { |
| return nil, fmt.Errorf("reference to undefined type: %s", identRef.Name) |
| } |
| return NewConstValue(id, cVal), nil |
| } |
| |
| // Otherwise, return the attribute for the resolved identifier name. |
| return &evalAttr{ |
| adapter: p.adapter, |
| attr: p.attrFactory.AbsoluteAttribute(id, identRef.Name), |
| }, nil |
| } |
| |
| // planSelect creates an Interpretable with either: |
| // |
| // a) selects a field from a map or proto. |
| // b) creates a field presence test for a select within a has() macro. |
| // c) resolves the select expression to a namespaced identifier. |
| func (p *planBuilder) planSelect(expr ast.Expr) (Interpretable, error) { |
| // If the Select id appears in the reference map from the CheckedExpr proto then it is either |
| // a namespaced identifier or enum value. |
| if identRef, found := p.refMap[expr.ID()]; found { |
| return p.planCheckedIdent(expr.ID(), identRef) |
| } |
| |
| sel := expr.AsSelect() |
| // Plan the operand evaluation. |
| op, err := p.plan(sel.Operand()) |
| if err != nil { |
| return nil, err |
| } |
| opType := p.typeMap[sel.Operand().ID()] |
| |
| // If the Select was marked TestOnly, this is a presence test. |
| // |
| // Note: presence tests are defined for structured (e.g. proto) and dynamic values (map, json) |
| // as follows: |
| // - True if the object field has a non-default value, e.g. obj.str != "" |
| // - True if the dynamic value has the field defined, e.g. key in map |
| // |
| // However, presence tests are not defined for qualified identifier names with primitive types. |
| // If a string named 'a.b.c' is declared in the environment and referenced within `has(a.b.c)`, |
| // it is not clear whether has should error or follow the convention defined for structured |
| // values. |
| |
| // Establish the attribute reference. |
| attr, isAttr := op.(InterpretableAttribute) |
| if !isAttr { |
| attr, err = p.relativeAttr(op.ID(), op, false) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| // Build a qualifier for the attribute. |
| qual, err := p.attrFactory.NewQualifier(opType, expr.ID(), sel.FieldName(), false) |
| if err != nil { |
| return nil, err |
| } |
| // Modify the attribute to be test-only. |
| if sel.IsTestOnly() { |
| attr = &evalTestOnly{ |
| id: expr.ID(), |
| InterpretableAttribute: attr, |
| } |
| } |
| // Append the qualifier on the attribute. |
| _, err = attr.AddQualifier(qual) |
| return attr, err |
| } |
| |
| // planCall creates a callable Interpretable while specializing for common functions and invocation |
| // patterns. Specifically, conditional operators &&, ||, ?:, and (in)equality functions result in |
| // optimized Interpretable values. |
| func (p *planBuilder) planCall(expr ast.Expr) (Interpretable, error) { |
| call := expr.AsCall() |
| target, fnName, oName := p.resolveFunction(expr) |
| argCount := len(call.Args()) |
| var offset int |
| if target != nil { |
| argCount++ |
| offset++ |
| } |
| |
| args := make([]Interpretable, argCount) |
| if target != nil { |
| arg, err := p.plan(target) |
| if err != nil { |
| return nil, err |
| } |
| args[0] = arg |
| } |
| for i, argExpr := range call.Args() { |
| arg, err := p.plan(argExpr) |
| if err != nil { |
| return nil, err |
| } |
| args[i+offset] = arg |
| } |
| |
| // Generate specialized Interpretable operators by function name if possible. |
| switch fnName { |
| case operators.LogicalAnd: |
| return p.planCallLogicalAnd(expr, args) |
| case operators.LogicalOr: |
| return p.planCallLogicalOr(expr, args) |
| case operators.Conditional: |
| return p.planCallConditional(expr, args) |
| case operators.Equals: |
| return p.planCallEqual(expr, args) |
| case operators.NotEquals: |
| return p.planCallNotEqual(expr, args) |
| case operators.Index: |
| return p.planCallIndex(expr, args, false) |
| case operators.OptSelect, operators.OptIndex: |
| return p.planCallIndex(expr, args, true) |
| } |
| |
| // Otherwise, generate Interpretable calls specialized by argument count. |
| // Try to find the specific function by overload id. |
| var fnDef *functions.Overload |
| if oName != "" { |
| fnDef, _ = p.disp.FindOverload(oName) |
| } |
| // If the overload id couldn't resolve the function, try the simple function name. |
| if fnDef == nil { |
| fnDef, _ = p.disp.FindOverload(fnName) |
| } |
| switch argCount { |
| case 0: |
| return p.planCallZero(expr, fnName, oName, fnDef) |
| case 1: |
| // If the FunctionOp has been used, then use it as it may exist for the purposes |
| // of dynamic dispatch within a singleton function implementation. |
| if fnDef != nil && fnDef.Unary == nil && fnDef.Function != nil { |
| return p.planCallVarArgs(expr, fnName, oName, fnDef, args) |
| } |
| return p.planCallUnary(expr, fnName, oName, fnDef, args) |
| case 2: |
| // If the FunctionOp has been used, then use it as it may exist for the purposes |
| // of dynamic dispatch within a singleton function implementation. |
| if fnDef != nil && fnDef.Binary == nil && fnDef.Function != nil { |
| return p.planCallVarArgs(expr, fnName, oName, fnDef, args) |
| } |
| return p.planCallBinary(expr, fnName, oName, fnDef, args) |
| default: |
| return p.planCallVarArgs(expr, fnName, oName, fnDef, args) |
| } |
| } |
| |
| // planCallZero generates a zero-arity callable Interpretable. |
| func (p *planBuilder) planCallZero(expr ast.Expr, |
| function string, |
| overload string, |
| impl *functions.Overload) (Interpretable, error) { |
| if impl == nil || impl.Function == nil { |
| return nil, fmt.Errorf("no such overload: %s()", function) |
| } |
| return &evalZeroArity{ |
| id: expr.ID(), |
| function: function, |
| overload: overload, |
| impl: impl.Function, |
| }, nil |
| } |
| |
| // planCallUnary generates a unary callable Interpretable. |
| func (p *planBuilder) planCallUnary(expr ast.Expr, |
| function string, |
| overload string, |
| impl *functions.Overload, |
| args []Interpretable) (Interpretable, error) { |
| var fn functions.UnaryOp |
| var trait int |
| var nonStrict bool |
| if impl != nil { |
| if impl.Unary == nil { |
| return nil, fmt.Errorf("no such overload: %s(arg)", function) |
| } |
| fn = impl.Unary |
| trait = impl.OperandTrait |
| nonStrict = impl.NonStrict |
| } |
| return &evalUnary{ |
| id: expr.ID(), |
| function: function, |
| overload: overload, |
| arg: args[0], |
| trait: trait, |
| impl: fn, |
| nonStrict: nonStrict, |
| }, nil |
| } |
| |
| // planCallBinary generates a binary callable Interpretable. |
| func (p *planBuilder) planCallBinary(expr ast.Expr, |
| function string, |
| overload string, |
| impl *functions.Overload, |
| args []Interpretable) (Interpretable, error) { |
| var fn functions.BinaryOp |
| var trait int |
| var nonStrict bool |
| if impl != nil { |
| if impl.Binary == nil { |
| return nil, fmt.Errorf("no such overload: %s(lhs, rhs)", function) |
| } |
| fn = impl.Binary |
| trait = impl.OperandTrait |
| nonStrict = impl.NonStrict |
| } |
| return &evalBinary{ |
| id: expr.ID(), |
| function: function, |
| overload: overload, |
| lhs: args[0], |
| rhs: args[1], |
| trait: trait, |
| impl: fn, |
| nonStrict: nonStrict, |
| }, nil |
| } |
| |
| // planCallVarArgs generates a variable argument callable Interpretable. |
| func (p *planBuilder) planCallVarArgs(expr ast.Expr, |
| function string, |
| overload string, |
| impl *functions.Overload, |
| args []Interpretable) (Interpretable, error) { |
| var fn functions.FunctionOp |
| var trait int |
| var nonStrict bool |
| if impl != nil { |
| if impl.Function == nil { |
| return nil, fmt.Errorf("no such overload: %s(...)", function) |
| } |
| fn = impl.Function |
| trait = impl.OperandTrait |
| nonStrict = impl.NonStrict |
| } |
| return &evalVarArgs{ |
| id: expr.ID(), |
| function: function, |
| overload: overload, |
| args: args, |
| trait: trait, |
| impl: fn, |
| nonStrict: nonStrict, |
| }, nil |
| } |
| |
| // planCallEqual generates an equals (==) Interpretable. |
| func (p *planBuilder) planCallEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) { |
| return &evalEq{ |
| id: expr.ID(), |
| lhs: args[0], |
| rhs: args[1], |
| }, nil |
| } |
| |
| // planCallNotEqual generates a not equals (!=) Interpretable. |
| func (p *planBuilder) planCallNotEqual(expr ast.Expr, args []Interpretable) (Interpretable, error) { |
| return &evalNe{ |
| id: expr.ID(), |
| lhs: args[0], |
| rhs: args[1], |
| }, nil |
| } |
| |
| // planCallLogicalAnd generates a logical and (&&) Interpretable. |
| func (p *planBuilder) planCallLogicalAnd(expr ast.Expr, args []Interpretable) (Interpretable, error) { |
| return &evalAnd{ |
| id: expr.ID(), |
| terms: args, |
| }, nil |
| } |
| |
| // planCallLogicalOr generates a logical or (||) Interpretable. |
| func (p *planBuilder) planCallLogicalOr(expr ast.Expr, args []Interpretable) (Interpretable, error) { |
| return &evalOr{ |
| id: expr.ID(), |
| terms: args, |
| }, nil |
| } |
| |
| // planCallConditional generates a conditional / ternary (c ? t : f) Interpretable. |
| func (p *planBuilder) planCallConditional(expr ast.Expr, args []Interpretable) (Interpretable, error) { |
| cond := args[0] |
| t := args[1] |
| var tAttr Attribute |
| truthyAttr, isTruthyAttr := t.(InterpretableAttribute) |
| if isTruthyAttr { |
| tAttr = truthyAttr.Attr() |
| } else { |
| tAttr = p.attrFactory.RelativeAttribute(t.ID(), t) |
| } |
| |
| f := args[2] |
| var fAttr Attribute |
| falsyAttr, isFalsyAttr := f.(InterpretableAttribute) |
| if isFalsyAttr { |
| fAttr = falsyAttr.Attr() |
| } else { |
| fAttr = p.attrFactory.RelativeAttribute(f.ID(), f) |
| } |
| |
| return &evalAttr{ |
| adapter: p.adapter, |
| attr: p.attrFactory.ConditionalAttribute(expr.ID(), cond, tAttr, fAttr), |
| }, nil |
| } |
| |
| // planCallIndex either extends an attribute with the argument to the index operation, or creates |
| // a relative attribute based on the return of a function call or operation. |
| func (p *planBuilder) planCallIndex(expr ast.Expr, args []Interpretable, optional bool) (Interpretable, error) { |
| op := args[0] |
| ind := args[1] |
| opType := p.typeMap[op.ID()] |
| |
| // Establish the attribute reference. |
| var err error |
| attr, isAttr := op.(InterpretableAttribute) |
| if !isAttr { |
| attr, err = p.relativeAttr(op.ID(), op, false) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| // Construct the qualifier type. |
| var qual Qualifier |
| switch ind := ind.(type) { |
| case InterpretableConst: |
| qual, err = p.attrFactory.NewQualifier(opType, expr.ID(), ind.Value(), optional) |
| case InterpretableAttribute: |
| qual, err = p.attrFactory.NewQualifier(opType, expr.ID(), ind, optional) |
| default: |
| qual, err = p.relativeAttr(expr.ID(), ind, optional) |
| } |
| if err != nil { |
| return nil, err |
| } |
| |
| // Add the qualifier to the attribute |
| _, err = attr.AddQualifier(qual) |
| return attr, err |
| } |
| |
| // planCreateList generates a list construction Interpretable. |
| func (p *planBuilder) planCreateList(expr ast.Expr) (Interpretable, error) { |
| list := expr.AsList() |
| optionalIndices := list.OptionalIndices() |
| elements := list.Elements() |
| optionals := make([]bool, len(elements)) |
| for _, index := range optionalIndices { |
| if index < 0 || index >= int32(len(elements)) { |
| return nil, fmt.Errorf("optional index %d out of element bounds [0, %d]", index, len(elements)) |
| } |
| optionals[index] = true |
| } |
| elems := make([]Interpretable, len(elements)) |
| for i, elem := range elements { |
| elemVal, err := p.plan(elem) |
| if err != nil { |
| return nil, err |
| } |
| elems[i] = elemVal |
| } |
| return &evalList{ |
| id: expr.ID(), |
| elems: elems, |
| optionals: optionals, |
| hasOptionals: len(optionalIndices) != 0, |
| adapter: p.adapter, |
| }, nil |
| } |
| |
| // planCreateStruct generates a map or object construction Interpretable. |
| func (p *planBuilder) planCreateMap(expr ast.Expr) (Interpretable, error) { |
| m := expr.AsMap() |
| entries := m.Entries() |
| optionals := make([]bool, len(entries)) |
| keys := make([]Interpretable, len(entries)) |
| vals := make([]Interpretable, len(entries)) |
| hasOptionals := false |
| for i, e := range entries { |
| entry := e.AsMapEntry() |
| keyVal, err := p.plan(entry.Key()) |
| if err != nil { |
| return nil, err |
| } |
| keys[i] = keyVal |
| |
| valVal, err := p.plan(entry.Value()) |
| if err != nil { |
| return nil, err |
| } |
| vals[i] = valVal |
| optionals[i] = entry.IsOptional() |
| hasOptionals = hasOptionals || entry.IsOptional() |
| } |
| return &evalMap{ |
| id: expr.ID(), |
| keys: keys, |
| vals: vals, |
| optionals: optionals, |
| hasOptionals: hasOptionals, |
| adapter: p.adapter, |
| }, nil |
| } |
| |
| // planCreateObj generates an object construction Interpretable. |
| func (p *planBuilder) planCreateStruct(expr ast.Expr) (Interpretable, error) { |
| obj := expr.AsStruct() |
| typeName, defined := p.resolveTypeName(obj.TypeName()) |
| if !defined { |
| return nil, fmt.Errorf("unknown type: %s", obj.TypeName()) |
| } |
| objFields := obj.Fields() |
| optionals := make([]bool, len(objFields)) |
| fields := make([]string, len(objFields)) |
| vals := make([]Interpretable, len(objFields)) |
| hasOptionals := false |
| for i, f := range objFields { |
| field := f.AsStructField() |
| fields[i] = field.Name() |
| val, err := p.plan(field.Value()) |
| if err != nil { |
| return nil, err |
| } |
| vals[i] = val |
| optionals[i] = field.IsOptional() |
| hasOptionals = hasOptionals || field.IsOptional() |
| } |
| return &evalObj{ |
| id: expr.ID(), |
| typeName: typeName, |
| fields: fields, |
| vals: vals, |
| optionals: optionals, |
| hasOptionals: hasOptionals, |
| provider: p.provider, |
| }, nil |
| } |
| |
| // planComprehension generates an Interpretable fold operation. |
| func (p *planBuilder) planComprehension(expr ast.Expr) (Interpretable, error) { |
| fold := expr.AsComprehension() |
| accu, err := p.plan(fold.AccuInit()) |
| if err != nil { |
| return nil, err |
| } |
| iterRange, err := p.plan(fold.IterRange()) |
| if err != nil { |
| return nil, err |
| } |
| p.pushLocalVars(fold.AccuVar(), fold.IterVar(), fold.IterVar2()) |
| cond, err := p.plan(fold.LoopCondition()) |
| if err != nil { |
| return nil, err |
| } |
| step, err := p.plan(fold.LoopStep()) |
| if err != nil { |
| return nil, err |
| } |
| p.popLocalVars(fold.IterVar(), fold.IterVar2()) |
| result, err := p.plan(fold.Result()) |
| if err != nil { |
| return nil, err |
| } |
| p.popLocalVars(fold.AccuVar()) |
| return &evalFold{ |
| id: expr.ID(), |
| accuVar: fold.AccuVar(), |
| accu: accu, |
| iterVar: fold.IterVar(), |
| iterVar2: fold.IterVar2(), |
| iterRange: iterRange, |
| cond: cond, |
| step: step, |
| result: result, |
| adapter: p.adapter, |
| }, nil |
| } |
| |
| // planConst generates a constant valued Interpretable. |
| func (p *planBuilder) planConst(expr ast.Expr) (Interpretable, error) { |
| return NewConstValue(expr.ID(), expr.AsLiteral()), nil |
| } |
| |
| // resolveTypeName takes a qualified string constructed at parse time, applies the proto |
| // namespace resolution rules to it in a scan over possible matching types in the TypeProvider. |
| func (p *planBuilder) resolveTypeName(typeName string) (string, bool) { |
| for _, qualifiedTypeName := range p.container.ResolveCandidateNames(typeName) { |
| if _, found := p.provider.FindStructType(qualifiedTypeName); found { |
| return qualifiedTypeName, true |
| } |
| } |
| return "", false |
| } |
| |
| // resolveFunction determines the call target, function name, and overload name from a given Expr |
| // value. |
| // |
| // The resolveFunction resolves ambiguities where a function may either be a receiver-style |
| // invocation or a qualified global function name. |
| // - The target expression may only consist of ident and select expressions. |
| // - The function is declared in the environment using its fully-qualified name. |
| // - The fully-qualified function name matches the string serialized target value. |
| func (p *planBuilder) resolveFunction(expr ast.Expr) (ast.Expr, string, string) { |
| // Note: similar logic exists within the `checker/checker.go`. If making changes here |
| // please consider the impact on checker.go and consolidate implementations or mirror code |
| // as appropriate. |
| call := expr.AsCall() |
| var target ast.Expr = nil |
| if call.IsMemberFunction() { |
| target = call.Target() |
| } |
| fnName := call.FunctionName() |
| |
| // Checked expressions always have a reference map entry, and _should_ have the fully qualified |
| // function name as the fnName value. |
| oRef, hasOverload := p.refMap[expr.ID()] |
| if hasOverload { |
| if len(oRef.OverloadIDs) == 1 { |
| return target, fnName, oRef.OverloadIDs[0] |
| } |
| // Note, this namespaced function name will not appear as a fully qualified name in ASTs |
| // built and stored before cel-go v0.5.0; however, this functionality did not work at all |
| // before the v0.5.0 release. |
| return target, fnName, "" |
| } |
| |
| // Parse-only expressions need to handle the same logic as is normally performed at check time, |
| // but with potentially much less information. The only reliable source of information about |
| // which functions are configured is the dispatcher. |
| if target == nil { |
| // If the user has a parse-only expression, then it should have been configured as such in |
| // the interpreter dispatcher as it may have been omitted from the checker environment. |
| for _, qualifiedName := range p.container.ResolveCandidateNames(fnName) { |
| _, found := p.disp.FindOverload(qualifiedName) |
| if found { |
| return nil, qualifiedName, "" |
| } |
| } |
| // It's possible that the overload was not found, but this situation is accounted for in |
| // the planCall phase; however, the leading dot used for denoting fully-qualified |
| // namespaced identifiers must be stripped, as all declarations already use fully-qualified |
| // names. This stripping behavior is handled automatically by the ResolveCandidateNames |
| // call. |
| return target, strings.TrimPrefix(fnName, "."), "" |
| } |
| |
| // Handle the situation where the function target actually indicates a qualified function name. |
| qualifiedPrefix, maybeQualified := p.toQualifiedName(target) |
| if maybeQualified { |
| maybeQualifiedName := qualifiedPrefix + "." + fnName |
| for _, qualifiedName := range p.container.ResolveCandidateNames(maybeQualifiedName) { |
| _, found := p.disp.FindOverload(qualifiedName) |
| if found { |
| // Clear the target to ensure the proper arity is used for finding the |
| // implementation. |
| return nil, qualifiedName, "" |
| } |
| } |
| } |
| // In the default case, the function is exactly as it was advertised: a receiver call on with |
| // an expression-based target with the given simple function name. |
| return target, fnName, "" |
| } |
| |
| // relativeAttr indicates that the attribute in this case acts as a qualifier and as such needs to |
| // be observed to ensure that it's evaluation value is properly recorded for state tracking. |
| func (p *planBuilder) relativeAttr(id int64, eval Interpretable, opt bool) (InterpretableAttribute, error) { |
| eAttr, ok := eval.(InterpretableAttribute) |
| if !ok { |
| eAttr = &evalAttr{ |
| adapter: p.adapter, |
| attr: p.attrFactory.RelativeAttribute(id, eval), |
| optional: opt, |
| } |
| } |
| // This looks like it should either decorate the new evalAttr node, or early return the InterpretableAttribute |
| decAttr, err := p.decorate(eAttr, nil) |
| if err != nil { |
| return nil, err |
| } |
| eAttr, ok = decAttr.(InterpretableAttribute) |
| if !ok { |
| return nil, fmt.Errorf("invalid attribute decoration: %v(%T)", decAttr, decAttr) |
| } |
| return eAttr, nil |
| } |
| |
| // toQualifiedName converts an expression AST into a qualified name if possible, with a boolean |
| // 'found' value that indicates if the conversion is successful. |
| func (p *planBuilder) toQualifiedName(operand ast.Expr) (string, bool) { |
| // If the checker identified the expression as an attribute by the type-checker, then it can't |
| // possibly be part of qualified name in a namespace. |
| _, isAttr := p.refMap[operand.ID()] |
| if isAttr { |
| return "", false |
| } |
| // Since functions cannot be both namespaced and receiver functions, if the operand is not an |
| // qualified variable name, return the (possibly) qualified name given the expressions. |
| switch operand.Kind() { |
| case ast.IdentKind: |
| id := operand.AsIdent() |
| return id, true |
| case ast.SelectKind: |
| sel := operand.AsSelect() |
| // Test only expressions are not valid as qualified names. |
| if sel.IsTestOnly() { |
| return "", false |
| } |
| if qual, found := p.toQualifiedName(sel.Operand()); found { |
| return qual + "." + sel.FieldName(), true |
| } |
| } |
| return "", false |
| } |
| |
| func (p *planBuilder) pushLocalVars(names ...string) { |
| for _, name := range names { |
| if name == "" { |
| continue |
| } |
| if cnt, found := p.localVars[name]; found { |
| p.localVars[name] = cnt + 1 |
| } else { |
| p.localVars[name] = 1 |
| } |
| } |
| } |
| |
| func (p *planBuilder) popLocalVars(names ...string) { |
| for _, name := range names { |
| if name == "" { |
| continue |
| } |
| if cnt, found := p.localVars[name]; found { |
| if cnt == 1 { |
| delete(p.localVars, name) |
| } else { |
| p.localVars[name] = cnt - 1 |
| } |
| } |
| } |
| } |
| |
| func (p *planBuilder) isLocalVar(name string) bool { |
| _, found := p.localVars[name] |
| return found |
| } |