Skip to content

Commit a1f6103

Browse files
committed
Implement _PyDict_HasOnlyStringKeys
1 parent 65d9cf2 commit a1f6103

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_modsupport.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -81,6 +81,15 @@ def _reference_parse_tuple(args):
8181
raise TypeError
8282

8383

84+
def _reference_validate_keywords(args):
85+
kwargs = args[0]
86+
if not isinstance(kwargs, dict):
87+
raise SystemError
88+
if not all(isinstance(k, str) for k in kwargs):
89+
raise TypeError
90+
return 1
91+
92+
8493
class Indexable:
8594
def __int__(self):
8695
return 456
@@ -717,3 +726,16 @@ def compare_new_object(x, y):
717726
arguments=["PyObject* a"],
718727
cmpfunc=compare_new_object
719728
)
729+
730+
test_PyArg_ValidateKeywordArguments = CPyExtFunction(
731+
_reference_validate_keywords,
732+
lambda: (
733+
({'a': 1, 'b': 2},),
734+
({'a': 1, 1: 2},),
735+
("not-dict",),
736+
),
737+
resultspec='i',
738+
argspec='O',
739+
arguments=["PyObject* kwds"],
740+
cmpfunc=unhandled_error_compare,
741+
)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextDictBuiltins.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext;
7777
import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions;
7878
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess;
79+
import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage;
7980
import com.oracle.graal.python.builtins.objects.common.EconomicMapStorage;
8081
import com.oracle.graal.python.builtins.objects.common.HashingCollectionNodes.SetItemNode;
8182
import com.oracle.graal.python.builtins.objects.common.HashingStorage;
@@ -93,6 +94,7 @@
9394
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageLen;
9495
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageSetItem;
9596
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageSetItemWithHash;
97+
import com.oracle.graal.python.builtins.objects.common.KeywordsStorage;
9698
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemNode;
9799
import com.oracle.graal.python.builtins.objects.dict.DictBuiltins.ClearNode;
98100
import com.oracle.graal.python.builtins.objects.dict.DictBuiltins.PopNode;
@@ -105,6 +107,7 @@
105107
import com.oracle.graal.python.lib.PyDictSetDefault;
106108
import com.oracle.graal.python.lib.PyObjectGetAttr;
107109
import com.oracle.graal.python.lib.PyObjectHashNode;
110+
import com.oracle.graal.python.lib.PyUnicodeCheckNode;
108111
import com.oracle.graal.python.nodes.PRaiseNode;
109112
import com.oracle.graal.python.nodes.builtins.ListNodes.ConstructListNode;
110113
import com.oracle.graal.python.nodes.call.CallNode;
@@ -127,6 +130,7 @@
127130
import com.oracle.truffle.api.library.CachedLibrary;
128131
import com.oracle.truffle.api.nodes.Node;
129132
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
133+
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
130134
import com.oracle.truffle.api.profiles.InlinedLoopConditionProfile;
131135

132136
public final class PythonCextDictBuiltins {
@@ -654,4 +658,32 @@ static boolean isTracked(Object object, CStructAccess.ReadI64Node readI64Node) {
654658
// return false;
655659
}
656660
}
661+
662+
@CApiBuiltin(ret = Int, args = {PyObject}, call = Direct)
663+
abstract static class _PyDict_HasOnlyStringKeys extends CApiUnaryBuiltinNode {
664+
665+
@Specialization
666+
static int check(PDict dict,
667+
@Bind Node inliningTarget,
668+
@Cached InlinedConditionProfile storageProfile,
669+
@Cached InlinedLoopConditionProfile loopConditionProfile,
670+
@Cached HashingStorageGetIterator getIter,
671+
@Cached HashingStorageIteratorNext getIterNext,
672+
@Cached HashingStorageIteratorKey getIterKey,
673+
@Cached PyUnicodeCheckNode check) {
674+
HashingStorage storage = dict.getDictStorage();
675+
// Keywords and dynamic object storages only allow strings
676+
if (storageProfile.profile(inliningTarget, storage instanceof KeywordsStorage || storage instanceof DynamicObjectStorage)) {
677+
return 1;
678+
}
679+
HashingStorageIterator it = getIter.execute(inliningTarget, storage);
680+
while (loopConditionProfile.profile(inliningTarget, getIterNext.execute(inliningTarget, storage, it))) {
681+
Object key = getIterKey.execute(inliningTarget, storage, it);
682+
if (!check.execute(inliningTarget, key)) {
683+
return 0;
684+
}
685+
}
686+
return 1;
687+
}
688+
}
657689
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiFunction.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,6 @@ public final class CApiFunction {
10441044
@CApiBuiltin(name = "_PyDict_DelItemIf", ret = Int, args = {PyObject, PyObject, func_objint}, call = NotImplemented)
10451045
@CApiBuiltin(name = "_PyDict_DelItem_KnownHash", ret = Int, args = {PyObject, PyObject, Py_hash_t}, call = NotImplemented)
10461046
@CApiBuiltin(name = "_PyDict_GetItemWithError", ret = PyObject, args = {PyObject, PyObject}, call = NotImplemented)
1047-
@CApiBuiltin(name = "_PyDict_HasOnlyStringKeys", ret = Int, args = {PyObject}, call = NotImplemented)
10481047
@CApiBuiltin(name = "_PyDict_MergeEx", ret = Int, args = {PyObject, PyObject, Int}, call = NotImplemented)
10491048
@CApiBuiltin(name = "_PyDict_SizeOf", ret = Py_ssize_t, args = {PYDICTOBJECT_PTR}, call = NotImplemented)
10501049
@CApiBuiltin(name = "_PyErr_CheckSignals", ret = Int, args = {}, call = NotImplemented)

0 commit comments

Comments
 (0)