bazel syntax: eliminate LValue
This reduces the number of nodes allocated by the parser.
The logic of LValue is now moved into Eval, the tree-based evaluator.
A follow-up change will move all the Evaluator.eval methods into this class too,
so that the tree-based evaluator is entirely confined to this class.
ASTNode methods that used to return an LValue are now named getLHS.
PiperOrigin-RevId: 255950649
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
index 7a4273b..036210d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
@@ -15,7 +15,9 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.events.Location;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.function.Function;
@@ -67,14 +69,13 @@
}
void execAssignment(AssignmentStatement node) throws EvalException, InterruptedException {
- Object rvalue = node.getExpression().eval(env);
- node.getLValue().assign(rvalue, env, node.getLocation());
+ Object rvalue = node.getRHS().eval(env);
+ assign(node.getLHS(), rvalue, env, node.getLocation());
}
void execAugmentedAssignment(AugmentedAssignmentStatement node)
throws EvalException, InterruptedException {
- node.getLValue()
- .assignAugmented(node.getOperator(), node.getExpression(), env, node.getLocation());
+ assignAugmented(node.getLHS(), node.getOperator(), node.getRHS(), env, node.getLocation());
}
void execIfBranch(IfStatement.ConditionalStatements node)
@@ -88,7 +89,7 @@
EvalUtils.lock(o, node.getLocation());
try {
for (Object it : col) {
- node.getVariable().assign(it, env, node.getLocation());
+ assign(node.getLHS(), it, env, node.getLocation());
try {
execStatements(node.getBlock());
@@ -230,4 +231,123 @@
exec(statements.get(i));
}
}
+
+ /**
+ * Updates the environment bindings, and possibly mutates objects, so as to assign the given value
+ * to the given expression. The expression must be valid for an {@code LValue}.
+ */
+ // TODO(adonovan): make this a private instance method once all Expression.eval methods move here.
+ static void assign(Expression expr, Object value, Environment env, Location loc)
+ throws EvalException, InterruptedException {
+ if (expr instanceof Identifier) {
+ assignIdentifier((Identifier) expr, value, env);
+ } else if (expr instanceof IndexExpression) {
+ Object object = ((IndexExpression) expr).getObject().eval(env);
+ Object key = ((IndexExpression) expr).getKey().eval(env);
+ assignItem(object, key, value, env, loc);
+ } else if (expr instanceof ListLiteral) {
+ ListLiteral list = (ListLiteral) expr;
+ assignList(list, value, env, loc);
+ } else {
+ // Not possible for validated ASTs.
+ throw new EvalException(loc, "cannot assign to '" + expr + "'");
+ }
+ }
+
+ /** Binds a variable to the given value in the environment. */
+ private static void assignIdentifier(Identifier ident, Object value, Environment env)
+ throws EvalException {
+ env.updateAndExport(ident.getName(), value);
+ }
+
+ /**
+ * Adds or changes an object-key-value relationship for a list or dict.
+ *
+ * <p>For a list, the key is an in-range index. For a dict, it is a hashable value.
+ *
+ * @throws EvalException if the object is not a list or dict
+ */
+ @SuppressWarnings("unchecked")
+ private static void assignItem(
+ Object object, Object key, Object value, Environment env, Location loc) throws EvalException {
+ if (object instanceof SkylarkDict) {
+ SkylarkDict<Object, Object> dict = (SkylarkDict<Object, Object>) object;
+ dict.put(key, value, loc, env);
+ } else if (object instanceof SkylarkList.MutableList) {
+ SkylarkList.MutableList<Object> list = (SkylarkList.MutableList<Object>) object;
+ int index = EvalUtils.getSequenceIndex(key, list.size(), loc);
+ list.set(index, value, loc, env.mutability());
+ } else {
+ throw new EvalException(
+ loc,
+ "can only assign an element in a dictionary or a list, not in a '"
+ + EvalUtils.getDataTypeName(object)
+ + "'");
+ }
+ }
+
+ /**
+ * Recursively assigns an iterable value to a list literal.
+ *
+ * @throws EvalException if the list literal has length 0, or if the value is not an iterable of
+ * matching length
+ */
+ private static void assignList(ListLiteral list, Object value, Environment env, Location loc)
+ throws EvalException, InterruptedException {
+ Collection<?> collection = EvalUtils.toCollection(value, loc, env);
+ int len = list.getElements().size();
+ if (len == 0) {
+ throw new EvalException(
+ loc, "lists or tuples on the left-hand side of assignments must have at least one item");
+ }
+ if (len != collection.size()) {
+ throw new EvalException(
+ loc,
+ String.format(
+ "assignment length mismatch: left-hand side has length %d, but right-hand side"
+ + " evaluates to value of length %d",
+ len, collection.size()));
+ }
+ int i = 0;
+ for (Object item : collection) {
+ assign(list.getElements().get(i), item, env, loc);
+ i++;
+ }
+ }
+
+ /**
+ * Evaluates an augmented assignment that mutates this {@code LValue} with the given right-hand
+ * side's value.
+ *
+ * <p>The left-hand side expression is evaluated only once, even when it is an {@link
+ * IndexExpression}. The left-hand side is evaluated before the right-hand side to match Python's
+ * behavior (hence why the right-hand side is passed as an expression rather than as an evaluated
+ * value).
+ */
+ private static void assignAugmented(
+ Expression expr, Operator operator, Expression rhs, Environment env, Location loc)
+ throws EvalException, InterruptedException {
+ if (expr instanceof Identifier) {
+ Object result =
+ BinaryOperatorExpression.evaluateAugmented(
+ operator, expr.eval(env), rhs.eval(env), env, loc);
+ assignIdentifier((Identifier) expr, result, env);
+ } else if (expr instanceof IndexExpression) {
+ IndexExpression indexExpression = (IndexExpression) expr;
+ // The object and key should be evaluated only once, so we don't use expr.eval().
+ Object object = indexExpression.getObject().eval(env);
+ Object key = indexExpression.getKey().eval(env);
+ Object oldValue = IndexExpression.evaluate(object, key, env, loc);
+ // Evaluate rhs after lhs.
+ Object rhsValue = rhs.eval(env);
+ Object result =
+ BinaryOperatorExpression.evaluateAugmented(operator, oldValue, rhsValue, env, loc);
+ assignItem(object, key, result, env, loc);
+ } else if (expr instanceof ListLiteral) {
+ throw new EvalException(loc, "cannot perform augmented assignment on a list literal");
+ } else {
+ // Not possible for validated ASTs.
+ throw new EvalException(loc, "cannot perform augmented assignment on '" + expr + "'");
+ }
+ }
}