Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
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;
import static org.projectnessie.cel.common.types.Err.noSuchAttributeException;
import static org.projectnessie.cel.common.types.Err.noSuchOverload;
import static org.projectnessie.cel.common.types.Err.valOrErr;
import static org.projectnessie.cel.common.types.UnknownT.isUnknown;
import static org.projectnessie.cel.common.types.UnknownT.unknownOf;
import static org.projectnessie.cel.common.types.Util.isUnknownOrError;
import static org.projectnessie.cel.interpreter.Activation.emptyActivation;
import static org.projectnessie.cel.interpreter.Coster.Cost.OneOne;
Expand Down Expand Up @@ -54,6 +56,7 @@
import org.projectnessie.cel.common.types.traits.Receiver;
import org.projectnessie.cel.common.types.traits.Sizer;
import org.projectnessie.cel.common.types.traits.Trait;
import org.projectnessie.cel.interpreter.Activation.PartialActivation;
import org.projectnessie.cel.interpreter.Activation.VarActivation;
import org.projectnessie.cel.interpreter.AttributeFactory.Attribute;
import org.projectnessie.cel.interpreter.AttributeFactory.ConditionalAttribute;
Expand Down Expand Up @@ -143,6 +146,48 @@ interface InterpretableCall extends Interpretable {

// Core Interpretable implementations used during the program planning phase.

/** evalIdent evaluates a checked top-level variable directly from the activation. */
final class EvalIdent extends AbstractEval implements Coster {
private final String name;
private final TypeAdapter adapter;

EvalIdent(long id, String name, TypeAdapter adapter) {
super(id);
this.name = Objects.requireNonNull(name);
this.adapter = Objects.requireNonNull(adapter);
}

/** Eval implements the Interpretable interface method. */
@Override
public Val eval(Activation ctx) {
if (ctx instanceof PartialActivation) {
for (AttributePattern pattern : ((PartialActivation) ctx).unknownAttributePatterns()) {
if (pattern.variableMatches(name)) {
return unknownOf(id);
}
}
}

ResolvedValue value = ctx.resolveName(name);
if (!value.present()) {
RuntimeException err = noSuchAttributeException("id: " + id + ", names: [" + name + "]");
return newErr(err, err.toString());
}
return adapter.nativeToValue(value.value());
}

/** Cost implements the Coster interface method. */
@Override
public Cost cost() {
return OneOne;
}

@Override
public String toString() {
return "EvalIdent{" + "id=" + id + ", name='" + name + '\'' + '}';
}
}

final class EvalTestOnly implements Interpretable, Coster {
private final long id;
private final Interpretable op;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.projectnessie.cel.interpreter.Interpretable.EvalBinary;
import org.projectnessie.cel.interpreter.Interpretable.EvalEq;
import org.projectnessie.cel.interpreter.Interpretable.EvalFold;
import org.projectnessie.cel.interpreter.Interpretable.EvalIdent;
import org.projectnessie.cel.interpreter.Interpretable.EvalList;
import org.projectnessie.cel.interpreter.Interpretable.EvalListFold;
import org.projectnessie.cel.interpreter.Interpretable.EvalMap;
Expand Down Expand Up @@ -238,7 +239,11 @@ Interpretable planCheckedIdent(long id, Reference identRef) {
return newConstValue(id, cVal);
}

// Otherwise, return the attribute for the resolved identifier name.
// Otherwise, evaluate the checked top-level variable directly for ordinary plans. Decorated
// programs keep the attribute shape because custom decorators may inspect attributes.
if (decorators.length == 0) {
return new EvalIdent(id, identRef.getName(), adapter);
}
return new EvalAttr(adapter, attrFactory.absoluteAttribute(id, identRef.getName()));
}

Expand All @@ -261,7 +266,8 @@ Interpretable planSelect(Expr expr) {

Select sel = expr.getSelectExpr();
// Plan the operand evaluation.
Interpretable op = plan(sel.getOperand());
Interpretable op =
decorators.length == 0 ? planSelectOperand(sel.getOperand()) : plan(sel.getOperand());

// Determine the field type if this is a proto message type.
FieldType fieldType = null;
Expand Down Expand Up @@ -314,6 +320,25 @@ Interpretable planSelect(Expr expr) {
return relAttr;
}

private Interpretable planSelectOperand(Expr operand) {
if (operand.getExprKindCase() != Expr.ExprKindCase.IDENT_EXPR) {
return plan(operand);
}

Reference identRef = refMap.get(operand.getId());
if (identRef == null || identRef.getValue() != Reference.getDefaultInstance().getValue()) {
return plan(operand);
}

Type cType = typeMap.get(operand.getId());
if (cType != null && cType.getType() != Type.getDefaultInstance()) {
return plan(operand);
}

return new EvalAttr(
adapter, attrFactory.absoluteAttribute(operand.getId(), identRef.getName()));
}

private FieldType findFieldType(String messageType, String fieldName) {
String key = messageType + '\n' + fieldName;
FieldType ft = fieldTypes.get(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,54 @@ void missingIdentInSelect() {
assertThat(result).isInstanceOf(Err.class);
}

@Test
void checkedTopLevelIdentPreservesActivationSemantics() {
Source src = newTextSource("x");
ParseResult parsed = Parser.parseAllMacros(src);
assertThat(parsed.hasErrors()).withFailMessage(parsed.getErrors()::toDisplayString).isFalse();

Container cont = testContainer("test");
TypeRegistry reg = newRegistry();
CheckerEnv env = newStandardCheckerEnv(cont, reg);
env.add(Decls.newVar("x", Decls.Bool));
CheckResult checkResult = Checker.Check(parsed, src, env);

AttributeFactory attrs = newPartialAttributeFactory(cont, reg, reg);
Interpreter interp = newStandardInterpreter(cont, reg, reg, attrs);
Interpretable i = interp.newInterpretable(checkResult.getCheckedExpr());

assertThat(i.eval(newActivation(mapOf("x", true)))).isSameAs(True);
assertThat(i.eval(emptyActivation())).isInstanceOf(Err.class);
assertThat(i.eval(newPartialActivation(mapOf("x", true), newAttributePattern("x"))))
.isInstanceOf(UnknownT.class);
}

@Test
void checkedTopLevelIdentSelectPreservesTrackedState() {
Source src = newTextSource("a.b");
ParseResult parsed = Parser.parseAllMacros(src);
assertThat(parsed.hasErrors()).withFailMessage(parsed.getErrors()::toDisplayString).isFalse();

Container cont = testContainer("test");
TypeRegistry reg = newRegistry();
CheckerEnv env = newStandardCheckerEnv(cont, reg);
env.add(Decls.newVar("a", Decls.newMapType(Decls.String, Decls.Bool)));
CheckResult checkResult = Checker.Check(parsed, src, env);

Expr select = checkResult.getCheckedExpr().getExpr();
long selectId = select.getId();
long operandId = select.getSelectExpr().getOperand().getId();
EvalState state = newEvalState();
AttributeFactory attrs = newAttributeFactory(cont, reg, reg);
Interpreter interp = newStandardInterpreter(cont, reg, reg, attrs);
Interpretable i = interp.newInterpretable(checkResult.getCheckedExpr(), trackState(state));

assertThat(i.eval(newActivation(mapOf("a", mapOf("b", true))))).isSameAs(True);
assertThat(state.ids()).containsExactlyInAnyOrder(operandId, selectId);
assertThat(state.value(operandId)).isSameAs(True);
assertThat(state.value(selectId)).isSameAs(True);
}

@Test
void equalityWithUnknownOperandStaysUnknown() {
assertThat(evalWithUnknownStringX("x == 'foo'")).isInstanceOf(UnknownT.class);
Expand Down
Loading