Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ public Object advanceEvaluation(UnknownContext context) throws CelEvaluationExce

static Builder newBuilder() {
return new AutoValue_CelRuntimeImpl.Builder()
.setFunctionBindings(ImmutableMap.of())
.setStandardFunctions(CelStandardFunctions.newBuilder().build())
.setContainer(CelContainer.newBuilder().build())
.setExtensionRegistry(ExtensionRegistry.getEmptyRegistry());
Expand Down Expand Up @@ -222,6 +223,8 @@ abstract static class Builder implements CelRuntimeBuilder {

abstract ExtensionRegistry extensionRegistry();

abstract ImmutableMap<String, CelFunctionBinding> functionBindings();

abstract ImmutableSet.Builder<Descriptors.FileDescriptor> fileDescriptorsBuilder();

abstract ImmutableSet.Builder<CelRuntimeLibrary> runtimeLibrariesBuilder();
Expand Down Expand Up @@ -442,6 +445,9 @@ public CelRuntime build() {
DescriptorTypeResolver descriptorTypeResolver =
DescriptorTypeResolver.create(combinedTypeProvider);
TypeFunction typeFunction = TypeFunction.create(descriptorTypeResolver);

mutableFunctionBindings.putAll(functionBindings());

for (CelFunctionBinding binding :
typeFunction.newFunctionBindings(options(), runtimeEquality)) {
mutableFunctionBindings.put(binding.getOverloadId(), binding);
Expand Down
1 change: 1 addition & 0 deletions runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ java_library(
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
import dev.cel.common.types.EnumType;
import dev.cel.common.types.SimpleType;
import dev.cel.common.types.TypeType;
import dev.cel.common.values.CelValue;
import dev.cel.common.values.CelValueConverter;
import dev.cel.runtime.GlobalResolver;
import java.util.NoSuchElementException;
import org.jspecify.annotations.Nullable;

@Immutable
final class NamespacedAttribute implements Attribute {
Expand All @@ -34,6 +36,14 @@ final class NamespacedAttribute implements Attribute {
private final CelValueConverter celValueConverter;
private final CelTypeProvider typeProvider;

ImmutableList<Qualifier> qualifiers() {
return qualifiers;
}

ImmutableSet<String> candidateVariableNames() {
return namespacedNames;
}

@Override
public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
GlobalResolver inputVars = ctx;
Expand All @@ -59,41 +69,62 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
}
}

CelType type = typeProvider.findType(name).orElse(null);
if (type != null) {
if (qualifiers.isEmpty()) {
// Resolution of a fully qualified type name: foo.bar.baz
return TypeType.create(type);
} else {
// This is potentially a fully qualified reference to an enum value
if (type instanceof EnumType && qualifiers.size() == 1) {
EnumType enumType = (EnumType) type;
String strQualifier = (String) qualifiers.get(0).value();
return enumType
.findNumberByName(strQualifier)
.orElseThrow(
() ->
new NoSuchElementException(
String.format(
"Field %s was not found on enum %s",
enumType.name(), strQualifier)));
}
}

throw new IllegalStateException(
"Unexpected type resolution when there were remaining qualifiers: " + type.name());
// Attempt to resolve the qualify type name if the name is not a variable identifier
value = findIdent(name);
if (value != null) {
return value;
}
}

return MissingAttribute.newMissingAttribute(namespacedNames);
}

ImmutableList<Qualifier> qualifiers() {
return qualifiers;
private @Nullable Object findIdent(String name) {
CelType type = typeProvider.findType(name).orElse(null);
// If the name resolves directly, this is a fully qualified type name
// (ex: 'int' or 'google.protobuf.Timestamp')
if (type != null) {
if (qualifiers.isEmpty()) {
// Resolution of a fully qualified type name: foo.bar.baz
if (type instanceof TypeType) {
// Coalesce all type(foo) "type" into a sentinel runtime type to allow for
// erasure based type comparisons
return TypeType.create(SimpleType.DYN);
}

return TypeType.create(type);
}

throw new IllegalStateException(
"Unexpected type resolution when there were remaining qualifiers: " + type.name());
}

// The name itself could be a fully qualified reference to an enum value
// (e.g: my.enum_type.BAR)
int lastDotIndex = name.lastIndexOf('.');
if (lastDotIndex > 0) {
String enumTypeName = name.substring(0, lastDotIndex);
String enumValueQualifier = name.substring(lastDotIndex + 1);

return typeProvider
.findType(enumTypeName)
.filter(EnumType.class::isInstance)
.map(EnumType.class::cast)
.map(enumType -> getEnumValue(enumType, enumValueQualifier))
.orElse(null);
}

return null;
}

ImmutableSet<String> candidateVariableNames() {
return namespacedNames;
private static Long getEnumValue(EnumType enumType, String field) {
return enumType
.findNumberByName(field)
.map(Integer::longValue)
.orElseThrow(
() ->
new NoSuchElementException(
String.format("Field %s was not found on enum %s", enumType.name(), field)));
}

private GlobalResolver unwrapToNonLocal(GlobalResolver resolver) {
Expand Down
4 changes: 4 additions & 0 deletions runtime/src/test/java/dev/cel/runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,11 @@ java_library(
"PlannerInterpreterTest.java",
],
deps = [
"//common:cel_ast",
"//common:compiler_common",
"//common:container",
"//common:options",
"//common/types:type_providers",
"//extensions",
"//runtime",
"//runtime:runtime_planner_impl",
Expand Down
58 changes: 58 additions & 0 deletions runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@

package dev.cel.runtime;

import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelContainer;
import dev.cel.common.CelOptions;
import dev.cel.common.CelValidationException;
import dev.cel.common.types.CelTypeProvider;
import dev.cel.extensions.CelExtensions;
import dev.cel.testing.BaseInterpreterTest;
import org.junit.runner.RunWith;
Expand All @@ -24,6 +29,8 @@
@RunWith(TestParameterInjector.class)
public class PlannerInterpreterTest extends BaseInterpreterTest {

@TestParameter boolean isParseOnly;

@Override
protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) {
return CelRuntimeImpl.newBuilder()
Expand All @@ -34,6 +41,36 @@ protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) {
.addFileTypes(TEST_FILE_DESCRIPTORS);
}

@Override
protected void setContainer(CelContainer container) {
super.setContainer(container);
this.celRuntime = this.celRuntime.toRuntimeBuilder().setContainer(container).build();
}

@Override
protected CelAbstractSyntaxTree prepareTest(CelTypeProvider typeProvider) {
super.prepareCompiler(typeProvider);

CelAbstractSyntaxTree ast;
try {
ast = celCompiler.parse(source, testSourceDescription()).getAst();
} catch (CelValidationException e) {
printTestValidationError(e);
return null;
}

if (isParseOnly) {
return ast;
}

try {
return celCompiler.check(ast).getAst();
} catch (CelValidationException e) {
printTestValidationError(e);
return null;
}
}

@Override
public void unknownField() {
// TODO: Unknown support not implemented yet
Expand All @@ -45,4 +82,25 @@ public void unknownResultSet() {
// TODO: Unknown support not implemented yet
skipBaselineVerification();
}

@Override
public void optional() {
if (isParseOnly) {
// TODO: Fix for parsed-only mode.
skipBaselineVerification();
} else {
super.optional();
}
}

@Override
public void optional_errors() {
if (isParseOnly) {
// Parsed-only evaluation contains function name in the
// error message instead of the function overload.
skipBaselineVerification();
} else {
super.optional_errors();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,33 @@ public void plan_ident_enum() throws Exception {

Object result = program.eval();

assertThat(result).isEqualTo(1);
assertThat(result).isEqualTo(1L);
}

@Test
public void plan_ident_enumContainer() throws Exception {
CelContainer container = CelContainer.ofName(GlobalEnum.getDescriptor().getFullName());
CelCompiler compiler =
CelCompilerFactory.standardCelCompilerBuilder()
.addMessageTypes(TestAllTypes.getDescriptor())
.setContainer(container)
.build();
CelAbstractSyntaxTree ast = compile(compiler, GlobalEnum.GAR.name());
ProgramPlanner planner =
ProgramPlanner.newPlanner(
TYPE_PROVIDER,
VALUE_PROVIDER,
newDispatcher(),
CEL_VALUE_CONVERTER,
container,
CEL_OPTIONS,
ImmutableSet.of());

Program program = planner.plan(ast);

Object result = program.eval();

assertThat(result).isEqualTo(1L);
}

@Test
Expand All @@ -300,6 +326,18 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t
assertThat(result).isEqualTo(testCase.type);
}

@Test
public void planIdent_typeLiteral_equality(@TestParameter TypeLiteralTestCase testCase)
throws Exception {
// ex: type(bool) == type, type(TestAllTypes) == type
CelAbstractSyntaxTree ast = compile(String.format("type(%s) == type", testCase.expression));
Program program = PLANNER.plan(ast);

boolean result = (boolean) program.eval();

assertThat(result).isTrue();
}

@Test
public void plan_ident_missingAttribute_throws() throws Exception {
CelAbstractSyntaxTree ast = compile("int_var");
Expand Down
1 change: 1 addition & 0 deletions testing/src/main/java/dev/cel/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ java_library(
"//runtime:late_function_binding",
"//runtime:unknown_attributes",
"@cel_spec//proto/cel/expr:checked_java_proto",
"@cel_spec//proto/cel/expr:syntax_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
"@maven//:com_google_errorprone_error_prone_annotations",
Expand Down
Loading
Loading