Skip to content

Commit a1fb2de

Browse files
committed
Defer keyword argument deduplication until entire group is evaluated
1 parent ff7e755 commit a1fb2de

File tree

2 files changed

+74
-3
lines changed
  • graalpython

2 files changed

+74
-3
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_call.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,15 @@ def test_doesnt_modify_passed_dict():
378378
assert d1 == {'a': 1}
379379
assert d2 == {'b': 2}
380380

381+
def test_keyword_validation_order_of_evaluation():
382+
# The division by zero is evaluated before the duplicate argument is detected.
383+
assert_call_raises(ZeroDivisionError, "f26(**{'a':1}, a=1/0)")
384+
assert_call_raises(ZeroDivisionError, "f26(**{'p1':0}, p1=1, p2=2, p3=3, p4=4, p5=5, p6=6, p7=7, p8=8, p9=9, p10=10, p11=11, p12=12, p13=13, p14=14, p15=15/0)")
385+
assert_call_raises(ZeroDivisionError, "f26(**{'p1':0}, p1=1, p2=2, p3=3, p4=4, p5=5, p6=6, p7=7, p8=8, p9=9, p10=10, p11=11, p12=12, p13=13, p14=14, p15=15, p16=16/0)")
386+
387+
# The duplicate argument is detected before the division by zero is evaluated.
388+
assert_call_raises(TypeError, "f26(**{'a':1}, **{'a':2}, b=1/0)")
389+
381390
fooo = f26
382391
def test_multiple_values_with_callable_name():
383392
def assert_get_message(err, fn, *args, **kwargs):

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/compiler/Compiler.java

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ public void flushStackIfNecessary() {
696696
}
697697
}
698698

699-
protected void doFlushStack() {
699+
private void doFlushStack() {
700700
assert stackItems <= CollectionBits.KIND_MASK;
701701
if (collectionOnStack) {
702702
addOp(COLLECTION_ADD_STACK, typeBits | stackItems);
@@ -729,6 +729,27 @@ public boolean isEmpty() {
729729
}
730730

731731
private class KwargsMergingDictCollector extends Collector {
732+
/*
733+
* Keyword arguments get evaluated in "groups", where a group is a contiguous sequence of
734+
* named keyword arguments or a keyword-splat. For example, in
735+
*
736+
* f(a=2, b=3, **kws, c=4, d=5, **more_kws)
737+
*
738+
* there are 4 groups. Each group is evaluated and only then merged with the existing
739+
* keyword arguments. The merge step is where duplicate keyword arguments are checked.
740+
*
741+
* A call can have an arbitrarily long sequence of named keyword arguments, so we flush them
742+
* from the stack and collect them into an intermediate dict. We cannot eagerly merge them
743+
* with the existing keyword arguments since that could trigger a duplication check before
744+
* all of the arguments in the group have been evaluated. For example, in
745+
*
746+
* f(**{'a': 2}, a=3, b=4, ..., z=function_with_side_effects())
747+
*
748+
* we cannot merge "a=3" with the existing keywords dict (and consequently raise a
749+
* TypeError) until we compute "z".
750+
*/
751+
private boolean namedKeywordDictOnStack = false;
752+
732753
public KwargsMergingDictCollector(OpCodes callOp) {
733754
super(CollectionBits.KIND_DICT);
734755
/*
@@ -740,25 +761,66 @@ public KwargsMergingDictCollector(OpCodes callOp) {
740761
}
741762

742763
@Override
743-
protected void doFlushStack() {
744-
addOp(COLLECTION_FROM_STACK, typeBits | stackItems);
764+
public void appendItem() {
765+
stackItems += stackItemsPerItem;
766+
if (stackItems + stackItemsPerItem > CollectionBits.KIND_MASK) {
767+
collectIntoNamedKeywordDict();
768+
}
769+
}
770+
771+
@Override
772+
public void flushStackIfNecessary() {
773+
if (stackItems == 0 && !namedKeywordDictOnStack) {
774+
// Nothing pending to be merged.
775+
return;
776+
}
777+
778+
if (stackItems > 0) {
779+
collectIntoNamedKeywordDict();
780+
}
745781
if (collectionOnStack) {
782+
// If there's already a collection on the stack, merge it with the dict we just
783+
// finished creating.
746784
addOp(KWARGS_DICT_MERGE);
747785
}
748786
collectionOnStack = true;
787+
namedKeywordDictOnStack = false;
788+
}
789+
790+
protected void collectIntoNamedKeywordDict() {
791+
if (namedKeywordDictOnStack) {
792+
// Just add the keywords to the existing dict. Since we statically check for
793+
// duplicate named keywords, we don't need to worry about duplicates in this dict.
794+
addOp(COLLECTION_ADD_STACK, typeBits | stackItems);
795+
} else {
796+
// Create a new dict.
797+
addOp(COLLECTION_FROM_STACK, typeBits | stackItems);
798+
namedKeywordDictOnStack = true;
799+
}
749800
stackItems = 0;
750801
}
751802

752803
@Override
753804
public void appendCollection() {
754805
assert stackItems == 0;
806+
assert !namedKeywordDictOnStack;
755807
if (collectionOnStack) {
756808
addOp(KWARGS_DICT_MERGE);
757809
} else {
758810
addOp(COLLECTION_FROM_COLLECTION, typeBits);
759811
}
760812
collectionOnStack = true;
761813
}
814+
815+
@Override
816+
public void finishCollection() {
817+
flushStackIfNecessary();
818+
}
819+
820+
@Override
821+
public boolean isEmpty() {
822+
return stackItems == 0 || !namedKeywordDictOnStack || !collectionOnStack;
823+
}
762824
}
763825

764826
private void collectIntoArray(ExprTy[] nodes, int bits, int alreadyOnStack) {

0 commit comments

Comments
 (0)