3030import com .google .protobuf .ExtensionRegistry ;
3131import com .google .protobuf .TypeRegistry ;
3232import dev .cel .checker .CelChecker ;
33+ import dev .cel .checker .CelCheckerBuilder ;
3334import dev .cel .common .CelContainer ;
35+ import dev .cel .common .CelIssue ;
3436import dev .cel .common .CelOptions ;
3537import dev .cel .common .CelValidationResult ;
38+ import dev .cel .common .CelVarDecl ;
39+ import dev .cel .common .ast .CelBlock ;
40+ import dev .cel .common .ast .CelConstant ;
41+ import dev .cel .common .ast .CelExpr ;
3642import dev .cel .common .types .CelProtoTypes ;
43+ import dev .cel .common .types .SimpleType ;
3744import dev .cel .compiler .CelCompilerFactory ;
3845import dev .cel .compiler .CelCompilerLibrary ;
3946import dev .cel .expr .conformance .test .SimpleTest ;
47+ import dev .cel .extensions .CelBindingsExtensions ;
4048import dev .cel .extensions .CelExtensions ;
4149import dev .cel .extensions .CelOptionalLibrary ;
50+ import dev .cel .parser .CelMacro ;
51+ import dev .cel .parser .CelMacroExpander ;
52+ import dev .cel .parser .CelMacroExprFactory ;
4253import dev .cel .parser .CelParser ;
54+ import dev .cel .parser .CelParserBuilder ;
4355import dev .cel .parser .CelParserFactory ;
4456import dev .cel .parser .CelStandardMacro ;
4557import dev .cel .runtime .CelEvaluationException ;
5062import dev .cel .runtime .CelRuntimeImpl ;
5163import dev .cel .runtime .CelRuntimeLibrary ;
5264import java .util .Map ;
65+ import java .util .Optional ;
5366import org .junit .runners .model .Statement ;
5467
5568// Qualifying proto2/proto3 TestAllTypes makes it less clear.
@@ -73,7 +86,8 @@ public final class ConformanceTest extends Statement {
7386 CelExtensions .protos (),
7487 CelExtensions .sets (OPTIONS ),
7588 CelExtensions .strings (),
76- CelOptionalLibrary .INSTANCE );
89+ CelOptionalLibrary .INSTANCE ,
90+ new ConformanceBlockLibrary ());
7791
7892 private static final ImmutableList <CelRuntimeLibrary > CANONICAL_RUNTIME_EXTENSIONS =
7993 ImmutableList .of (
@@ -206,11 +220,11 @@ public boolean shouldSkip() {
206220 @ Override
207221 public void evaluate () throws Throwable {
208222 CelValidationResult response = getParser (test ).parse (test .getExpr (), test .getName ());
209- assertThat (response .hasError ()).isFalse ();
223+ assertThat (response .getErrors ()).isEmpty ();
210224 if (!test .getDisableCheck ()) {
211225 response = getChecker (test ).check (response .getAst ());
212226 }
213- assertThat (response .hasError ()).isFalse ();
227+ assertThat (response .getErrors ()).isEmpty ();
214228 Type resultType = CelProtoTypes .celTypeToType (response .getAst ().getResultType ());
215229
216230 if (test .getCheckOnly ()) {
@@ -262,4 +276,103 @@ public void evaluate() throws Throwable {
262276 String .format ("Unexpected matcher kind: %s" , test .getResultMatcherCase ()));
263277 }
264278 }
279+
280+ /**
281+ * Conformance-only library providing macros for the {@code block_ext} test suite.
282+ *
283+ * <p>These macros ({@code cel.block}, {@code cel.index}, {@code cel.iterVar}, and {@code
284+ * cel.accuVar}) are strictly used for conformance testing to represent block expressions in text
285+ * form. In production, AST optimization passes (such as common subexpression elimination)
286+ * directly generate the {@code cel.@block} call and {@code @index} / {@code @it} / {@code @ac}
287+ * variable nodes without going through these macros.
288+ */
289+ private static final class ConformanceBlockLibrary implements CelCompilerLibrary {
290+ private static final int MAX_INDICES = 30 ;
291+
292+ @ Override
293+ public void setParserOptions (CelParserBuilder parserBuilder ) {
294+ parserBuilder .addMacros (
295+ CelMacro .newReceiverMacro ("block" , 2 , ConformanceBlockLibrary ::expandBlock ),
296+ CelMacro .newReceiverMacro ("index" , 1 , ConformanceBlockLibrary ::expandIndex ),
297+ CelMacro .newReceiverMacro ("iterVar" , 2 , expandCompreVar ("cel.iterVar" , "@it" )),
298+ CelMacro .newReceiverMacro ("accuVar" , 2 , expandCompreVar ("cel.accuVar" , "@ac" )));
299+ }
300+
301+ @ Override
302+ public void setCheckerOptions (CelCheckerBuilder checkerBuilder ) {
303+ checkerBuilder .addFunctionDeclarations (CelBindingsExtensions .CEL_BLOCK_FUNCTION_DECL );
304+ for (int i = 0 ; i < MAX_INDICES ; i ++) {
305+ checkerBuilder .addVarDeclarations (
306+ CelVarDecl .newVarDeclaration (CelBlock .INDEX_PREFIX + i , SimpleType .DYN ));
307+ }
308+ }
309+
310+ private static Optional <CelExpr > expandBlock (
311+ CelMacroExprFactory exprFactory , CelExpr target , ImmutableList <CelExpr > args ) {
312+ if (!isCelNamespace (target )) {
313+ return Optional .empty ();
314+ }
315+ CelExpr bindings = args .get (0 );
316+ if (!bindings .exprKind ().getKind ().equals (CelExpr .ExprKind .Kind .LIST )) {
317+ return Optional .of (
318+ exprFactory .reportError (
319+ CelIssue .formatError (
320+ exprFactory .getSourceLocation (bindings ),
321+ "cel.block requires the first arg to be a list literal" )));
322+ }
323+ return Optional .of (exprFactory .newGlobalCall (CelBlock .FUNCTION_NAME , args ));
324+ }
325+
326+ private static Optional <CelExpr > expandIndex (
327+ CelMacroExprFactory exprFactory , CelExpr target , ImmutableList <CelExpr > args ) {
328+ if (!isCelNamespace (target )) {
329+ return Optional .empty ();
330+ }
331+ CelExpr index = args .get (0 );
332+ if (!isNonNegativeInt (index )) {
333+ return Optional .of (
334+ exprFactory .reportError (
335+ CelIssue .formatError (
336+ exprFactory .getSourceLocation (index ),
337+ "cel.index requires a single non-negative int constant arg" )));
338+ }
339+ return Optional .of (
340+ exprFactory .newIdentifier (CelBlock .INDEX_PREFIX + index .constant ().int64Value ()));
341+ }
342+
343+ private static CelMacroExpander expandCompreVar (String macroName , String prefix ) {
344+ return (exprFactory , target , args ) -> {
345+ if (!isCelNamespace (target )) {
346+ return Optional .empty ();
347+ }
348+ for (CelExpr arg : args ) {
349+ if (!isNonNegativeInt (arg )) {
350+ return Optional .of (
351+ exprFactory .reportError (
352+ CelIssue .formatError (
353+ exprFactory .getSourceLocation (arg ),
354+ macroName + " requires two non-negative int constant args" )));
355+ }
356+ }
357+ return Optional .of (
358+ exprFactory .newIdentifier (
359+ String .format (
360+ "%s:%d:%d" ,
361+ prefix ,
362+ args .get (0 ).constant ().int64Value (),
363+ args .get (1 ).constant ().int64Value ())));
364+ };
365+ }
366+
367+ private static boolean isNonNegativeInt (CelExpr expr ) {
368+ return expr .exprKind ().getKind ().equals (CelExpr .ExprKind .Kind .CONSTANT )
369+ && expr .constant ().getKind ().equals (CelConstant .Kind .INT64_VALUE )
370+ && expr .constant ().int64Value () >= 0 ;
371+ }
372+
373+ private static boolean isCelNamespace (CelExpr target ) {
374+ return target .exprKind ().getKind ().equals (CelExpr .ExprKind .Kind .IDENT )
375+ && target .ident ().name ().equals ("cel" );
376+ }
377+ }
265378}
0 commit comments