Skip to content

Commit 9c4c430

Browse files
maskri17copybara-github
authored andcommitted
Internal cleanup
PiperOrigin-RevId: 974574709
1 parent a2353b3 commit 9c4c430

7 files changed

Lines changed: 192 additions & 53 deletions

File tree

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ bazel_dep(name = "rules_java", version = "9.7.0")
2727
bazel_dep(name = "rules_android", version = "0.7.3")
2828
bazel_dep(name = "rules_shell", version = "0.8.0")
2929
bazel_dep(name = "googleapis-java", version = "1.1.5")
30-
bazel_dep(name = "cel-spec", version = "0.25.2", repo_name = "cel_spec")
30+
bazel_dep(name = "cel-spec", version = "0.25.3", repo_name = "cel_spec")
3131
bazel_dep(name = "rules_go", version = "0.62.0")
3232

3333
# Required by cel-spec to satisfy gazelle transitive dependency

conformance/src/test/java/dev/cel/conformance/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ _ALL_TESTS = [
8989
"@cel_spec//tests/simple:testdata/fp_math.textproto",
9090
"@cel_spec//tests/simple:testdata/integer_math.textproto",
9191
"@cel_spec//tests/simple:testdata/lists.textproto",
92+
"@cel_spec//tests/simple:testdata/lists_ext.textproto",
9293
"@cel_spec//tests/simple:testdata/logic.textproto",
9394
"@cel_spec//tests/simple:testdata/macros.textproto",
9495
"@cel_spec//tests/simple:testdata/macros2.textproto",

conformance/src/test/java/dev/cel/conformance/ConformanceTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ public final class ConformanceTest extends Statement {
8282
CelExtensions.bindings(),
8383
CelExtensions.comprehensions(),
8484
CelExtensions.encoders(OPTIONS),
85-
CelExtensions.math(OPTIONS),
85+
CelExtensions.lists(),
86+
CelExtensions.math(),
8687
CelExtensions.protos(),
8788
CelExtensions.sets(OPTIONS),
8889
CelExtensions.strings(),
@@ -93,7 +94,8 @@ public final class ConformanceTest extends Statement {
9394
ImmutableList.of(
9495
CelExtensions.comprehensions(),
9596
CelExtensions.encoders(OPTIONS),
96-
CelExtensions.math(OPTIONS),
97+
CelExtensions.lists(),
98+
CelExtensions.math(),
9799
CelExtensions.sets(OPTIONS),
98100
CelExtensions.strings(),
99101
CelOptionalLibrary.INSTANCE);

extensions/src/main/java/dev/cel/extensions/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ java_library(
274274
"//common/ast",
275275
"//common/internal:comparison_functions",
276276
"//common/types",
277+
"//common/values:cel_byte_string",
277278
"//compiler:compiler_builder",
278279
"//extensions:extension_library",
279280
"//parser:macro",

extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java

Lines changed: 134 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import dev.cel.common.types.ListType;
3535
import dev.cel.common.types.SimpleType;
3636
import dev.cel.common.types.TypeParamType;
37+
import dev.cel.common.values.CelByteString;
3738
import dev.cel.compiler.CelCompilerLibrary;
3839
import dev.cel.parser.CelMacro;
3940
import dev.cel.parser.CelMacroExprFactory;
@@ -42,11 +43,14 @@
4243
import dev.cel.runtime.CelInternalRuntimeLibrary;
4344
import dev.cel.runtime.CelRuntimeBuilder;
4445
import dev.cel.runtime.RuntimeEquality;
46+
import java.time.Duration;
47+
import java.time.Instant;
4548
import java.util.Arrays;
4649
import java.util.Collection;
4750
import java.util.Comparator;
4851
import java.util.Iterator;
4952
import java.util.List;
53+
import java.util.Map;
5054
import java.util.Optional;
5155
import java.util.Set;
5256

@@ -132,16 +136,21 @@ public enum Function {
132136
CelFunctionBinding.from("list_sort", Collection.class, CelListsExtensions::sort)),
133137
SORT_BY(
134138
CelFunctionDecl.newFunctionDeclaration(
135-
"lists.@sortByAssociatedKeys",
136-
CelOverloadDecl.newGlobalOverload(
139+
"@sortByAssociatedKeys",
140+
CelOverloadDecl.newMemberOverload(
137141
"list_sortByAssociatedKeys",
138-
"Sorts a list by a key value. Used by the 'sortBy' macro",
142+
"Sorts a list by associated keys. Used by the 'sortBy' macro",
139143
ListType.create(TypeParamType.create("T")),
140-
ListType.create(TypeParamType.create("T")))),
144+
ListType.create(TypeParamType.create("T")),
145+
ListType.create(TypeParamType.create("U")))),
141146
CelFunctionBinding.from(
142147
"list_sortByAssociatedKeys",
143-
Collection.class,
144-
CelListsExtensions::sortByAssociatedKeys));
148+
ImmutableList.of(Collection.class, Collection.class),
149+
(args) -> {
150+
Collection<Object> target = (Collection<Object>) args[0];
151+
Collection<Object> keys = (Collection<Object>) args[1];
152+
return CelListsExtensions.sortByAssociatedKeys(target, keys);
153+
}));
145154

146155
private final CelFunctionDecl functionDecl;
147156
private final ImmutableSet<CelFunctionBinding> functionBindings;
@@ -222,7 +231,7 @@ public ImmutableSet<CelFunctionDecl> functions() {
222231

223232
@Override
224233
public ImmutableSet<CelMacro> macros() {
225-
if (version >= 2) {
234+
if (version >= 2 || (version == -1 && functions.contains(Function.SORT_BY))) {
226235
return ImmutableSet.of(
227236
CelMacro.newReceiverMacro("sortBy", 2, CelListsExtensions::sortByMacro));
228237
}
@@ -300,7 +309,10 @@ private static ImmutableList<Object> flatten(Collection<Object> list, long depth
300309
}
301310

302311
public static ImmutableList<Long> genRange(long end) {
303-
ImmutableList.Builder<Long> builder = ImmutableList.builder();
312+
checkArgument(end >= 0, "lists.range: size must be non-negative, got %s", end);
313+
checkArgument(end <= 1_000_000, "lists.range: size %s exceeds maximum allowed (1000000)", end);
314+
315+
ImmutableList.Builder<Long> builder = ImmutableList.builderWithExpectedSize((int) end);
304316
for (long i = 0; i < end; i++) {
305317
builder.add(i);
306318
}
@@ -359,6 +371,17 @@ private static List<Object> reverse(Collection<Object> list) {
359371
}
360372

361373
private static ImmutableList<Object> sort(Collection<Object> objects) {
374+
if (objects.isEmpty()) {
375+
return ImmutableList.of();
376+
}
377+
for (Object element : objects) {
378+
if (!isSupportedComparableType(element)) {
379+
throw new IllegalArgumentException("List elements must be comparable");
380+
}
381+
}
382+
if (objects.size() < 2) {
383+
return ImmutableList.copyOf(objects);
384+
}
362385
return ImmutableList.sortedCopyOf(new CelObjectComparator(), objects);
363386
}
364387

@@ -369,12 +392,14 @@ private static class CelObjectComparator implements Comparator<Object> {
369392
@SuppressWarnings({"unchecked"})
370393
@Override
371394
public int compare(Object o1, Object o2) {
395+
if (o1 == null || o2 == null) {
396+
throw new IllegalArgumentException("List elements must be comparable");
397+
}
372398
if (o1 instanceof Number && o2 instanceof Number) {
373399
return ComparisonFunctions.numericCompare((Number) o1, (Number) o2);
374400
}
375-
376-
if (!(o1 instanceof Comparable)) {
377-
throw new IllegalArgumentException("List elements must be comparable");
401+
if (isByteType(o1) && isByteType(o2)) {
402+
return compareBytes(o1, o2);
378403
}
379404
if (o1.getClass() != o2.getClass()) {
380405
throw new IllegalArgumentException("List elements must have the same type");
@@ -383,6 +408,45 @@ public int compare(Object o1, Object o2) {
383408
}
384409
}
385410

411+
private static boolean isByteType(Object obj) {
412+
return obj instanceof CelByteString || obj instanceof byte[];
413+
}
414+
415+
private static int compareBytes(Object o1, Object o2) {
416+
if (o1 instanceof CelByteString && o2 instanceof CelByteString) {
417+
return CelByteString.unsignedLexicographicalComparator()
418+
.compare((CelByteString) o1, (CelByteString) o2);
419+
}
420+
421+
byte[] b1 = o1 instanceof CelByteString ? ((CelByteString) o1).toByteArray() : (byte[]) o1;
422+
byte[] b2 = o2 instanceof CelByteString ? ((CelByteString) o2).toByteArray() : (byte[]) o2;
423+
424+
int minLength = Math.min(b1.length, b2.length);
425+
for (int i = 0; i < minLength; i++) {
426+
int result = Integer.compare(Byte.toUnsignedInt(b1[i]), Byte.toUnsignedInt(b2[i]));
427+
if (result != 0) {
428+
return result;
429+
}
430+
}
431+
return Integer.compare(b1.length, b2.length);
432+
}
433+
434+
private static boolean isSupportedComparableType(Object obj) {
435+
if (obj == null) {
436+
return false;
437+
}
438+
if (obj instanceof Number
439+
|| obj instanceof Boolean
440+
|| obj instanceof String
441+
|| obj instanceof CelByteString
442+
|| obj instanceof byte[]
443+
|| obj instanceof Duration
444+
|| obj instanceof Instant) {
445+
return true;
446+
}
447+
return obj instanceof Comparable && !(obj instanceof Collection) && !(obj instanceof Map);
448+
}
449+
386450
private static Optional<CelExpr> sortByMacro(
387451
CelMacroExprFactory exprFactory, CelExpr target, ImmutableList<CelExpr> arguments) {
388452
checkNotNull(exprFactory);
@@ -400,56 +464,79 @@ private static Optional<CelExpr> sortByMacro(
400464
String varName = varIdent.ident().name();
401465
CelExpr sortKeyExpr = checkNotNull(arguments.get(1));
402466

403-
// Compute the key using the second argument of the `sortBy(e, key)` macro.
404-
// Combine the key and the value in a two-element list
405-
CelExpr step = exprFactory.newList(sortKeyExpr, varIdent);
406-
// Wrap the pair in another list in order to be able to use the `list+list` operator
407-
step = exprFactory.newList(step);
408-
// Append the key-value pair to the i
409-
step =
467+
String sortByInputVar = "@__sortBy_input__";
468+
CelExpr sortByInputIdent = exprFactory.newIdentifier(sortByInputVar);
469+
470+
// Map comprehension: target.map(varName, sortKeyExpr)
471+
CelExpr mapStep =
410472
exprFactory.newGlobalCall(
411473
Operator.ADD.getFunction(),
412474
exprFactory.newIdentifier(exprFactory.getAccumulatorVarName()),
413-
step);
414-
// Create an intermediate list and populate it with key-value pairs
415-
step =
475+
exprFactory.newList(sortKeyExpr));
476+
CelExpr mapCompr =
416477
exprFactory.fold(
417478
varName,
418-
target,
479+
sortByInputIdent,
419480
exprFactory.getAccumulatorVarName(),
420481
exprFactory.newList(),
421-
exprFactory.newBoolLiteral(true), // Include all elements
422-
step,
482+
exprFactory.newBoolLiteral(true),
483+
mapStep,
423484
exprFactory.newIdentifier(exprFactory.getAccumulatorVarName()));
424-
// Finally, sort the list of key-value pairs and map it to a list of values
425-
step = exprFactory.newGlobalCall(Function.SORT_BY.getFunction(), step);
426485

427-
return Optional.of(step);
486+
// Receiver call: sortByInputIdent.@sortByAssociatedKeys(mapCompr)
487+
CelExpr callExpr =
488+
exprFactory.newReceiverCall(
489+
Function.SORT_BY.getFunction(),
490+
sortByInputIdent,
491+
mapCompr);
492+
493+
// cel.bind(sortByInputVar, target, callExpr)
494+
CelExpr bindExpr =
495+
exprFactory.fold(
496+
"#unused",
497+
exprFactory.newList(),
498+
sortByInputVar,
499+
target,
500+
// Loop condition is false because this comprehension simulates a local variable assignment (`bind`), rather than a traditional iteration.
501+
exprFactory.newBoolLiteral(false),
502+
sortByInputIdent,
503+
callExpr);
504+
505+
return Optional.of(bindExpr);
428506
}
429507

430-
@SuppressWarnings({"unchecked", "rawtypes"})
431508
private static ImmutableList<Object> sortByAssociatedKeys(
432-
Collection<List<Object>> keyValuePairs) {
433-
List<Object>[] array = keyValuePairs.toArray(new List[0]);
434-
Arrays.sort(array, new CelObjectByKeyComparator(new CelObjectComparator()));
435-
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(array.length);
436-
for (List<Object> pair : array) {
437-
builder.add(pair.get(1));
509+
Collection<Object> list, Collection<Object> keys) {
510+
if (list.size() != keys.size()) {
511+
throw new IllegalArgumentException(
512+
String.format(
513+
"@sortByAssociatedKeys() expected a list of the same size as the associated keys"
514+
+ " list, but got %d and %d elements respectively.",
515+
list.size(), keys.size()));
438516
}
439-
return builder.build();
440-
}
441-
442-
private static class CelObjectByKeyComparator implements Comparator<Object> {
443-
private final CelObjectComparator keyComparator;
444-
445-
CelObjectByKeyComparator(CelObjectComparator keyComparator) {
446-
this.keyComparator = keyComparator;
517+
if (list.isEmpty()) {
518+
return ImmutableList.of();
447519
}
448-
449-
@SuppressWarnings({"unchecked"})
450-
@Override
451-
public int compare(Object o1, Object o2) {
452-
return keyComparator.compare(((List<Object>) o1).get(0), ((List<Object>) o2).get(0));
520+
for (Object key : keys) {
521+
if (!isSupportedComparableType(key)) {
522+
throw new IllegalArgumentException("List elements must be comparable");
523+
}
524+
}
525+
if (list.size() < 2) {
526+
return ImmutableList.copyOf(list);
453527
}
528+
Object[] listArray = list.toArray();
529+
Object[] keysArray = keys.toArray();
530+
Integer[] indices = new Integer[listArray.length];
531+
for (int i = 0; i < indices.length; i++) {
532+
indices[i] = i;
533+
}
534+
CelObjectComparator comparator = new CelObjectComparator();
535+
Arrays.sort(indices, (i1, i2) -> comparator.compare(keysArray[i1], keysArray[i2]));
536+
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(indices.length);
537+
for (int idx : indices) {
538+
builder.add(listArray[idx]);
539+
}
540+
return builder.build();
454541
}
455542
}

extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ public void getAllFunctionNames() {
185185
"distinct",
186186
"reverse",
187187
"sort",
188-
"lists.@sortByAssociatedKeys",
188+
"@sortByAssociatedKeys",
189189
"regex.replace",
190190
"regex.extract",
191191
"regex.extractAll",

0 commit comments

Comments
 (0)