All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.cel.checker.Env Maven / Gradle / Ivy

// 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
//
//      https://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 dev.cel.checker;

import dev.cel.expr.Constant;
import dev.cel.expr.Decl;
import dev.cel.expr.Decl.FunctionDecl.Overload;
import dev.cel.expr.Expr;
import dev.cel.expr.Type;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelOptions;
import dev.cel.common.CelOverloadDecl;
import dev.cel.common.ExprFeatures;
import dev.cel.common.annotations.Internal;
import dev.cel.common.ast.CelConstant;
import dev.cel.common.ast.CelExpr;
import dev.cel.common.ast.CelExprConverter;
import dev.cel.common.ast.CelReference;
import dev.cel.common.internal.Errors;
import dev.cel.common.types.CelKind;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypes;
import dev.cel.common.types.SimpleType;
import dev.cel.parser.CelStandardMacro;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.jspecify.nullness.Nullable;

/**
 * Environment used during checking of expressions. Provides name resolution and error reporting.
 *
 * 

Note: the environment is not thread-safe. Create multiple envs from scratch for working with * different threads. * *

CEL Library Internals. Do Not Use. CEL-Java users should leverage the Fluent APIs instead. See * {@code CelCompilerFactory}. */ @Internal public class Env { /** The top-most scope in the environment, for use with {@link #getDeclGroup(int)}. */ public static final int ROOT_SCOPE = 0; /** An ident declaration to represent an error. */ public static final CelIdentDecl ERROR_IDENT_DECL = CelIdentDecl.newBuilder().setName("*error*").setType(SimpleType.ERROR).build(); /** A function declaration to represent an error. */ public static final CelFunctionDecl ERROR_FUNCTION_DECL = CelFunctionDecl.newBuilder().setName("*error*").build(); /** Type provider responsible for resolving CEL message references to strong types. */ private final TypeProvider typeProvider; /** * Stack of declaration groups where each entry in stack represents a scope capable of hinding * declarations lower in the stack. */ private final ArrayList decls = new ArrayList<>(); /** A map from expression ids into references resolved for the overall tree so far. */ private final Map referenceMap = new LinkedHashMap<>(); /** A map from expression ids into types resolved for the overall tree so far. */ private final Map typeMap = new LinkedHashMap<>(); /** Object used for error reporting. */ private final Errors errors; /** CEL Feature flags. */ private final CelOptions celOptions; private Env( Errors errors, TypeProvider typeProvider, DeclGroup declGroup, CelOptions celOptions) { this.celOptions = celOptions; this.errors = Preconditions.checkNotNull(errors); this.typeProvider = Preconditions.checkNotNull(typeProvider); this.decls.add(Preconditions.checkNotNull(declGroup)); } /** * Creates an unconfigured {@code Env} value without the standard CEL types, functions, and * operators with a reference to the feature flags enabled in the environment. * * @deprecated use {@code unconfigured} with {@code CelOptions} instead. */ @Deprecated public static Env unconfigured(Errors errors, ExprFeatures... exprFeatures) { return unconfigured(errors, new DescriptorTypeProvider(), ImmutableSet.copyOf(exprFeatures)); } /** * Creates an unconfigured {@code Env} value without the standard CEL types, functions, and * operators using a custom {@code typeProvider}. * * @deprecated use {@code unconfigured} with {@code CelOptions} instead. */ @Deprecated public static Env unconfigured( Errors errors, TypeProvider typeProvider, ExprFeatures... exprFeatures) { return unconfigured(errors, typeProvider, ImmutableSet.copyOf(exprFeatures)); } /** * Creates an unconfigured {@code Env} value without the standard CEL types, functions, and * operators using a custom {@code typeProvider}. The set of enabled {@code exprFeatures} is also * provided. * * @deprecated use {@code unconfigured} with {@code CelOptions} instead. */ @Deprecated public static Env unconfigured( Errors errors, TypeProvider typeProvider, ImmutableSet exprFeatures) { return unconfigured(errors, typeProvider, CelOptions.fromExprFeatures(exprFeatures)); } /** * Creates an unconfigured {@code Env} value without the standard CEL types, functions, and * operators with a reference to the configured {@code celOptions}. */ public static Env unconfigured(Errors errors, CelOptions celOptions) { return unconfigured(errors, new DescriptorTypeProvider(), celOptions); } /** * Creates an unconfigured {@code Env} value without the standard CEL types, functions, and * operators using a custom {@code typeProvider}. The {@code CelOptions} are provided as well. */ public static Env unconfigured(Errors errors, TypeProvider typeProvider, CelOptions celOptions) { return new Env(errors, typeProvider, new DeclGroup(), celOptions); } /** * Creates an {@code Env} value configured with the standard types, functions, and operators with * a reference to the set of {@code exprFeatures} enabled in the environment. * *

Note: standard declarations are configured in an isolated scope, and may be shadowed by * subsequent declarations. * * @deprecated use {@code standard} with {@code CelOptions} instead. */ @Deprecated public static Env standard(Errors errors, ExprFeatures... exprFeatures) { return standard(errors, new DescriptorTypeProvider(), exprFeatures); } /** * Creates an {@code Env} value configured with the standard types, functions, and operators, * configured with a custom {@code typeProvider}. * *

Note: standard declarations are configured in an isolated scope, and may be shadowed by * subsequent declarations with the same signature. * * @deprecated use {@code standard} with {@code CelOptions} instead. */ @Deprecated public static Env standard( Errors errors, TypeProvider typeProvider, ExprFeatures... exprFeatures) { return standard(errors, typeProvider, ImmutableSet.copyOf(exprFeatures)); } /** * Creates an {@code Env} value configured with the standard types, functions, and operators, * configured with a custom {@code typeProvider} and a reference to the set of {@code * exprFeatures} enabled in the environment. * *

Note: standard declarations are configured in an isolated scope, and may be shadowed by * subsequent declarations with the same signature. * * @deprecated use {@code standard} with {@code CelOptions} instead. */ @Deprecated public static Env standard( Errors errors, TypeProvider typeProvider, ImmutableSet exprFeatures) { return standard(errors, typeProvider, CelOptions.fromExprFeatures(exprFeatures)); } /** * Creates an {@code Env} value configured with the standard types, functions, and operators and a * reference to the configured {@code celOptions}. * *

Note: standard declarations are configured in an isolated scope, and may be shadowed by * subsequent declarations with the same signature. */ public static Env standard(Errors errors, CelOptions celOptions) { return standard(errors, new DescriptorTypeProvider(), celOptions); } /** * Creates an {@code Env} value configured with the standard types, functions, and operators, * configured with a custom {@code typeProvider} and a reference to the {@code celOptions} to use * within the environment. * *

Note: standard declarations are configured in an isolated scope, and may be shadowed by * subsequent declarations with the same signature. */ public static Env standard(Errors errors, TypeProvider typeProvider, CelOptions celOptions) { Env env = Env.unconfigured(errors, typeProvider, celOptions); // Isolate the standard declarations into their own scope for forward compatibility. Standard.add(env); env.enterScope(); return env; } /** Returns the current Errors object. */ public Errors getErrorContext() { return errors; } /** Returns the {@code TypeProvider}. */ public TypeProvider getTypeProvider() { return typeProvider; } /** * Enters a new scope. All new declarations added to the environment exist only in this scope, and * will shadow declarations of the same name in outer scopes. This includes overloads in outer * scope (overloads from different scopes are not merged). */ public void enterScope() { decls.add(new DeclGroup()); } /** Exits a previously opened scope, forgetting all declarations created in this scope. */ public void exitScope() { Preconditions.checkState(!decls.isEmpty(), "Cannot exit top-level environment scope"); decls.remove(decls.size() - 1); } /** Return the current scope depth for the environment. */ public int scopeDepth() { return decls.size() - 1; } /** Returns the top-most declaration scope. */ public DeclGroup getDeclGroup() { return Iterables.getLast(decls); } /** * Returns the {@code DeclGroup} at the given {@code scopeDepth}, where depth of {@code 0} * represents root scope. */ public DeclGroup getDeclGroup(int scopeDepth) { Preconditions.checkArgument( scopeDepth <= scopeDepth() && scopeDepth >= 0, "Invalid scope depth."); return decls.get(scopeDepth); } /** Reset type and ref maps. This must be called before type checking an expression. */ public void resetTypeAndRefMaps() { typeMap.clear(); referenceMap.clear(); } /** Returns the reference map. */ public Map getRefMap() { return referenceMap; } /** Returns the type map. */ public Map getTypeMap() { return typeMap; } /** * Returns the type associated with an expression by expression id. It's an error to call this * method if the type is not present. * * @deprecated Use {@link #getType(CelExpr)} instead. */ @Deprecated public Type getType(Expr expr) { Preconditions.checkNotNull(expr); return CelTypes.celTypeToType(getType(CelExprConverter.fromExpr(expr))); } /** * Returns the type associated with an expression by expression id. It's an error to call this * method if the type is not present. */ public CelType getType(CelExpr expr) { return Preconditions.checkNotNull(typeMap.get(expr.id()), "expression has no type"); } /** * Sets the type associated with an expression by id. It's an error if the type is already set and * is different than the provided one. Returns the expression parameter. */ @CanIgnoreReturnValue public CelExpr setType(CelExpr expr, CelType type) { CelType oldType = typeMap.put(expr.id(), type); Preconditions.checkState( oldType == null || oldType.equals(type), "expression already has a type which is incompatible.\n old: %s\n new: %s", oldType, type); return expr; } /** * Sets the reference associated with an expression. It's an error if the reference is already set * and is different. */ public void setRef(CelExpr expr, CelReference reference) { CelReference oldReference = referenceMap.put(expr.id(), reference); Preconditions.checkState( oldReference == null || oldReference.equals(reference), "expression already has a reference which is incompatible"); } /** * Adds a declaration to the environment, based on the Decl proto. Will report errors if the * declaration overlaps with an existing one, or clashes with a macro. * * @deprecated Migrate to the CEL-Java fluent APIs and leverage the publicly available native * types (e.g: {@code CelCompilerFactory} accepts {@code CelFunctionDecl} and {@code * CelVarDecl}). */ @CanIgnoreReturnValue @Deprecated public Env add(Decl decl) { switch (decl.getDeclKindCase()) { case IDENT: CelIdentDecl.Builder identBuilder = CelIdentDecl.newBuilder() .setName(decl.getName()) .setType(CelTypes.typeToCelType(decl.getIdent().getType())) // Note: Setting doc and constant value exists for compatibility reason. This should // not be set by the users. .setDoc(decl.getIdent().getDoc()); if (decl.getIdent().hasValue()) { identBuilder.setConstant( CelExprConverter.exprConstantToCelConstant(decl.getIdent().getValue())); } return add(identBuilder.build()); case FUNCTION: ImmutableList.Builder overloadDeclBuilder = new ImmutableList.Builder<>(); for (Overload overload : decl.getFunction().getOverloadsList()) { overloadDeclBuilder.add(CelOverloadDecl.overloadToCelOverload(overload)); } return add( CelFunctionDecl.newBuilder() .setName(decl.getName()) .addOverloads(overloadDeclBuilder.build()) .build()); default: break; } return this; } @CanIgnoreReturnValue public Env add(CelFunctionDecl celFunctionDecl) { return addFunction(sanitizeFunction(celFunctionDecl)); } @CanIgnoreReturnValue public Env add(CelIdentDecl celIdentDecl) { return addIdent(sanitizeIdent(celIdentDecl)); } /** * Adds simple name declaration to the environment for a non-function. * * @deprecated Migrate to the CEL-Java fluent APIs and leverage the publicly available native * types (e.g: {@code CelCompilerFactory} accepts {@code CelFunctionDecl} and {@code * CelVarDecl}). */ @CanIgnoreReturnValue @Deprecated public Env add(String name, Type type) { return add(CelIdentDecl.newIdentDeclaration(name, CelTypes.typeToCelType(type))); } /** * @deprecated Use {@link #tryLookupCelFunction} instead. */ @Deprecated public @Nullable Decl tryLookupFunction(String container, String name) { CelFunctionDecl decl = tryLookupCelFunction(container, name); if (decl == null) { return null; } return CelFunctionDecl.celFunctionDeclToDecl(decl); } /** * Try to lookup a function with the given {@code name} within a {@code container}. * *

For protos, the {@code container} may be a package or message name. The code tries to * resolve the {@code name} first in the container, then within the container parent, and so on * until the root package is reached. If {@code container} starts with {@code .}, the resolution * is in the root container only. * *

Returns {@code null} if the function cannot be found. */ public @Nullable CelFunctionDecl tryLookupCelFunction(String container, String name) { for (String cand : qualifiedTypeNameCandidates(container, name)) { // First determine whether we know this name already. CelFunctionDecl decl = findFunctionDecl(cand); if (decl != null) { return decl; } } return null; } /** * @deprecated Use {@link #tryLookupCelIdent} instead. */ @Deprecated public @Nullable Decl tryLookupIdent(String container, String name) { CelIdentDecl decl = tryLookupCelIdent(container, name); if (decl == null) { return null; } return CelIdentDecl.celIdentToDecl(decl); } /** * Try to lookup an identifier with the given {@code name} within a {@code container}. * *

For protos, the {@code container} may be a package or message name. The code tries to * resolve the {@code name} first in the container, then within the container parent, and so on * until the root package is reached. If {@code container} starts with {@code .}, the resolution * is in the root container only. * *

Returns {@code null} if the function cannot be found. */ public @Nullable CelIdentDecl tryLookupCelIdent(String container, String name) { for (String cand : qualifiedTypeNameCandidates(container, name)) { // First determine whether we know this name already. CelIdentDecl decl = findIdentDecl(cand); if (decl != null) { return decl; } // Next try to import the name as a reference to a message type. // This is done via the type provider. Optional type = typeProvider.lookupCelType(cand); if (type.isPresent()) { decl = CelIdentDecl.newIdentDeclaration(cand, type.get()); decls.get(0).putIdent(decl); return decl; } // Next try to import this as an enum value by splitting the name in a type prefix and // the enum inside. Integer enumValue = typeProvider.lookupEnumValue(cand); if (enumValue != null) { decl = CelIdentDecl.newBuilder() .setName(cand) .setType(SimpleType.INT) .setConstant(CelConstant.ofValue(enumValue)) .build(); decls.get(0).putIdent(decl); return decl; } } return null; } /** * Lookup a name like {@link #tryLookupCelIdent}, but report an error if the name is not found and * return the {@link #ERROR_IDENT_DECL}. */ public CelIdentDecl lookupIdent(int position, String inContainer, String name) { CelIdentDecl result = tryLookupCelIdent(inContainer, name); if (result == null) { reportError(position, "undeclared reference to '%s' (in container '%s')", name, inContainer); return ERROR_IDENT_DECL; } return result; } /** * Lookup a name like {@link #tryLookupCelFunction} but report an error if the name is not found * and return the {@link #ERROR_FUNCTION_DECL}. */ public CelFunctionDecl lookupFunction(int position, String inContainer, String name) { CelFunctionDecl result = tryLookupCelFunction(inContainer, name); if (result == null) { reportError(position, "undeclared reference to '%s' (in container '%s')", name, inContainer); return ERROR_FUNCTION_DECL; } return result; } /** Reports an error. */ public void reportError(int position, String message, Object... args) { errors.reportError(position, message, args); } boolean enableCompileTimeOverloadResolution() { return celOptions.enableCompileTimeOverloadResolution(); } boolean enableHomogeneousLiterals() { return celOptions.enableHomogeneousLiterals(); } boolean enableNamespacedDeclarations() { return celOptions.enableNamespacedDeclarations(); } boolean enableHeterogeneousNumericComparisons() { return celOptions.enableHeterogeneousNumericComparisons(); } boolean enableTimestampEpoch() { return celOptions.enableTimestampEpoch(); } /** Add an identifier {@code decl} to the environment. */ @CanIgnoreReturnValue private Env addIdent(CelIdentDecl celIdentDecl) { CelIdentDecl current = getDeclGroup().getIdent(celIdentDecl.name()); if (current == null) { getDeclGroup().putIdent(celIdentDecl); } else { reportError( 0, "overlapping declaration name '%s' (type '%s' cannot be distinguished from '%s')", celIdentDecl.name(), CelTypes.format(current.type()), CelTypes.format(celIdentDecl.type())); } return this; } /** Add a function {@code decl} to the environment. */ @CanIgnoreReturnValue private Env addFunction(CelFunctionDecl decl) { CelFunctionDecl current = getDeclGroup().getFunction(decl.name()); CelFunctionDecl.Builder builder = current != null ? current.toBuilder() : CelFunctionDecl.newBuilder().setName(decl.name()); for (CelOverloadDecl overload : decl.overloads()) { addOverload(builder, overload); } getDeclGroup().putFunction(builder.build()); return this; } /** * Attempt to extend the declaration with a new overload. This reports an error if the overload * overlaps with existing ones or with a macro. */ private void addOverload(CelFunctionDecl.Builder builder, CelOverloadDecl overload) { // Compute the type of the overload with all type parameters replaced by DYN. // We are using a property of Types.substitute which replaces all unbound type // parameters by DYN. ImmutableMap emptySubs = ImmutableMap.of(); CelType overloadFunction = CelTypes.createFunctionType(overload.resultType(), overload.parameterTypes()); CelType overloadTypeErased = Types.substitute(emptySubs, overloadFunction, true); // Loop over existing overloads to find any overlap. for (CelOverloadDecl existing : builder.overloads()) { CelType existingFunction = CelTypes.createFunctionType(existing.resultType(), existing.parameterTypes()); CelType existingTypeErased = Types.substitute(emptySubs, existingFunction, true); boolean overlap = Types.isAssignable(emptySubs, overloadTypeErased, existingTypeErased) != null || Types.isAssignable(emptySubs, existingTypeErased, overloadTypeErased) != null; if (overlap && existing.isInstanceFunction() == overload.isInstanceFunction()) { reportError( 0, "overlapping overload for name '%s' (type '%s' cannot be distinguished from '%s')", builder.name(), CelTypes.format(existingFunction), CelTypes.format(overloadFunction)); return; } } // If this is a function, loop over macros to detect any clash. for (CelStandardMacro macro : CelStandardMacro.STANDARD_MACROS) { if (macro.getFunction().equals(builder.name()) && macro.getDefinition().isReceiverStyle() == overload.isInstanceFunction() && macro.getDefinition().getArgumentCount() == overload.parameterTypes().size()) { reportError( 0, "overload for name '%s' with %s argument(s) overlaps with predefined macro", builder.name(), macro.getDefinition().getArgumentCount()); return; } } builder.addOverloads(overload); } /** Search for the named identifier declaration. */ private @Nullable CelIdentDecl findIdentDecl(String name) { for (DeclGroup declGroup : Lists.reverse(decls)) { CelIdentDecl ident = declGroup.getIdent(name); if (ident != null) { return ident; } } return null; } /** Search for the named function declaration. */ private @Nullable CelFunctionDecl findFunctionDecl(String name) { // Search bottom-up for the matching function declarations. List functions = new ArrayList<>(); for (DeclGroup declGroup : Lists.reverse(decls)) { CelFunctionDecl function = declGroup.getFunction(name); if (function != null) { functions.add(function); } } // If no functions have the declared name, return null. if (functions.isEmpty()) { return null; } // If only one function declaration has the specified name, return it. if (functions.size() == 1) { return functions.get(0); } // Otherwise form a composite view of the overloads, most specific first, as indicated by the // bottom-up traversal order of the declaration scopes. Map overloadSignatureMap = new HashMap<>(); for (CelFunctionDecl function : functions) { for (CelOverloadDecl overload : function.overloads()) { // The input signature is enough to disambiguate overloads. When two or more functions in // different scopes share the same signature, the function in the lowest scope will shadow // its ancestors. // // Note: declaring a function with the same signature in the same scope is an error. String overloadSignature = TypeFormatter.formatFunction( /* resultType= */ null, overload.parameterTypes(), overload.isInstanceFunction(), /* typeParamToDyn= */ true); overloadSignatureMap.putIfAbsent(overloadSignature, overload); } } return CelFunctionDecl.newBuilder() .setName(name) .addOverloads(overloadSignatureMap.values()) .build(); } /** * Returns the candidates for name resolution of a name within a container(e.g. package, message, * enum, service elements) context following PB (== C++) conventions. Iterates those names which * shadow other names first; recognizes and removes a leading '.' for overriding shadowing. Given * a container name {@code a.b.c.M.N} and a type name {@code R.s}, this will deliver in order * {@code a.b.c.M.N.R.s, a.b.c.M.R.s, a.b.c.R.s, a.b.R.s, a.R.s, R.s}. */ private static ImmutableList qualifiedTypeNameCandidates( String container, String typeName) { // This function is a copy of //j/c/g/api/tools/model/SymbolTable#nameCandidates. if (typeName.startsWith(".")) { return ImmutableList.of(typeName.substring(1)); } if (container.isEmpty()) { return ImmutableList.of(typeName); } else { int i = container.lastIndexOf('.'); return ImmutableList.builder() .add(container + "." + typeName) .addAll(qualifiedTypeNameCandidates(i >= 0 ? container.substring(0, i) : "", typeName)) .build(); } } /** * A helper class for constructing identifier declarations. * * @deprecated Use {@code CelVarDecl#newBuilder()} instead. */ @Deprecated public static final class IdentBuilder { private final CelIdentDecl.Builder builder = CelIdentDecl.newBuilder(); /** Create an identifier builder. */ public IdentBuilder(String name) { builder.setName(Preconditions.checkNotNull(name)); } /** Set the identifier type. */ @CanIgnoreReturnValue public IdentBuilder type(Type value) { Preconditions.checkNotNull(value); builder.setType(CelTypes.typeToCelType(Preconditions.checkNotNull(value))); return this; } /** Set the identifier to a {@code Constant} value. */ @CanIgnoreReturnValue public IdentBuilder value(@Nullable Constant value) { if (value == null) { builder.clearConstant(); } else { builder.setConstant(CelExprConverter.exprConstantToCelConstant(value)); } return this; } /** Set the documentation for the identifier. */ @CanIgnoreReturnValue public IdentBuilder doc(@Nullable String value) { if (value == null) { builder.setDoc(""); } else { builder.setDoc(value); } return this; } /** Build the ident {@code Decl}. */ public Decl build() { return CelIdentDecl.celIdentToDecl(builder.build()); } } /** * A helper class for building declarations. * * @deprecated Use {@link CelFunctionDecl#newBuilder()} instead. */ @Deprecated public static final class FunctionBuilder { private final String name; private final List overloads = new ArrayList<>(); private final boolean isInstance; /** Create a global function builder. */ public FunctionBuilder(String name) { this(name, false); } /** Create an instance function builder. */ public FunctionBuilder(String name, boolean isInstance) { this.name = Preconditions.checkNotNull(name); this.isInstance = isInstance; } /** * Add the overloads of another function to this function, after replacing the overload id as * specified. */ @CanIgnoreReturnValue public FunctionBuilder sameAs(Decl func, String idPart, String idPartReplace) { Preconditions.checkNotNull(func); for (Overload overload : func.getFunction().getOverloadsList()) { this.overloads.add( CelOverloadDecl.overloadToCelOverload(overload).toBuilder() .setOverloadId(overload.getOverloadId().replace(idPart, idPartReplace)) .build()); } return this; } /** Add an overload. */ @CanIgnoreReturnValue public FunctionBuilder add(String id, Type resultType, Type... argTypes) { return add(id, resultType, ImmutableList.copyOf(argTypes)); } /** Add an overload. */ @CanIgnoreReturnValue public FunctionBuilder add(String id, Type resultType, Iterable argTypes) { ImmutableList.Builder argumentBuilder = new ImmutableList.Builder<>(); for (Type type : argTypes) { argumentBuilder.add(CelTypes.typeToCelType(type)); } this.overloads.add( CelOverloadDecl.newBuilder() .setOverloadId(id) .setResultType(CelTypes.typeToCelType(resultType)) .addParameterTypes(argumentBuilder.build()) .setIsInstanceFunction(isInstance) .build()); return this; } /** Add an overload, with type params. */ @CanIgnoreReturnValue public FunctionBuilder add( String id, List typeParams, Type resultType, Type... argTypes) { return add(id, typeParams, resultType, ImmutableList.copyOf(argTypes)); } /** Add an overload, with type params. */ @CanIgnoreReturnValue public FunctionBuilder add( String id, List typeParams, Type resultType, Iterable argTypes) { ImmutableList.Builder argumentBuilder = new ImmutableList.Builder<>(); for (Type type : argTypes) { argumentBuilder.add(CelTypes.typeToCelType(type)); } this.overloads.add( CelOverloadDecl.newBuilder() .setOverloadId(id) .setResultType(CelTypes.typeToCelType(resultType)) .addParameterTypes(argumentBuilder.build()) .setIsInstanceFunction(isInstance) .build()); return this; } /** Adds documentation to the last added overload. */ @CanIgnoreReturnValue public FunctionBuilder doc(@Nullable String value) { int current = this.overloads.size() - 1; CelOverloadDecl.Builder builder = this.overloads.get(current).toBuilder(); if (value == null) { builder.setDoc(""); } else { builder.setDoc(value); } this.overloads.set(current, builder.build()); return this; } /** Build the function {@code Decl}. */ @CheckReturnValue public Decl build() { return CelFunctionDecl.celFunctionDeclToDecl( CelFunctionDecl.newBuilder().setName(name).addOverloads(overloads).build()); } } /** * Object for managing a group of declarations within a scope. * *

Identifiers and functions can share the same declaration name, so a simple map will not * suffice for tracking declaration overloads. * *

Whether a given {@code DeclGroup} is mutable or immutable depends on whether the maps * supplied as input to the group are standard {@code Map} implementations or {@code ImmutableMap} * implementations. The {DeclGroup#immutableCopy} method is provided as a convenience to make it * easy to create an instance of the group which will honor the developer's intent. */ public static class DeclGroup { private final Map idents; private final Map functions; /** Construct an empty {@code DeclGroup}. */ public DeclGroup() { this(new HashMap<>(), new HashMap<>()); } /** Construct a new {@code DeclGroup} from the input {@code idents} and {@code functions}. */ public DeclGroup(Map idents, Map functions) { this.functions = functions; this.idents = idents; } /** * Get an immutable map of the identifiers in the {@code DeclGroup} keyed by declaration name. */ public Map getIdents() { return ImmutableMap.copyOf(idents); } /** Get an immutable map of the functions in the {@code DeclGroup} keyed by declaration name. */ public Map getFunctions() { return ImmutableMap.copyOf(functions); } /** Get an identifier declaration by {@code name}. Returns {@code null} if absent. */ public @Nullable CelIdentDecl getIdent(String name) { return idents.get(name); } /** Put an identifier declaration into the {@code DeclGroup}. */ public void putIdent(CelIdentDecl ident) { idents.put(ident.name(), ident); } /** Get a function declaration by {@code name}. Returns {@code null} if absent. */ public @Nullable CelFunctionDecl getFunction(String name) { return functions.get(name); } /** Put a function declaration into the {@code DeclGroup}. */ public void putFunction(CelFunctionDecl function) { functions.put(function.name(), function); } /** Create a copy of the {@code DeclGroup} with immutable identifier and function maps. */ public DeclGroup immutableCopy() { return new DeclGroup(getIdents(), getFunctions()); } } /** * Sanitize the identifier declaration type making sure that proto-based message names are mapped * to the appropriate CEL type. */ private static CelIdentDecl sanitizeIdent(CelIdentDecl decl) { CelType type = decl.type(); if (!isWellKnownType(type)) { return decl; } return CelIdentDecl.newIdentDeclaration(decl.name(), getWellKnownType(decl.type())); } /** * Sanitize the function declaration type making sure that proto-based message names appearing in * the result or parameter types are mapped to the appropriate CEL types. */ private static CelFunctionDecl sanitizeFunction(CelFunctionDecl func) { boolean needsSanitizing = false; for (CelOverloadDecl o : func.overloads()) { if (isWellKnownType(o.resultType())) { needsSanitizing = true; break; } for (CelType p : o.parameterTypes()) { if (isWellKnownType(p)) { needsSanitizing = true; break; } } } if (!needsSanitizing) { return func; } CelFunctionDecl.Builder funcBuilder = func.toBuilder(); ImmutableList.Builder overloadsBuilder = new ImmutableList.Builder<>(); for (CelOverloadDecl overloadDecl : funcBuilder.overloads()) { CelOverloadDecl.Builder overloadBuilder = overloadDecl.toBuilder(); CelType resultType = overloadBuilder.build().resultType(); if (isWellKnownType(resultType)) { overloadBuilder.setResultType(getWellKnownType(resultType)); } ImmutableList.Builder parameterTypeBuilder = ImmutableList.builder(); for (CelType paramType : overloadBuilder.parameterTypes()) { if (isWellKnownType(paramType)) { parameterTypeBuilder.add(getWellKnownType(paramType)); } else { parameterTypeBuilder.add(paramType); } } overloadBuilder.setParameterTypes(parameterTypeBuilder.build()); overloadsBuilder.add(overloadBuilder.build()); } return funcBuilder.setOverloads(overloadsBuilder.build()).build(); } static boolean isWellKnownType(CelType type) { return type.kind() == CelKind.STRUCT && CelTypes.isWellKnownType(type.name()); } static CelType getWellKnownType(CelType type) { Preconditions.checkArgument(type.kind() == CelKind.STRUCT); return CelTypes.getWellKnownCelType(type.name()).get(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy