Skip to content

Commit 04118be

Browse files
committed
Avoid executing arg nodes multiple times with op element assignment.
1 parent 74c1eab commit 04118be

File tree

3 files changed

+67
-14
lines changed

3 files changed

+67
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Bug fixes:
4141
* Fix `/#{...}/o` to evaluate only once per context when splitting happens (@eregon).
4242
* Fix `Kernel#sprintf` formatting of floats to be like CRuby (@aardvark179).
4343
* Fix `Process.egid=` to accept `String`s (#2615, @ngtban)
44+
* Fix optional assignment to only evaluate index arguments once (#2658, @aardvark179).
4445

4546
Compatibility:
4647

src/main/java/org/truffleruby/parser/BodyTranslator.java

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.ArrayDeque;
1515
import java.util.ArrayList;
1616
import java.util.Arrays;
17+
import java.util.Collections;
1718
import java.util.Deque;
1819
import java.util.Iterator;
1920
import java.util.List;
@@ -2469,58 +2470,87 @@ public RubyNode visitOpElementAsgnNode(OpElementAsgnParseNode node) {
24692470
tempName,
24702471
0,
24712472
node.getReceiverNode());
2473+
final ArrayList<ValueFromNode> argValues = argsToTemp(node);
24722474

24732475
final String op = node.getOperatorName();
24742476
final boolean logicalOperation = op.equals("&&") || op.equals("||");
24752477

24762478
if (logicalOperation) {
2477-
final ParseNode write = write(node, readReceiverFromTemp, value);
2478-
final ParseNode operation = operation(node, readReceiverFromTemp, op, write);
2479+
final ParseNode write = write(node, readReceiverFromTemp, argValues, value);
2480+
final ParseNode operation = operation(node, readReceiverFromTemp, argValues, op, write);
24792481

2480-
return block(node, writeReceiverToTemp, operation);
2482+
return block(node, writeReceiverToTemp, argValues, operation);
24812483
} else {
2482-
final ParseNode operation = operation(node, readReceiverFromTemp, op, value);
2483-
final ParseNode write = write(node, readReceiverFromTemp, operation);
2484+
final ParseNode operation = operation(node, readReceiverFromTemp, argValues, op, value);
2485+
final ParseNode write = write(node, readReceiverFromTemp, argValues, operation);
24842486

2485-
return block(node, writeReceiverToTemp, write);
2487+
return block(node, writeReceiverToTemp, argValues, write);
24862488
}
24872489
}
24882490

2489-
private RubyNode block(OpElementAsgnParseNode node, ParseNode writeReceiverToTemp, ParseNode main) {
2491+
private RubyNode block(OpElementAsgnParseNode node, ParseNode writeReceiverToTemp,
2492+
ArrayList<ValueFromNode> argValues, ParseNode main) {
24902493
final BlockParseNode block = new BlockParseNode(node.getPosition());
24912494
block.add(writeReceiverToTemp);
24922495
block.add(main);
24932496

2494-
final RubyNode ret = block.accept(this);
2497+
RubyNode ret = block.accept(this);
2498+
Collections.reverse(argValues);
2499+
for (var arg : argValues) {
2500+
ret = arg.prepareAndThen(node.getPosition(), ret);
2501+
}
24952502
return addNewlineIfNeeded(node, ret);
24962503
}
24972504

2498-
private ParseNode write(OpElementAsgnParseNode node, ParseNode readReceiverFromTemp, ParseNode value) {
2499-
final ParseNode readArguments = node.getArgsNode();
2505+
private ParseNode write(OpElementAsgnParseNode node, ParseNode readReceiverFromTemp,
2506+
ArrayList<ValueFromNode> argValues, ParseNode value) {
25002507
final ParseNode writeArguments;
25012508
// Like ParserSupport#arg_add, but copy the first node
2502-
if (readArguments instanceof ArrayParseNode) {
2509+
if (node.getArgsNode() instanceof ArrayParseNode) {
25032510
final ArrayParseNode readArgsCopy = new ArrayParseNode(node.getPosition());
2504-
readArgsCopy.addAll((ArrayParseNode) readArguments).add(value);
2511+
for (var arg : argValues) {
2512+
readArgsCopy.add(arg.get(node.getPosition()));
2513+
}
2514+
readArgsCopy.add(value);
25052515
writeArguments = readArgsCopy;
25062516
} else {
2507-
writeArguments = new ArgsPushParseNode(node.getPosition(), readArguments, value);
2517+
writeArguments = new ArgsPushParseNode(node.getPosition(), argValues.get(0).get(node.getPosition()), value);
25082518
}
25092519

25102520
return new AttrAssignParseNode(node.getPosition(), readReceiverFromTemp, "[]=", writeArguments, false);
25112521
}
25122522

2523+
private ArrayList<ValueFromNode> argsToTemp(OpElementAsgnParseNode node) {
2524+
ArrayList<ValueFromNode> argValues = new ArrayList<>();
2525+
2526+
final ParseNode readArguments = node.getArgsNode();
2527+
if (readArguments instanceof ArrayParseNode) {
2528+
for (ParseNode child : ((ArrayParseNode) readArguments).children()) {
2529+
argValues.add(ValueFromNode.valueFromNode(this, child));
2530+
}
2531+
} else {
2532+
argValues.add(ValueFromNode.valueFromNode(this, readArguments));
2533+
}
2534+
2535+
return argValues;
2536+
}
2537+
25132538
private ParseNode operation(
25142539
OpElementAsgnParseNode node,
25152540
ParseNode readReceiverFromTemp,
2541+
ArrayList<ValueFromNode> argValues,
25162542
String op,
25172543
ParseNode right) {
2544+
final ArrayParseNode readArguments = new ArrayParseNode(node.getPosition());
2545+
for (var arg : argValues) {
2546+
readArguments.add(arg.get(node.getPosition()));
2547+
}
25182548

25192549
final ParseNode read = new CallParseNode(
25202550
node.getPosition(),
25212551
readReceiverFromTemp,
25222552
"[]",
2523-
node.getArgsNode(),
2553+
readArguments,
25242554
null);
25252555
ParseNode operation;
25262556
switch (op) {

src/main/java/org/truffleruby/parser/ValueFromNode.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.truffleruby.parser.ast.LocalVarParseNode;
1616
import org.truffleruby.parser.ast.ParseNode;
1717
import org.truffleruby.parser.ast.SelfParseNode;
18+
import org.truffleruby.parser.ast.SplatParseNode;
1819

1920
import java.util.Arrays;
2021

@@ -80,9 +81,30 @@ public ParseNode get(SourceIndexLength sourceSection) {
8081

8182
}
8283

84+
class ValueFromSplatNode implements ValueFromNode {
85+
86+
private final ValueFromNode value;
87+
88+
public ValueFromSplatNode(BodyTranslator translator, SplatParseNode node) {
89+
value = valueFromNode(translator, node.getValue());
90+
}
91+
92+
@Override
93+
public RubyNode prepareAndThen(SourceIndexLength sourceSection, RubyNode subsequent) {
94+
return value.prepareAndThen(sourceSection, subsequent);
95+
}
96+
97+
@Override
98+
public ParseNode get(SourceIndexLength sourceSection) {
99+
return new SplatParseNode(sourceSection, value.get(sourceSection));
100+
}
101+
}
102+
83103
static ValueFromNode valueFromNode(BodyTranslator translator, ParseNode node) {
84104
if (node instanceof SelfParseNode) {
85105
return new ValueFromSelfNode();
106+
} else if (node instanceof SplatParseNode) {
107+
return new ValueFromSplatNode(translator, (SplatParseNode) node);
86108
} else {
87109
return new ValueFromEffectNode(translator, node);
88110
}

0 commit comments

Comments
 (0)