2828import dev .cel .common .ast .CelExpr .CelCall ;
2929import dev .cel .common .ast .CelExpr .CelList ;
3030import dev .cel .common .ast .CelExpr .CelMap ;
31+ import dev .cel .common .ast .CelExpr .CelSelect ;
3132import dev .cel .common .ast .CelExpr .CelStruct ;
3233import dev .cel .common .ast .CelExpr .CelStruct .Entry ;
3334import dev .cel .common .ast .CelReference ;
@@ -58,6 +59,7 @@ public final class ProgramPlanner {
5859 private final CelValueProvider valueProvider ;
5960 private final DefaultDispatcher dispatcher ;
6061 private final AttributeFactory attributeFactory ;
62+ private final CelContainer container ;
6163
6264 /**
6365 * Plans a {@link Program} from the provided parsed-only or type-checked {@link
@@ -168,7 +170,6 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
168170 evaluatedArgs [argIndex + offset ] = plan (args .get (argIndex ), ctx );
169171 }
170172
171- // TODO: Handle all specialized calls (logical operators, conditionals, equals etc)
172173 String functionName = resolvedFunction .functionName ();
173174 Operator operator = Operator .findReverse (functionName ).orElse (null );
174175 if (operator != null ) {
@@ -209,16 +210,7 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
209210
210211 private Interpretable planCreateStruct (CelExpr celExpr , PlannerContext ctx ) {
211212 CelStruct struct = celExpr .struct ();
212- CelType structType =
213- typeProvider
214- .findType (struct .messageName ())
215- .orElseThrow (
216- () -> new IllegalArgumentException ("Undefined type name: " + struct .messageName ()));
217- if (!structType .kind ().equals (CelKind .STRUCT )) {
218- throw new IllegalArgumentException (
219- String .format (
220- "Expected struct type for %s, got %s" , structType .name (), structType .kind ()));
221- }
213+ StructType structType = resolveStructType (struct );
222214
223215 ImmutableList <Entry > entries = struct .entries ();
224216 String [] keys = new String [entries .size ()];
@@ -230,7 +222,7 @@ private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
230222 values [i ] = plan (entry .value (), ctx );
231223 }
232224
233- return EvalCreateStruct .create (valueProvider , ( StructType ) structType , keys , values );
225+ return EvalCreateStruct .create (valueProvider , structType , keys , values );
234226 }
235227
236228 private Interpretable planCreateList (CelExpr celExpr , PlannerContext ctx ) {
@@ -269,7 +261,7 @@ private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
269261 private ResolvedFunction resolveFunction (
270262 CelExpr expr , ImmutableMap <Long , CelReference > referenceMap ) {
271263 CelCall call = expr .call ();
272- Optional <CelExpr > target = call .target ();
264+ Optional <CelExpr > maybeTarget = call .target ();
273265 String functionName = call .function ();
274266
275267 CelReference reference = referenceMap .get (expr .id ());
@@ -281,22 +273,89 @@ private ResolvedFunction resolveFunction(
281273 .setFunctionName (functionName )
282274 .setOverloadId (reference .overloadIds ().get (0 ));
283275
284- target .ifPresent (builder ::setTarget );
276+ maybeTarget .ifPresent (builder ::setTarget );
285277
286278 return builder .build ();
287279 }
288280 }
289281
290- // Parsed-only.
291- // TODO: Handle containers.
292- if (!target .isPresent ()) {
282+ // Parsed-only function resolution.
283+ //
284+ // There are two distinct cases we must handle:
285+ //
286+ // 1. Non-qualified function calls. This will resolve into either:
287+ // - A simple global call foo()
288+ // - A fully qualified global call through normal container resolution foo.bar.qux()
289+ // 2. Qualified function calls:
290+ // - A member call on an identifier foo.bar()
291+ // - A fully qualified global call, through normal container resolution or abbreviations
292+ // foo.bar.qux()
293+ if (!maybeTarget .isPresent ()) {
294+ for (String cand : container .resolveCandidateNames (functionName )) {
295+ CelResolvedOverload overload = dispatcher .findOverload (cand ).orElse (null );
296+ if (overload != null ) {
297+ return ResolvedFunction .newBuilder ().setFunctionName (cand ).build ();
298+ }
299+ }
300+
301+ // Normal global call
293302 return ResolvedFunction .newBuilder ().setFunctionName (functionName ).build ();
294- } else {
295- return ResolvedFunction .newBuilder ()
296- .setFunctionName (functionName )
297- .setTarget (target .get ())
298- .build ();
299303 }
304+
305+ CelExpr target = maybeTarget .get ();
306+ String qualifiedPrefix = toQualifiedName (target ).orElse (null );
307+ if (qualifiedPrefix != null ) {
308+ String qualifiedName = qualifiedPrefix + "." + functionName ;
309+ for (String cand : container .resolveCandidateNames (qualifiedName )) {
310+ CelResolvedOverload overload = dispatcher .findOverload (cand ).orElse (null );
311+ if (overload != null ) {
312+ return ResolvedFunction .newBuilder ().setFunctionName (cand ).build ();
313+ }
314+ }
315+ }
316+
317+ // Normal member call
318+ return ResolvedFunction .newBuilder ().setFunctionName (functionName ).setTarget (target ).build ();
319+ }
320+
321+ private StructType resolveStructType (CelStruct struct ) {
322+ String messageName = struct .messageName ();
323+ for (String typeName : container .resolveCandidateNames (messageName )) {
324+ CelType structType = typeProvider .findType (typeName ).orElse (null );
325+ if (structType == null ) {
326+ continue ;
327+ }
328+
329+ if (!structType .kind ().equals (CelKind .STRUCT )) {
330+ throw new IllegalArgumentException (
331+ String .format (
332+ "Expected struct type for %s, got %s" , structType .name (), structType .kind ()));
333+ }
334+
335+ return (StructType ) structType ;
336+ }
337+
338+ throw new IllegalArgumentException ("Undefined type name: " + messageName );
339+ }
340+
341+ /** Converts a given expression into a qualified name, if possible. */
342+ private Optional <String > toQualifiedName (CelExpr operand ) {
343+ switch (operand .getKind ()) {
344+ case IDENT :
345+ return Optional .of (operand .ident ().name ());
346+ case SELECT :
347+ CelSelect select = operand .select ();
348+ String maybeQualified = toQualifiedName (select .operand ()).orElse (null );
349+ if (maybeQualified != null ) {
350+ return Optional .of (maybeQualified + "." + select .field ());
351+ }
352+
353+ break ;
354+ default :
355+ // fall-through
356+ }
357+
358+ return Optional .empty ();
300359 }
301360
302361 @ AutoValue
@@ -338,17 +397,22 @@ private static PlannerContext create(CelAbstractSyntaxTree ast) {
338397 }
339398
340399 public static ProgramPlanner newPlanner (
341- CelTypeProvider typeProvider , CelValueProvider valueProvider , DefaultDispatcher dispatcher ) {
342- return new ProgramPlanner (typeProvider , valueProvider , dispatcher );
400+ CelTypeProvider typeProvider ,
401+ CelValueProvider valueProvider ,
402+ DefaultDispatcher dispatcher ,
403+ CelContainer container ) {
404+ return new ProgramPlanner (typeProvider , valueProvider , dispatcher , container );
343405 }
344406
345407 private ProgramPlanner (
346- CelTypeProvider typeProvider , CelValueProvider valueProvider , DefaultDispatcher dispatcher ) {
408+ CelTypeProvider typeProvider ,
409+ CelValueProvider valueProvider ,
410+ DefaultDispatcher dispatcher ,
411+ CelContainer container ) {
347412 this .typeProvider = typeProvider ;
348413 this .valueProvider = valueProvider ;
349- // TODO: Container support
350414 this .dispatcher = dispatcher ;
351- this .attributeFactory =
352- AttributeFactory .newAttributeFactory (CelContainer . newBuilder (). build () , typeProvider );
415+ this .container = container ;
416+ this . attributeFactory = AttributeFactory .newAttributeFactory (container , typeProvider );
353417 }
354418}
0 commit comments