diff --git a/conformance/README.md b/conformance/README.md index 2762beaa..45e73b2d 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -12,7 +12,9 @@ The CEL-spec conformance test suite is written in Go and uses the bazel build to Required tools: * [Bazel build tool](https://bazel.build/) - On Ubuntu run `apt-get install bazel-bootstrap` + See [Bazel web site](https://bazel.build/install) for installation instructions. + Do **not** use the `bazel-bootstrap` package on Ubuntu, because it might be built + against Java 17. * gcc On Ubuntu run `apt-get install gcc` diff --git a/conformance/build.gradle.kts b/conformance/build.gradle.kts index 58da1452..b718358b 100644 --- a/conformance/build.gradle.kts +++ b/conformance/build.gradle.kts @@ -28,7 +28,10 @@ plugins { apply() -sourceSets.main { java.srcDir(layout.buildDirectory.dir("generated/source/proto/main/java")) } +sourceSets.main { + java.srcDir(layout.buildDirectory.dir("generated/source/proto/main/java")) + java.srcDir(layout.buildDirectory.dir("generated/source/proto/main/grpc")) +} dependencies { implementation(project(":cel-core")) @@ -58,6 +61,10 @@ configure { // Download from repositories artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.get()}" } + plugins { + this.create("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:${libs.versions.grpc.get()}" } + } + generateProtoTasks { all().configureEach { this.plugins.create("grpc") {} } } } // The protobuf-plugin should ideally do this diff --git a/conformance/run-conformance-tests.sh b/conformance/run-conformance-tests.sh index da46b510..9e9a2a95 100755 --- a/conformance/run-conformance-tests.sh +++ b/conformance/run-conformance-tests.sh @@ -46,26 +46,24 @@ cel_java_skips=( # nested protobuf-object-structure, which gets rejected during gRPC/protobuf request # deserialization. Just skip those tests. "--skip_test=parse/nest/message_literal" - "--skip_test=parse/repeat/index" + # Proto equality specialties don't seem to be in effect for Java + "--skip_test=comparisons/eq_wrapper/eq_proto_nan_equal" + "--skip_test=comparisons/ne_literal/ne_proto_nan_not_equal" - # TODO Actual known issux to fix, a protobuf Any returned via this test is wrapped twice (Any in Any). + # TODO Actual known issue to fix, a protobuf Any returned via this test is wrapped twice (Any in Any). "--skip_test=dynamic/any/var" ) cel_go_skips=( - "--skip_test=comparisons/eq_literal/not_eq_list_false_vs_types,not_eq_map_false_vs_types" - "--skip_test=comparisons/in_map_literal/key_in_mixed_key_type_map_error" - "--skip_test=comparisons/eq_literal/not_eq_list_false_vs_types,not_eq_map_false_vs_types" - "--skip_test=comparisons/in_map_literal/key_in_mixed_key_type_map_error" "--skip_test=dynamic/int32/field_assign_proto2_range,field_assign_proto3_range" "--skip_test=dynamic/uint32/field_assign_proto2_range,field_assign_proto3_range" "--skip_test=dynamic/float/field_assign_proto2_range,field_assign_proto3_range" - "--skip_test=dynamic/value_null/literal_unset,field_read_proto2_unset,field_read_proto3_unset" "--skip_test=enums/legacy_proto2/assign_standalone_int_too_big,assign_standalone_int_too_neg" "--skip_test=enums/legacy_proto3/assign_standalone_int_too_big,assign_standalone_int_too_neg" "--skip_test=enums/strong_proto2" "--skip_test=enums/strong_proto3" - "--skip_test=fields/qualified_identifier_resolution/map_key_float,map_key_null,map_value_repeat_key" + # This conformance test is invalid nowadays + "--skip_test=fields/qualified_identifier_resolution/map_key_float" ) test_files=( diff --git a/conformance/src/main/java/org/projectnessie/cel/server/ConformanceServiceImpl.java b/conformance/src/main/java/org/projectnessie/cel/server/ConformanceServiceImpl.java index af9968a3..a3d86aa6 100644 --- a/conformance/src/main/java/org/projectnessie/cel/server/ConformanceServiceImpl.java +++ b/conformance/src/main/java/org/projectnessie/cel/server/ConformanceServiceImpl.java @@ -39,20 +39,20 @@ import static org.projectnessie.cel.common.types.UnknownT.isUnknown; import static org.projectnessie.cel.common.types.UnknownT.unknownOf; -import com.google.api.expr.v1alpha1.CheckRequest; -import com.google.api.expr.v1alpha1.CheckResponse; -import com.google.api.expr.v1alpha1.ConformanceServiceGrpc.ConformanceServiceImplBase; +import com.google.api.expr.conformance.v1alpha1.CheckRequest; +import com.google.api.expr.conformance.v1alpha1.CheckResponse; +import com.google.api.expr.conformance.v1alpha1.ConformanceServiceGrpc.ConformanceServiceImplBase; +import com.google.api.expr.conformance.v1alpha1.EvalRequest; +import com.google.api.expr.conformance.v1alpha1.EvalResponse; +import com.google.api.expr.conformance.v1alpha1.IssueDetails; +import com.google.api.expr.conformance.v1alpha1.ParseRequest; +import com.google.api.expr.conformance.v1alpha1.ParseResponse; +import com.google.api.expr.conformance.v1alpha1.SourcePosition; import com.google.api.expr.v1alpha1.ErrorSet; -import com.google.api.expr.v1alpha1.EvalRequest; -import com.google.api.expr.v1alpha1.EvalResponse; import com.google.api.expr.v1alpha1.ExprValue; -import com.google.api.expr.v1alpha1.IssueDetails; import com.google.api.expr.v1alpha1.ListValue; import com.google.api.expr.v1alpha1.MapValue; import com.google.api.expr.v1alpha1.MapValue.Entry; -import com.google.api.expr.v1alpha1.ParseRequest; -import com.google.api.expr.v1alpha1.ParseResponse; -import com.google.api.expr.v1alpha1.SourcePosition; import com.google.api.expr.v1alpha1.UnknownSet; import com.google.api.expr.v1alpha1.Value; import com.google.protobuf.Any; diff --git a/conformance/src/main/proto/google/api/expr/conformance b/conformance/src/main/proto/google/api/expr/conformance new file mode 120000 index 00000000..04adab4d --- /dev/null +++ b/conformance/src/main/proto/google/api/expr/conformance @@ -0,0 +1 @@ +../../../../../../../submodules/googleapis/google/api/expr/conformance \ No newline at end of file diff --git a/conformance/src/test/java/org/projectnessie/cel/server/ConformanceServerTest.java b/conformance/src/test/java/org/projectnessie/cel/server/ConformanceServerTest.java index a75dda6c..ce79883b 100644 --- a/conformance/src/test/java/org/projectnessie/cel/server/ConformanceServerTest.java +++ b/conformance/src/test/java/org/projectnessie/cel/server/ConformanceServerTest.java @@ -20,22 +20,22 @@ import static org.projectnessie.cel.TestExpr.ExprLiteral; import static org.projectnessie.cel.Util.mapOf; -import com.google.api.expr.v1alpha1.CheckRequest; -import com.google.api.expr.v1alpha1.CheckResponse; +import com.google.api.expr.conformance.v1alpha1.CheckRequest; +import com.google.api.expr.conformance.v1alpha1.CheckResponse; +import com.google.api.expr.conformance.v1alpha1.ConformanceServiceGrpc; +import com.google.api.expr.conformance.v1alpha1.ConformanceServiceGrpc.ConformanceServiceBlockingStub; +import com.google.api.expr.conformance.v1alpha1.EvalRequest; +import com.google.api.expr.conformance.v1alpha1.EvalResponse; +import com.google.api.expr.conformance.v1alpha1.ParseRequest; +import com.google.api.expr.conformance.v1alpha1.ParseResponse; import com.google.api.expr.v1alpha1.CheckedExpr; -import com.google.api.expr.v1alpha1.ConformanceServiceGrpc; -import com.google.api.expr.v1alpha1.ConformanceServiceGrpc.ConformanceServiceBlockingStub; import com.google.api.expr.v1alpha1.Constant; import com.google.api.expr.v1alpha1.Constant.ConstantKindCase; -import com.google.api.expr.v1alpha1.EvalRequest; -import com.google.api.expr.v1alpha1.EvalResponse; import com.google.api.expr.v1alpha1.Expr; import com.google.api.expr.v1alpha1.Expr.Call; import com.google.api.expr.v1alpha1.Expr.ExprKindCase; import com.google.api.expr.v1alpha1.ExprValue; import com.google.api.expr.v1alpha1.ExprValue.KindCase; -import com.google.api.expr.v1alpha1.ParseRequest; -import com.google.api.expr.v1alpha1.ParseResponse; import com.google.api.expr.v1alpha1.ParsedExpr; import com.google.api.expr.v1alpha1.SourceInfo; import com.google.api.expr.v1alpha1.Type; diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 7c6718c5..7da46c0a 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { testImplementation(platform(libs.junit.bom)) testImplementation(libs.bundles.junit.testing) + testImplementation(libs.protobuf.java) testRuntimeOnly(libs.junit.jupiter.engine) jmhImplementation(libs.jmh.core) diff --git a/core/src/main/java/org/projectnessie/cel/checker/Standard.java b/core/src/main/java/org/projectnessie/cel/checker/Standard.java index 8ee4c47c..e16ae573 100644 --- a/core/src/main/java/org/projectnessie/cel/checker/Standard.java +++ b/core/src/main/java/org/projectnessie/cel/checker/Standard.java @@ -162,13 +162,13 @@ Overloads.GreaterEqualsBytes, asList(Decls.Bytes, Decls.Bytes), Decls.Bool), Decls.newFunction( Operator.Equals.id, Decls.newParameterizedOverload( - Overloads.Equals, asList(paramA, paramA), Decls.Bool, typeParamAList))); + Overloads.Equals, asList(paramA, paramB), Decls.Bool, typeParamABList))); idents.add( Decls.newFunction( Operator.NotEquals.id, Decls.newParameterizedOverload( - Overloads.NotEquals, asList(paramA, paramA), Decls.Bool, typeParamAList))); + Overloads.NotEquals, asList(paramA, paramB), Decls.Bool, typeParamABList))); // Algebra. diff --git a/core/src/main/java/org/projectnessie/cel/common/types/BoolT.java b/core/src/main/java/org/projectnessie/cel/common/types/BoolT.java index 40b21937..5747c73d 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/BoolT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/BoolT.java @@ -107,10 +107,14 @@ public Val convertToType(Type typeVal) { /** Equal implements the ref.Val interface method. */ @Override public Val equal(Val other) { - if (!(other instanceof BoolT)) { - return noSuchOverload(this, "equal", other); + switch (other.type().typeEnum()) { + case Bool: + return Types.boolOf(b == ((BoolT) other).b); + case Null: + return False; + default: + return noSuchOverload(this, "equal", other); } - return Types.boolOf(b == ((BoolT) other).b); } /** Negate implements the traits.Negater interface method. */ diff --git a/core/src/main/java/org/projectnessie/cel/common/types/BytesT.java b/core/src/main/java/org/projectnessie/cel/common/types/BytesT.java index 597ca249..0b1fa188 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/BytesT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/BytesT.java @@ -15,6 +15,7 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.Err.newErr; import static org.projectnessie.cel.common.types.Err.newTypeConversionError; import static org.projectnessie.cel.common.types.Err.noSuchOverload; @@ -162,10 +163,14 @@ public Val convertToType(Type typeValue) { /** Equal implements the ref.Val interface method. */ @Override public Val equal(Val other) { - if (!(other instanceof BytesT)) { - return noSuchOverload(this, "equal", other); + switch (other.type().typeEnum()) { + case Bytes: + return boolOf(Arrays.equals(b, ((BytesT) other).b)); + case Null: + return False; + default: + return noSuchOverload(this, "equal", other); } - return boolOf(Arrays.equals(b, ((BytesT) other).b)); } /** Size implements the traits.Sizer interface method. */ diff --git a/core/src/main/java/org/projectnessie/cel/common/types/DoubleT.java b/core/src/main/java/org/projectnessie/cel/common/types/DoubleT.java index 961e0051..a1e94860 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/DoubleT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/DoubleT.java @@ -15,6 +15,7 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.Err.newTypeConversionError; import static org.projectnessie.cel.common.types.Err.noSuchOverload; import static org.projectnessie.cel.common.types.Err.rangeError; @@ -31,7 +32,6 @@ import com.google.protobuf.Value; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Objects; import org.projectnessie.cel.common.types.ref.BaseVal; import org.projectnessie.cel.common.types.ref.Type; import org.projectnessie.cel.common.types.ref.TypeEnum; @@ -68,6 +68,11 @@ private DoubleT(double d) { this.d = d; } + @Override + public boolean isNegativeNumber() { + return d < 0; + } + /** Add implements traits.Adder.Add. */ @Override public Val add(Val other) { @@ -77,20 +82,6 @@ public Val add(Val other) { return doubleOf(d + ((DoubleT) other).d); } - /** Compare implements traits.Comparer.Compare. */ - @Override - public Val compare(Val other) { - if (!(other instanceof DoubleT)) { - return noSuchOverload(this, "compare", other); - } - double od = ((DoubleT) other).d; - if (d == od) { - // work around for special case of -0.0d == 0.0d (IEEE 754) - return IntZero; - } - return intOfCompare(Double.compare(d, od)); - } - /** ConvertToNative implements ref.Val.ConvertToNative. */ @SuppressWarnings("unchecked") @Override @@ -141,17 +132,23 @@ public Val convertToType(Type typeValue) { // (see https://github.com/google/cel-spec/blob/master/doc/langdef.md#numeric-values) switch (typeValue.typeEnum()) { case Int: + if (!Double.isFinite(d)) { + return rangeError(d, "int"); + } long r = (long) d; // ?? Math.round(d); if (r == Long.MIN_VALUE || r == Long.MAX_VALUE) { return rangeError(d, "int"); } return intOf(r); case Uint: + if (!Double.isFinite(d)) { + return rangeError(d, "uint"); + } // hack to support uint64 BigDecimal dec = new BigDecimal(d); BigInteger bi = dec.toBigInteger(); if (d < 0 || bi.compareTo(MAX_UINT64) > 0) { - return rangeError(d, "int"); + return rangeError(d, "uint"); } return uintOf(bi.longValue()); case Double: @@ -173,14 +170,52 @@ public Val divide(Val other) { return doubleOf(d / ((DoubleT) other).d); } + /** Compare implements traits.Comparer.Compare. */ + @Override + public Val compare(Val other) { + switch (other.type().typeEnum()) { + case Uint: + case Int: + case Double: + case String: + Val converted = other.convertToType(type()); + if (converted.type().typeEnum() == TypeEnum.Err) { + return converted; + } + double od = ((DoubleT) converted).d; + if (d == od) { + // work around for special case of -0.0d == 0.0d (IEEE 754) + return IntZero; + } + return intOfCompare(Double.compare(d, od)); + default: + return noSuchOverload(this, "compare", other); + } + } + /** Equal implements ref.Val.Equal. */ @Override public Val equal(Val other) { - if (!(other instanceof DoubleT)) { - return noSuchOverload(this, "equal", other); + switch (other.type().typeEnum()) { + case Uint: + case Int: + case Double: + case String: + Val converted = other.convertToType(type()); + if (converted.type().typeEnum() == TypeEnum.Err) { + return converted; + } + double o = ((DoubleT) converted).d; + // TODO: Handle NaNs properly. + return boolOf(d == o); + case Null: + case Bytes: + case List: + case Map: + return False; + default: + return noSuchOverload(this, "equal", other); } - /** TODO: Handle NaNs properly. */ - return boolOf(d == ((DoubleT) other).d); } /** Multiply implements traits.Multiplier.Multiply. */ @@ -224,20 +259,21 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof Val)) { return false; } - DoubleT doubleT = (DoubleT) o; - double od = ((DoubleT) o).d; - if (d == od) { - // work around for special case of -0.0d == 0.0d (IEEE 754) - return true; - } - return Double.compare(doubleT.d, d) == 0; + return equal((Val) o).booleanValue(); + // DoubleT doubleT = (DoubleT) o; + // double od = ((DoubleT) o).d; + // if (d == od) { + // // work around for special case of -0.0d == 0.0d (IEEE 754) + // return true; + // } + // return Double.compare(doubleT.d, d) == 0; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), d); + return (int) d; } } diff --git a/core/src/main/java/org/projectnessie/cel/common/types/DurationT.java b/core/src/main/java/org/projectnessie/cel/common/types/DurationT.java index 4a683fa7..e45870ba 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/DurationT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/DurationT.java @@ -15,6 +15,7 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.Err.errDurationOutOfRange; import static org.projectnessie.cel.common.types.Err.errDurationOverflow; import static org.projectnessie.cel.common.types.Err.newTypeConversionError; @@ -26,6 +27,7 @@ import com.google.protobuf.Any; import com.google.protobuf.Value; +import java.time.DateTimeException; import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; @@ -76,7 +78,15 @@ public static DurationT durationOf(String s) { try { dur = Duration.parse("PT" + s); } catch (DateTimeParseException e) { - dur = Duration.parse("P" + s); + try { + dur = Duration.parse("P" + s); + } catch (DateTimeParseException e2) { + if (s.endsWith("ns")) { + dur = Duration.ofNanos(Long.parseLong(s.substring(0, s.length() - 2))); + } else { + throw new DateTimeException("Cannot parse duration '" + s + "'", e2); + } + } } return durationOf(dur); } @@ -216,10 +226,14 @@ public Val convertToType(Type typeValue) { /** Equal implements ref.Val.Equal. */ @Override public Val equal(Val other) { - if (!(other instanceof DurationT)) { - return noSuchOverload(this, "equal", other); + switch (other.type().typeEnum()) { + case Duration: + return boolOf(d.equals(((DurationT) other).d)); + case Null: + return False; + default: + return noSuchOverload(this, "equal", other); } - return boolOf(d.equals(((DurationT) other).d)); } /** Negate implements traits.Negater.Negate. */ @@ -299,8 +313,6 @@ public static Val timeGetSeconds(Duration duration) { } public static Val timeGetMilliseconds(Duration duration) { - return IntT.intOf( - TimeUnit.SECONDS.toMillis(duration.getSeconds()) - + TimeUnit.NANOSECONDS.toMillis(duration.getNano())); + return IntT.intOf(TimeUnit.NANOSECONDS.toMillis(duration.getNano())); } } diff --git a/core/src/main/java/org/projectnessie/cel/common/types/IntT.java b/core/src/main/java/org/projectnessie/cel/common/types/IntT.java index a2c11d7a..adcc0056 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/IntT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/IntT.java @@ -15,6 +15,8 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.BoolT.False; +import static org.projectnessie.cel.common.types.DoubleT.DoubleType; import static org.projectnessie.cel.common.types.DoubleT.doubleOf; import static org.projectnessie.cel.common.types.Err.divideByZero; import static org.projectnessie.cel.common.types.Err.errIntOverflow; @@ -36,7 +38,6 @@ import com.google.protobuf.Int64Value; import com.google.protobuf.Value; import java.time.Instant; -import java.util.Objects; import org.projectnessie.cel.common.types.Overflow.OverflowException; import org.projectnessie.cel.common.types.ref.BaseVal; import org.projectnessie.cel.common.types.ref.Type; @@ -113,6 +114,11 @@ public long intValue() { return i; } + @Override + public boolean isNegativeNumber() { + return i < 0; + } + /** Add implements traits.Adder.Add. */ @Override public Val add(Val other) { @@ -126,15 +132,6 @@ public Val add(Val other) { } } - /** Compare implements traits.Comparer.Compare. */ - @Override - public Val compare(Val other) { - if (!(other instanceof IntT)) { - return noSuchOverload(this, "compare", other); - } - return IntT.intOf(Long.compare(i, ((IntT) other).i)); - } - /** ConvertToNative implements ref.Val.ConvertToNative. */ @SuppressWarnings("unchecked") @Override @@ -216,6 +213,63 @@ public Val convertToType(Type typeValue) { return newTypeConversionError(IntType, typeValue); } + /** Compare implements traits.Comparer.Compare. */ + @Override + public Val compare(Val other) { + switch (other.type().typeEnum()) { + case Double: + DoubleT cmp = (DoubleT) convertToType(DoubleType); + return cmp.compare(other); + case Uint: + if (isNegativeNumber()) { + // this int is negative, so it MUST be smaller than any uint + return IntT.IntNegOne; + } + if (other.isNegativeNumber()) { + // the OTHER uint is > Integer.MAX_VALUE, so THIS int MUST be smaller + return IntT.IntNegOne; + } + return IntT.intOf(Long.compareUnsigned(i, other.intValue())); + case Int: + case String: + Val converted = other.convertToType(type()); + if (converted.type().typeEnum() == TypeEnum.Err) { + return converted; + } + return IntT.intOf(Long.compare(i, converted.intValue())); + default: + return noSuchOverload(this, "compare", other); + } + } + + /** Equal implements ref.Val.Equal. */ + @Override + public Val equal(Val other) { + switch (other.type().typeEnum()) { + case Double: + DoubleT cmp = (DoubleT) convertToType(DoubleType); + return cmp.equal(other); + case Uint: + if (other.intValue() < 0L) { + return False; + } + case Int: + case String: + Val converted = other.convertToType(type()); + if (converted.type().typeEnum() == TypeEnum.Err) { + return converted; + } + return boolOf(i == converted.intValue()); + case Null: + case Bytes: + case List: + case Map: + return False; + default: + return noSuchOverload(this, "equal", other); + } + } + /** Divide implements traits.Divider.Divide. */ @Override public Val divide(Val other) { @@ -233,15 +287,6 @@ public Val divide(Val other) { } } - /** Equal implements ref.Val.Equal. */ - @Override - public Val equal(Val other) { - if (!(other instanceof IntT)) { - return noSuchOverload(this, "equal", other); - } - return boolOf(i == ((IntT) other).i); - } - /** Modulo implements traits.Modder.Modulo. */ @Override public Val modulo(Val other) { @@ -312,16 +357,15 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof Val)) { return false; } - IntT intT = (IntT) o; - return i == intT.i; + return equal((Val) o).booleanValue(); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), i); + return (int) i; } /** diff --git a/core/src/main/java/org/projectnessie/cel/common/types/ListT.java b/core/src/main/java/org/projectnessie/cel/common/types/ListT.java index c4c48d88..c68cc839 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/ListT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/ListT.java @@ -196,8 +196,11 @@ public Val equal(Val other) { if (isError(e2)) { return e2; } - if (e1.type() != e2.type()) { - return noSuchOverload(e1, Operator.Equals.id, e2); + if (!e1.type().equals(e2.type())) { + e2 = e2.convertToType(e2.type()); + if (e2.type().typeEnum() == TypeEnum.Err) { + return noSuchOverload(e1, Operator.Equals.id, e2); + } } if (e1.equal(e2) != True) { return False; @@ -222,9 +225,10 @@ public Val contains(Val value) { return True; } } - if (mixedType != null) { - return noSuchOverload(value, Operator.In.id, firstType, mixedType); - } + // TODO safe to omit? + // if (mixedType != null) { + // return noSuchOverload(value, Operator.In.id, firstType, mixedType); + // } return False; } @@ -303,8 +307,18 @@ public Val add(Val other) { @Override public Val get(Val index) { - if (!(index instanceof IntT)) { - return valOrErr(index, "unsupported index type '%s' in list", index.type()); + switch (index.type().typeEnum()) { + case Int: + case Uint: + break; + case Double: + double od = (double) index.value(); + if (Math.rint(od) != od) { + return newErr("invalid_argument"); + } + break; + default: + return valOrErr(index, "unsupported index type '%s' in list", index.type()); } int sz = array.length; int i = (int) index.intValue(); @@ -369,8 +383,18 @@ public Val add(Val other) { @Override public Val get(Val index) { - if (!(index instanceof IntT)) { - return valOrErr(index, "unsupported index type '%s' in list", index.type()); + switch (index.type().typeEnum()) { + case Int: + case Uint: + break; + case Double: + double od = (double) index.value(); + if (Math.rint(od) != od) { + return newErr("invalid_argument"); + } + break; + default: + return valOrErr(index, "unsupported index type '%s' in list", index.type()); } int sz = array.length; int i = (int) index.intValue(); diff --git a/core/src/main/java/org/projectnessie/cel/common/types/MapT.java b/core/src/main/java/org/projectnessie/cel/common/types/MapT.java index a1f08e6c..a39b5a31 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/MapT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/MapT.java @@ -17,12 +17,15 @@ import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.BoolT.True; +import static org.projectnessie.cel.common.types.DoubleT.DoubleType; import static org.projectnessie.cel.common.types.Err.isError; +import static org.projectnessie.cel.common.types.Err.newErr; import static org.projectnessie.cel.common.types.Err.newTypeConversionError; -import static org.projectnessie.cel.common.types.Err.noSuchOverload; +import static org.projectnessie.cel.common.types.IntT.IntType; import static org.projectnessie.cel.common.types.StringT.StringType; import static org.projectnessie.cel.common.types.TypeT.TypeType; import static org.projectnessie.cel.common.types.Types.boolOf; +import static org.projectnessie.cel.common.types.UintT.UintType; import com.google.protobuf.Any; import com.google.protobuf.Struct; @@ -30,7 +33,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.projectnessie.cel.common.operators.Operator; import org.projectnessie.cel.common.types.ref.BaseVal; import org.projectnessie.cel.common.types.ref.Type; import org.projectnessie.cel.common.types.ref.TypeAdapter; @@ -58,7 +60,16 @@ public static Val newWrappedMap(TypeAdapter adapter, Map value) { public static Val newMaybeWrappedMap(TypeAdapter adapter, Map value) { Map newMap = new HashMap<>(value.size() * 4 / 3 + 1); - value.forEach((k, v) -> newMap.put(adapter.nativeToValue(k), adapter.nativeToValue(v))); + for (Map.Entry entry : value.entrySet()) { + Val k = adapter.nativeToValue(entry.getKey()); + Val v = adapter.nativeToValue(entry.getValue()); + if (k.type().typeEnum() == TypeEnum.Null) { + return newErr("unsupported key type"); + } + if (newMap.putIfAbsent(k, v) != null) { + return Err.newErr("Failed with repeated key"); + } + } return newWrappedMap(adapter, newMap); } @@ -165,10 +176,22 @@ public Val equal(Val other) { if (isError(oVal)) { return val; } - if (val.type() != oVal.type()) { - return noSuchOverload(val, Operator.Equals.id, oVal); - } + Val eq = val.equal(oVal); + if (eq == True) { + continue; + } + if (eq == False) { + return False; + } + + if (!val.type().equals(oVal.type())) { + oVal = oVal.convertToType(val.type()); + if (oVal.type().typeEnum() == TypeEnum.Err) { + return False; + } + } + eq = val.equal(oVal); if (eq instanceof Err) { return eq; } @@ -188,7 +211,7 @@ public Object value() { @Override public Val contains(Val value) { - return boolOf(map.containsKey(value)); + return boolOf(find(value) != null); } @Override @@ -203,7 +226,36 @@ public Val size() { @Override public Val find(Val key) { - return map.get(key); + Val found = map.get(key); + if (found == null) { + switch (key.type().typeEnum()) { + case Int: + found = map.get(key.convertToType(UintType)); + if (found != null) { + return found; + } + found = map.get(key.convertToType(DoubleType)); + break; + case Uint: + found = map.get(key.convertToType(IntType)); + if (found != null) { + return found; + } + found = map.get(key.convertToType(DoubleType)); + break; + case Double: + double d = (double) key.value(); + if (Math.rint(d) == d) { + found = map.get(key.convertToType(UintType)); + if (found != null) { + return found; + } + found = map.get(key.convertToType(IntType)); + } + break; + } + } + return found; } @Override diff --git a/core/src/main/java/org/projectnessie/cel/common/types/NullT.java b/core/src/main/java/org/projectnessie/cel/common/types/NullT.java index 80ea8301..948126d2 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/NullT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/NullT.java @@ -15,6 +15,7 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.BoolT.True; import static org.projectnessie.cel.common.types.Err.newTypeConversionError; import static org.projectnessie.cel.common.types.Err.noSuchOverload; @@ -95,10 +96,18 @@ public Val convertToType(Type typeValue) { /** Equal implements ref.Val.Equal. */ @Override public Val equal(Val other) { - if (NullType != other.type()) { - return noSuchOverload(this, "equal", other); + switch (other.type().typeEnum()) { + case Null: + return True; + case Int: + case Uint: + case Double: + case String: + case Bytes: + return False; + default: + return noSuchOverload(this, "equal", other); } - return True; } /** Type implements ref.Val.Type. */ diff --git a/core/src/main/java/org/projectnessie/cel/common/types/ObjectT.java b/core/src/main/java/org/projectnessie/cel/common/types/ObjectT.java index 96871e31..e7f8c100 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/ObjectT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/ObjectT.java @@ -15,8 +15,8 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.Err.newTypeConversionError; -import static org.projectnessie.cel.common.types.Err.noSuchOverload; import static org.projectnessie.cel.common.types.Types.boolOf; import java.util.Objects; @@ -24,6 +24,7 @@ import org.projectnessie.cel.common.types.ref.Type; import org.projectnessie.cel.common.types.ref.TypeAdapter; import org.projectnessie.cel.common.types.ref.TypeDescription; +import org.projectnessie.cel.common.types.ref.TypeEnum; import org.projectnessie.cel.common.types.ref.Val; import org.projectnessie.cel.common.types.traits.FieldTester; import org.projectnessie.cel.common.types.traits.Indexer; @@ -57,9 +58,13 @@ public Val convertToType(Type typeVal) { @Override public Val equal(Val other) { + if (other.type().typeEnum() != TypeEnum.Object) { + return False; + } if (!typeDesc.name().equals(other.type().typeName())) { - return noSuchOverload(this, "equal", other); + return False; } + return boolOf(this.value.equals(other.value())); } diff --git a/core/src/main/java/org/projectnessie/cel/common/types/StringT.java b/core/src/main/java/org/projectnessie/cel/common/types/StringT.java index 2d29ba1f..238b544e 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/StringT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/StringT.java @@ -167,10 +167,19 @@ public Val convertToType(Type typeVal) { /** Equal implements ref.Val.Equal. */ @Override public Val equal(Val other) { - if (!(other instanceof StringT)) { - return noSuchOverload(this, "equal", other); + switch (other.type().typeEnum()) { + case String: + return boolOf(s.equals(((StringT) other).s)); + case Int: + case Uint: + case Double: + case Bool: + return boolOf(s.equals(((StringT) other.convertToType(StringType)).s)); + case Null: + return False; + default: + return noSuchOverload(this, "equal", other); } - return boolOf(s.equals(((StringT) other).s)); } /** Match implements traits.Matcher.Match. */ diff --git a/core/src/main/java/org/projectnessie/cel/common/types/TimestampT.java b/core/src/main/java/org/projectnessie/cel/common/types/TimestampT.java index 05ec1fa3..c16cf220 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/TimestampT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/TimestampT.java @@ -15,6 +15,7 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.DurationT.durationOf; import static org.projectnessie.cel.common.types.Err.errDurationOverflow; import static org.projectnessie.cel.common.types.Err.errTimestampOutOfRange; @@ -352,10 +353,14 @@ public Val convertToType(Type typeValue) { /** Equal implements ref.Val.Equal. */ @Override public Val equal(Val other) { - if (TimestampType != other.type()) { - return noSuchOverload(this, "equal", other); + switch (other.type().typeEnum()) { + case Timestamp: + return boolOf(t.equals(((TimestampT) other).t)); + case Null: + return False; + default: + return noSuchOverload(this, "equal", other); } - return boolOf(t.equals(((TimestampT) other).t)); } /** Receive implements traits.Reciever.Receive. */ diff --git a/core/src/main/java/org/projectnessie/cel/common/types/TypeT.java b/core/src/main/java/org/projectnessie/cel/common/types/TypeT.java index 3eced2fd..fd68307a 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/TypeT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/TypeT.java @@ -79,6 +79,11 @@ public long intValue() { throw new UnsupportedOperationException(); } + @Override + public boolean isNegativeNumber() { + throw new UnsupportedOperationException(); + } + /** ConvertToNative implements ref.Val.ConvertToNative. */ @Override public T convertToNative(Class typeDesc) { diff --git a/core/src/main/java/org/projectnessie/cel/common/types/UintT.java b/core/src/main/java/org/projectnessie/cel/common/types/UintT.java index de2b9432..de053e19 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/UintT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/UintT.java @@ -15,6 +15,8 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.BoolT.False; +import static org.projectnessie.cel.common.types.DoubleT.DoubleType; import static org.projectnessie.cel.common.types.DoubleT.doubleOf; import static org.projectnessie.cel.common.types.Err.divideByZero; import static org.projectnessie.cel.common.types.Err.errUintOverflow; @@ -22,6 +24,7 @@ import static org.projectnessie.cel.common.types.Err.newTypeConversionError; import static org.projectnessie.cel.common.types.Err.noSuchOverload; import static org.projectnessie.cel.common.types.Err.rangeError; +import static org.projectnessie.cel.common.types.IntT.IntOne; import static org.projectnessie.cel.common.types.IntT.intOf; import static org.projectnessie.cel.common.types.IntT.maxIntJSON; import static org.projectnessie.cel.common.types.StringT.stringOf; @@ -32,7 +35,6 @@ import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; import java.math.BigInteger; -import java.util.Objects; import org.projectnessie.cel.common.ULong; import org.projectnessie.cel.common.types.Overflow.OverflowException; import org.projectnessie.cel.common.types.ref.BaseVal; @@ -87,6 +89,11 @@ public long intValue() { return i; } + @Override + public boolean isNegativeNumber() { + return false; + } + /** Add implements traits.Adder.Add. */ @Override public Val add(Val other) { @@ -100,27 +107,17 @@ public Val add(Val other) { } } - /** Compare implements traits.Comparer.Compare. */ - @Override - public Val compare(Val other) { - if (other.type() != UintType) { - return noSuchOverload(this, "compare", other); - } - return intOf(Long.compareUnsigned(i, ((UintT) other).i)); - } - /** ConvertToNative implements ref.Val.ConvertToNative. */ @SuppressWarnings("unchecked") @Override public T convertToNative(Class typeDesc) { if (typeDesc == Long.class || typeDesc == long.class || typeDesc == Object.class) { - if (i < 0) { - Err.throwErrorAsIllegalStateException(rangeError(i, "Java long")); - } + // no "is negative" check here, because there is no Java representation of an + // unsigned long, reusing Java's signed long return (T) Long.valueOf(i); } if (typeDesc == Integer.class || typeDesc == int.class) { - if (i < 0 || i > Integer.MAX_VALUE) { + if (i < Integer.MIN_VALUE || i > Integer.MAX_VALUE) { Err.throwErrorAsIllegalStateException(rangeError(i, "Java int")); } return (T) Integer.valueOf((int) i); @@ -180,6 +177,81 @@ public Val convertToType(Type typeValue) { return newTypeConversionError(UintType, typeValue); } + /** Compare implements traits.Comparer.Compare. */ + @Override + public Val compare(Val other) { + switch (other.type().typeEnum()) { + case Int: + if (other.type().typeEnum() == TypeEnum.Err) { + return other; + } + if (other.isNegativeNumber()) { + // the other int is < 0, so any uint is greater + return IntOne; + } + if (isNegativeNumber()) { + // this uint is > Long.MAX_VALUE, so it MUST be greater than any signed int + return IntOne; + } + return intOf(Long.compareUnsigned(i, other.intValue())); + case Double: + if (other.type().typeEnum() == TypeEnum.Err) { + return other; + } + if (other.isNegativeNumber()) { + // the other int is < 0, so any uint is greater + return IntOne; + } + DoubleT cmp = (DoubleT) convertToType(DoubleType); + return cmp.compare(other); + case Uint: + case String: + Val converted = other.convertToType(type()); + if (converted.type().typeEnum() == TypeEnum.Err) { + return converted; + } + return intOf(Long.compareUnsigned(i, ((UintT) converted).i)); + default: + return noSuchOverload(this, "compare", other); + } + } + + /** Equal implements ref.Val.Equal. */ + @Override + public Val equal(Val other) { + switch (other.type().typeEnum()) { + case Int: + if (other.type().typeEnum() == TypeEnum.Err) { + return other; + } + if (other.isNegativeNumber()) { + // the other int is < 0, so no uint can be equal + return False; + } + if (isNegativeNumber()) { + // this uint is > Long.MAX_VALUE, so it CANNOT be equal + return False; + } + return boolOf(i == other.intValue()); + case Double: + return other.equal(this); + case Uint: + case String: + Val converted = other.convertToType(type()); + if (converted.type().typeEnum() == TypeEnum.Err) { + return converted; + } + return boolOf(i == converted.intValue()); + case Null: + case Bytes: + case List: + case Map: + return False; + default: + return noSuchOverload(this, "equal", other); + } + } + /** Divide implements traits.Divider.Divide. */ @Override public Val divide(Val other) { @@ -193,15 +265,6 @@ public Val divide(Val other) { return uintOf(i / otherInt); } - /** Equal implements ref.Val.Equal. */ - @Override - public Val equal(Val other) { - if (other.type() != UintType) { - return noSuchOverload(this, "equal", other); - } - return boolOf(i == ((UintT) other).i); - } - /** Modulo implements traits.Modder.Modulo. */ @Override public Val modulo(Val other) { @@ -258,16 +321,19 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof Val)) { return false; } - UintT uintT = (UintT) o; - return i == uintT.i; + return equal((Val) o).booleanValue(); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), i); + return (int) i; + } + + public String toString() { + return String.format("%s{%s}", type().typeName(), Long.toUnsignedString(i)); } /** diff --git a/core/src/main/java/org/projectnessie/cel/common/types/UnknownT.java b/core/src/main/java/org/projectnessie/cel/common/types/UnknownT.java index 42add63d..7630d266 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/UnknownT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/UnknownT.java @@ -15,6 +15,8 @@ */ package org.projectnessie.cel.common.types; +import static org.projectnessie.cel.common.types.Types.boolOf; + import java.util.Objects; import org.projectnessie.cel.common.types.ref.BaseVal; import org.projectnessie.cel.common.types.ref.Type; @@ -70,7 +72,7 @@ public Val convertToType(Type typeVal) { /** Equal implements ref.Val.Equal. */ @Override public Val equal(Val other) { - return this; + return boolOf(other.type().typeEnum() == TypeEnum.Unknown); } /** Type implements ref.Val.Type. */ diff --git a/core/src/main/java/org/projectnessie/cel/common/types/ref/BaseVal.java b/core/src/main/java/org/projectnessie/cel/common/types/ref/BaseVal.java index 3dee153a..3a6c103c 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/ref/BaseVal.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/ref/BaseVal.java @@ -48,4 +48,9 @@ public boolean booleanValue() { public long intValue() { return convertToType(IntType).intValue(); } + + @Override + public boolean isNegativeNumber() { + throw new UnsupportedOperationException(); + } } diff --git a/core/src/main/java/org/projectnessie/cel/common/types/ref/Val.java b/core/src/main/java/org/projectnessie/cel/common/types/ref/Val.java index 34de489a..6ffa32c2 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/ref/Val.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/ref/Val.java @@ -50,4 +50,6 @@ public interface Val { boolean booleanValue(); long intValue(); + + boolean isNegativeNumber(); } diff --git a/core/src/main/java/org/projectnessie/cel/interpreter/AttributeFactory.java b/core/src/main/java/org/projectnessie/cel/interpreter/AttributeFactory.java index ac5b5679..fa535b76 100644 --- a/core/src/main/java/org/projectnessie/cel/interpreter/AttributeFactory.java +++ b/core/src/main/java/org/projectnessie/cel/interpreter/AttributeFactory.java @@ -17,6 +17,7 @@ import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.BoolT.True; +import static org.projectnessie.cel.common.types.DoubleT.doubleOf; import static org.projectnessie.cel.common.types.Err.ErrException; import static org.projectnessie.cel.common.types.Err.indexOutOfBoundsException; import static org.projectnessie.cel.common.types.Err.isError; @@ -27,6 +28,7 @@ import static org.projectnessie.cel.common.types.Err.noSuchOverload; import static org.projectnessie.cel.common.types.Err.throwErrorAsIllegalStateException; import static org.projectnessie.cel.common.types.IntT.intOf; +import static org.projectnessie.cel.common.types.NullT.NullValue; import static org.projectnessie.cel.common.types.StringT.stringOf; import static org.projectnessie.cel.common.types.Types.boolOf; import static org.projectnessie.cel.common.types.UintT.uintOf; @@ -43,7 +45,6 @@ import org.projectnessie.cel.common.ULong; import org.projectnessie.cel.common.containers.Container; import org.projectnessie.cel.common.types.Err; -import org.projectnessie.cel.common.types.NullT; import org.projectnessie.cel.common.types.ref.FieldType; import org.projectnessie.cel.common.types.ref.TypeAdapter; import org.projectnessie.cel.common.types.ref.TypeProvider; @@ -729,12 +730,16 @@ static Qualifier newQualifierStatic(TypeAdapter adapter, long id, Object v) { switch (val.type().typeEnum()) { case String: return new StringQualifier(id, (String) val.value(), val, adapter); + case Double: + return new DoubleQualifier(id, (double) val.value(), val, adapter); case Int: return new IntQualifier(id, val.intValue(), val, adapter); case Uint: return new UintQualifier(id, val.intValue(), val, adapter); case Bool: return new BoolQualifier(id, val.booleanValue(), val, adapter); + case Null: + return new NullQualifier(id, val, adapter); } } @@ -749,6 +754,10 @@ static Qualifier newQualifierStatic(TypeAdapter adapter, long id, Object v) { long i = ((Number) v).longValue(); return new IntQualifier(id, i, intOf(i), adapter); } + if (c == Double.class) { + double b = (Double) v; + return new DoubleQualifier(id, b, doubleOf(b), adapter); + } if (c == Boolean.class) { boolean b = (Boolean) v; return new BoolQualifier(id, b, boolOf(b), adapter); @@ -828,7 +837,7 @@ public Object qualify(org.projectnessie.cel.interpreter.Activation vars, Object obj = m.get(s); if (obj == null) { if (m.containsKey(s)) { - return NullT.NullValue; + return NullValue; } throw noSuchKeyException(s); } @@ -876,6 +885,105 @@ public String toString() { } } + final class DoubleQualifier implements Coster, ConstantQualifierEquator { + final long id; + final double value; + final Val celValue; + final TypeAdapter adapter; + + DoubleQualifier(long id, double value, Val celValue, TypeAdapter adapter) { + this.id = id; + this.value = value; + this.celValue = celValue; + this.adapter = adapter; + } + + /** ID is an implementation of the Qualifier interface method. */ + @Override + public long id() { + return id; + } + + /** Qualify implements the Qualifier interface method. */ + @SuppressWarnings("rawtypes") + @Override + public Object qualify(org.projectnessie.cel.interpreter.Activation vars, Object obj) { + double i = value; + if (obj instanceof Map) { + Map m = (Map) obj; + obj = m.get(i); + if (obj == null) { + obj = m.get((int) i); + } + if (obj == null) { + if (m.containsKey(i) || m.containsKey((int) i)) { + return null; + } + throw noSuchKeyException(i); + } + return obj; + } + if (obj.getClass().isArray()) { + int l = Array.getLength(obj); + if (i < 0 || i >= l) { + throw indexOutOfBoundsException(i); + } + obj = Array.get(obj, (int) i); + return obj; + } + if (obj instanceof List) { + List list = (List) obj; + int l = list.size(); + if (i < 0 || i >= l) { + throw indexOutOfBoundsException(i); + } + obj = list.get((int) i); + return obj; + } + if (isUnknown(obj)) { + return obj; + } + return refResolve(adapter, celValue, obj); + } + + /** Value implements the ConstantQualifier interface */ + @Override + public Val value() { + return celValue; + } + + /** Cost returns zero for constant field qualifiers */ + @Override + public Cost cost() { + return Cost.None; + } + + @Override + public boolean qualifierValueEquals(Object value) { + if (value instanceof ULong) { + return false; + } + if (value instanceof Number) { + return this.value == ((Number) value).doubleValue(); + } + return false; + } + + @Override + public String toString() { + return "DoubleQualifier{" + + "id=" + + id + + ", value=" + + value + + ", celValue=" + + celValue + + ", adapter=" + + adapter + + '}'; + } + } + final class IntQualifier implements Coster, ConstantQualifierEquator { final long id; final long value; @@ -1132,6 +1240,59 @@ public String toString() { } } + final class NullQualifier implements Coster, ConstantQualifierEquator { + final long id; + final Val celValue; + final TypeAdapter adapter; + + NullQualifier(long id, Val celValue, TypeAdapter adapter) { + this.id = id; + this.celValue = celValue; + this.adapter = adapter; + } + + /** ID is an implementation of the Qualifier interface method. */ + @Override + public long id() { + return id; + } + + /** Qualify implements the Qualifier interface method. */ + @Override + public Object qualify(org.projectnessie.cel.interpreter.Activation vars, Object obj) { + return null; + } + + /** Value implements the ConstantQualifier interface */ + @Override + public Val value() { + return NullValue; + } + + /** Cost returns zero for constant field qualifiers */ + @Override + public Cost cost() { + return Cost.None; + } + + @Override + public boolean qualifierValueEquals(Object value) { + return value == null || value == NullValue; + } + + @Override + public String toString() { + return "NullQualifier{" + + "id=" + + id + + ", celValue=" + + celValue + + ", adapter=" + + adapter + + '}'; + } + } + /** * fieldQualifier indicates that the qualification is a well-defined field with a known field * type. When the field type is known this can be used to improve the speed and efficiency of diff --git a/core/src/main/java/org/projectnessie/cel/interpreter/Interpretable.java b/core/src/main/java/org/projectnessie/cel/interpreter/Interpretable.java index 9480118d..87255fb8 100644 --- a/core/src/main/java/org/projectnessie/cel/interpreter/Interpretable.java +++ b/core/src/main/java/org/projectnessie/cel/interpreter/Interpretable.java @@ -34,12 +34,14 @@ import java.util.Objects; import java.util.Set; import org.projectnessie.cel.common.operators.Operator; +import org.projectnessie.cel.common.types.Err; import org.projectnessie.cel.common.types.IterableT; import org.projectnessie.cel.common.types.IteratorT; import org.projectnessie.cel.common.types.Overloads; import org.projectnessie.cel.common.types.StringT; import org.projectnessie.cel.common.types.ref.FieldType; import org.projectnessie.cel.common.types.ref.TypeAdapter; +import org.projectnessie.cel.common.types.ref.TypeEnum; import org.projectnessie.cel.common.types.ref.TypeProvider; import org.projectnessie.cel.common.types.ref.Val; import org.projectnessie.cel.common.types.traits.Container; @@ -869,11 +871,16 @@ public Val eval(org.projectnessie.cel.interpreter.Activation ctx) { if (isUnknownOrError(keyVal)) { return keyVal; } + if (keyVal.type().typeEnum() == TypeEnum.Null) { + return newErr("unsupported key type"); + } Val valVal = vals[i].eval(ctx); if (isUnknownOrError(valVal)) { return valVal; } - entries.put(keyVal, valVal); + if (entries.putIfAbsent(keyVal, valVal) != null) { + return newErr("Failed with repeated key"); + } } return adapter.nativeToValue(entries); } diff --git a/core/src/test/java/org/projectnessie/cel/CELTest.java b/core/src/test/java/org/projectnessie/cel/CELTest.java index f0cd99ac..97cd7d22 100644 --- a/core/src/test/java/org/projectnessie/cel/CELTest.java +++ b/core/src/test/java/org/projectnessie/cel/CELTest.java @@ -49,6 +49,7 @@ import static org.projectnessie.cel.ProgramOption.functions; import static org.projectnessie.cel.ProgramOption.globals; import static org.projectnessie.cel.Util.mapOf; +import static org.projectnessie.cel.common.types.BoolT.False; import static org.projectnessie.cel.common.types.BoolT.True; import static org.projectnessie.cel.common.types.Err.isError; import static org.projectnessie.cel.common.types.Err.newErr; @@ -656,10 +657,10 @@ void ResidualAst_Complex() { assertThat(astIss.hasIssues()).isFalse(); Program prg = e.program(astIss.getAst(), evalOptions(OptTrackState, OptPartialEval)); EvalResult outDet = prg.eval(unkVars); - assertThat(outDet.getVal()).matches(UnknownT::isUnknown); + assertThat(outDet.getVal()).isSameAs(False); Ast residual = e.residualAst(astIss.getAst(), outDet.getEvalDetails()); String expr = astToString(residual); - assertThat(expr).isEqualTo("request.auth.claims.email == \"wiley@acme.co\""); + assertThat(expr).isEqualTo("false"); } @Test diff --git a/core/src/test/java/org/projectnessie/cel/checker/CheckerTest.java b/core/src/test/java/org/projectnessie/cel/checker/CheckerTest.java index 38739f62..6bc4194c 100644 --- a/core/src/test/java/org/projectnessie/cel/checker/CheckerTest.java +++ b/core/src/test/java/org/projectnessie/cel/checker/CheckerTest.java @@ -751,10 +751,9 @@ static TestCase[] checkTestCases() { .idents( Decls.newVar( "x", Decls.newObjectType("google.api.expr.test.v1.proto3.TestAllTypes")))) - .error( - "ERROR: :1:16: found no matching overload for '_!=_' applied to '(int, null)'\n" - + " | x.single_int64 != null\n" - + " | ...............^"), + .r( + "_!=_(x~google.api.expr.test.v1.proto3.TestAllTypes^x.single_int64~int,null~null)~bool^not_equals") + .type(Decls.Bool), new TestCase() .i("x.single_int64_wrapper == null") .env( diff --git a/core/src/test/java/org/projectnessie/cel/common/types/DurationTest.java b/core/src/test/java/org/projectnessie/cel/common/types/DurationTest.java index 0b65be60..d6a70eed 100644 --- a/core/src/test/java/org/projectnessie/cel/common/types/DurationTest.java +++ b/core/src/test/java/org/projectnessie/cel/common/types/DurationTest.java @@ -162,9 +162,9 @@ void durationGetSeconds() { @Test void durationGetMilliseconds() { - DurationT d = durationOf(ofSeconds(7506, 0)); + DurationT d = durationOf(ofSeconds(7506, 321456789)); Val min = d.receive(Overloads.TimeGetMilliseconds, Overloads.DurationToMilliseconds); - assertThat(min.equal(intOf(7506000))).isSameAs(True); + assertThat(min.equal(intOf(321))).isSameAs(True); } @Test diff --git a/core/src/test/java/org/projectnessie/cel/common/types/StringTest.java b/core/src/test/java/org/projectnessie/cel/common/types/StringTest.java index f9efb246..63e48256 100644 --- a/core/src/test/java/org/projectnessie/cel/common/types/StringTest.java +++ b/core/src/test/java/org/projectnessie/cel/common/types/StringTest.java @@ -155,10 +155,7 @@ void stringConvertToType() { void stringEqual() { assertThat(stringOf("hello").equal(stringOf("hello"))).isSameAs(True); assertThat(stringOf("hello").equal(stringOf("hell"))).isSameAs(False); - assertThat(stringOf("c").equal(intOf(99))) - .isInstanceOf(Err.class) - .extracting(Object::toString) - .isEqualTo("no such overload: string.equal(int)"); + assertThat(stringOf("c").equal(intOf(99))).isSameAs(False); } @Test diff --git a/core/src/test/java/org/projectnessie/cel/interpreter/InterpreterTest.java b/core/src/test/java/org/projectnessie/cel/interpreter/InterpreterTest.java index d0e8ec3c..ce58f0ad 100644 --- a/core/src/test/java/org/projectnessie/cel/interpreter/InterpreterTest.java +++ b/core/src/test/java/org/projectnessie/cel/interpreter/InterpreterTest.java @@ -239,6 +239,60 @@ TestCase err(String err) { @SuppressWarnings("unused") static TestCase[] testCases() { return new TestCase[] { + new TestCase(InterpreterTestCase.map_key_null) + .expr("{null:false}[null]") + .err("message: unsupported key type"), + new TestCase(InterpreterTestCase.map_value_repeat_key_heterogeneous) + .expr("{0: 1, 0u: 2}[0.0]") + .err("message: Failed with repeated key"), + new TestCase(InterpreterTestCase.map_key_mixed_numbers_lossy_double_key) + .expr("{1u: 1.0, 2: 2.0, 3u: 3.0}[3.1]") + .err("no such key: double{3.1}"), + new TestCase(InterpreterTestCase.zero_based_double_error) + .expr("[7, 8, 9][dyn(0.1)]") + .err("invalid_argument"), + new TestCase(InterpreterTestCase.zero_based_double).expr("[7, 8, 9][dyn(0.0)]").out(intOf(7)), + new TestCase(InterpreterTestCase.not_int32_eq_uint) + .expr("Int32Value{value: 34} == dyn(UInt64Value{value: 18446744073709551615u})") + .container("google.protobuf") + .out(False), + new TestCase(InterpreterTestCase.not_uint32_eq_double) + .expr("UInt32Value{value: 34u} == dyn(DoubleValue{value: 18446744073709551616.0})") + .container("google.protobuf") + .out(False), + new TestCase(InterpreterTestCase.eq_proto_different_types) + .expr("dyn(TestAllTypes{}) == dyn(NestedTestAllTypes{})") + .container("google.api.expr.test.v1.proto2") + .types( + com.google.api.expr.test.v1.proto2.TestAllTypesProto.TestAllTypes + .getDefaultInstance()) + .out(False), + new TestCase(InterpreterTestCase.not_lt_dyn_big_uint_int) + .expr("dyn(9223372036854775808u) < 1") + .out(False), + new TestCase(InterpreterTestCase.lt_dyn_int_big_uint) + .expr("dyn(1) < 9223372036854775808u") + .out(True), + new TestCase(InterpreterTestCase.lt_dyn_uint_big_double) + .expr("dyn(18446744073709551615u) < 18446744073709590000.0") + .out(True), + new TestCase(InterpreterTestCase.not_lt_dyn_uint_small_int) + .expr("dyn(1u) < -9223372036854775808") + .out(False), + new TestCase(InterpreterTestCase.lt_ne_dyn_int_double).expr("dyn(24) != 24.1").out(True), + new TestCase(InterpreterTestCase.eq_proto_nan_equal) + .expr( + "TestAllTypes{single_double: double('NaN')} == TestAllTypes{single_double: double('NaN')}") + .container("google.api.expr.test.v1.proto3") + .types( + com.google.api.expr.test.v1.proto3.TestAllTypesProto.TestAllTypes + .getDefaultInstance()) + // The outcome in the generated Java proto code is different than in the conformance-test, + // it is NOT: "For proto equality, fields with NaN value are treated as not equal." + .out(True), + new TestCase(InterpreterTestCase.eq_bool_not_null) + .expr("google.protobuf.BoolValue{} != null") + .out(True), new TestCase(InterpreterTestCase.literal_any) .expr( "google.protobuf.Any{type_url: 'type.googleapis.com/google.api.expr.test.v1.proto2.TestAllTypes', value: b'\\x08\\x96\\x01'}") @@ -272,9 +326,9 @@ static TestCase[] testCases() { com.google.api.expr.test.v1.proto3.TestAllTypesProto.TestAllTypes .getDefaultInstance()) .out(Struct.getDefaultInstance()), - new TestCase(InterpreterTestCase.elem_in_mixed_type_list_error) + new TestCase(InterpreterTestCase.elem_in_mixed_type_list2) .expr("'elem' in [1u, 'str', 2, b'bytes']") - .err("no such overload: string.@in(uint,bytes,...)"), + .out(False), new TestCase(InterpreterTestCase.elem_in_mixed_type_list) .expr("'elem' in [1, 'elem', 2]") .out(boolOf(true)), @@ -329,10 +383,22 @@ static TestCase[] testCases() { .setStandaloneEnumValue(-3) .build()) .out(intOf(-3)), - new TestCase(InterpreterTestCase.eq_list_elem_mixed_types_error) + new TestCase(InterpreterTestCase.eq_list_mixed_type_numbers) + .expr("[1.0, 2.0, 3] == [1u, 2, 3u]") + .out(True), + new TestCase(InterpreterTestCase.not_eq_list_mixed_type_numbers) + .expr("[1.0, 2.1] == [1u, 2]") + .out(False), + new TestCase(InterpreterTestCase.eq_list_elem_mixed_types_one_element) .expr("[1] == [1.0]") - .unchecked() - .err("no such overload: int._==_(double)"), + .out(True), + new TestCase(InterpreterTestCase.eq_list_elem_one_element) + .expr("['str'] == ['str']") + .out(True), + new TestCase(InterpreterTestCase.not_eq_list_one_element) + .expr("['str'] == ['blah']") + .out(False), + new TestCase(InterpreterTestCase.not_eq_list_one_element2).expr("[1] == [2]").out(False), new TestCase(InterpreterTestCase.parse_nest_message_literal) .container("google.api.expr.test.v1.proto3") .expr( @@ -1135,10 +1201,10 @@ static TestCase[] testCases() { .env(Decls.newVar("x", Decls.Duration)) .in( "x", - com.google.protobuf.Duration.newBuilder().setSeconds(123).setNanos(123456789).build()) + com.google.protobuf.Duration.newBuilder().setSeconds(123).setNanos(321456789).build()) .cost(costOf(2, 2)) .exhaustiveCost(costOf(2, 2)) - .out(123123), + .out(321), new TestCase(InterpreterTestCase.timestamp_get_hours_tz) .expr("timestamp('2009-02-13T23:31:30Z').getHours('2:00')") .out(intOf(1)) @@ -1563,6 +1629,7 @@ private void typeConversionOptCheck( if (tc.out != null) { assertThat(i).isInstanceOf(InterpretableConst.class); InterpretableConst ic = (InterpretableConst) i; + ic.value().equal(tc.out); assertThat(ic.value()).extracting(o -> o.equal(tc.out)).isSameAs(True); } } diff --git a/core/src/test/java/org/projectnessie/cel/interpreter/InterpreterTestCase.java b/core/src/test/java/org/projectnessie/cel/interpreter/InterpreterTestCase.java index 97045a41..2f0dbdf4 100644 --- a/core/src/test/java/org/projectnessie/cel/interpreter/InterpreterTestCase.java +++ b/core/src/test/java/org/projectnessie/cel/interpreter/InterpreterTestCase.java @@ -40,8 +40,14 @@ public enum InterpreterTestCase { cond_bad_type, duration_get_milliseconds, elem_in_mixed_type_list, - elem_in_mixed_type_list_error, + elem_in_mixed_type_list2, + eq_bool_not_null, + eq_list_elem_mixed_types_one_element, + eq_list_elem_one_element, eq_list_elem_mixed_types_error, + eq_list_mixed_type_numbers, + eq_proto_different_types, + eq_proto_nan_equal, in_list, in_map, index, @@ -63,6 +69,11 @@ public enum InterpreterTestCase { literal_pb_struct, literal_var, literal_any, + lt_dyn_int_big_uint, + lt_dyn_uint_big_double, + lt_ne_dyn_int_double, + map_key_mixed_numbers_lossy_double_key, + map_value_repeat_key_heterogeneous, timestamp_eq_timestamp, timestamp_ne_timestamp, timestamp_lt_timestamp, @@ -82,9 +93,17 @@ public enum InterpreterTestCase { macro_has_pb2_field, macro_has_pb3_field, macro_map, + map_key_null, matches, nested_proto_field, nested_proto_field_with_index, + not_eq_list_one_element, + not_eq_list_one_element2, + not_eq_list_mixed_type_numbers, + not_int32_eq_uint, + not_uint32_eq_double, + not_lt_dyn_big_uint_int, + not_lt_dyn_uint_small_int, or_true_1st, or_true_2nd, or_false, @@ -118,5 +137,7 @@ public enum InterpreterTestCase { select_subsumed_field, select_empty_repeated_nested, root_null_handling, - root_no_such_attribute + root_no_such_attribute, + zero_based_double, + zero_based_double_error } diff --git a/generated-pb/build.gradle.kts b/generated-pb/build.gradle.kts index 07d87732..97a8ffc5 100644 --- a/generated-pb/build.gradle.kts +++ b/generated-pb/build.gradle.kts @@ -29,7 +29,6 @@ apply() sourceSets.main { java.srcDir(layout.buildDirectory.dir("generated/source/proto/main/java")) - java.srcDir(layout.buildDirectory.dir("generated/source/proto/main/grpc")) java.destinationDirectory.set(layout.buildDirectory.dir("classes/java/generated")) } @@ -56,10 +55,6 @@ configure { // Download from repositories artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.get()}" } - plugins { - this.create("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:${libs.versions.grpc.get()}" } - } - generateProtoTasks { all().configureEach { this.plugins.create("grpc") {} } } } reflectionConfig { diff --git a/submodules/cel-spec b/submodules/cel-spec index 8ff24bab..7eb4db1a 160000 --- a/submodules/cel-spec +++ b/submodules/cel-spec @@ -1 +1 @@ -Subproject commit 8ff24babd31ef9caab6f24f303b7bd7528e2954b +Subproject commit 7eb4db1aa8cebecb71b2b33a0ced33b9ae5f4fdc diff --git a/submodules/googleapis b/submodules/googleapis index 0bcee5c6..f2d78630 160000 --- a/submodules/googleapis +++ b/submodules/googleapis @@ -1 +1 @@ -Subproject commit 0bcee5c673ecd59ffae84e8104aaa68c68e7e43a +Subproject commit f2d78630d2c1d5e20041dfff963e093de9298e4d