diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_dict.py b/graalpython/com.oracle.graal.python.test/src/tests/test_dict.py index 4b6d7c51aa..284cfb7297 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_dict.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_dict.py @@ -205,6 +205,31 @@ def test_init(): d.__init__({'d':4}) assert d == {'a':1, 'b':2, 'c':3, 'd':4} + +def test_dict_update_does_not_call_overridden_setitem(): + class DictSubclass(dict): + def __setitem__(self, key, value): + raise AssertionError("dict update called overridden __setitem__") + + d = DictSubclass() + dict.__init__(d, [('key', 'value')]) + assert d == {'key': 'value'} + + class Mapping: + def keys(self): + return ['other'] + + def __getitem__(self, key): + assert key == 'other' + return 'value' + + dict.__init__(d, Mapping()) + assert d == {'key': 'value', 'other': 'value'} + + dict.update(d, [('third', 'value')]) + assert d == {'key': 'value', 'other': 'value', 'third': 'value'} + + def test_init1(): try: dict([("a", 1), ("b", 2)], [("c", 3), ("d", 4)]) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_long_paths.py b/graalpython/com.oracle.graal.python.test/src/tests/test_long_paths.py index 12050736e4..a13e9fc1f7 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_long_paths.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_long_paths.py @@ -45,11 +45,11 @@ import unittest -def expected_failure_on_old_windows(test): +def skip_on_old_windows(test): # Despite the documented Windows 10 1607 cutoff, SetCurrentDirectoryW still rejects long paths # on Windows Server 2016 (build 14393), even when RtlAreLongPathsEnabled() returns true. if os.name == 'nt' and int(platform.version().split('.')[2]) <= 14393: - return unittest.expectedFailure(test) + return unittest.skip("old windows")(test) return test @@ -110,7 +110,7 @@ def test_long_paths(self): if root is not None: shutil.rmtree(root) - @expected_failure_on_old_windows + @skip_on_old_windows def test_long_path_chdir(self): root = tempfile.mkdtemp() old_cwd = os.getcwd() diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ordereddict/OrderedDictBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ordereddict/OrderedDictBuiltins.java index ec3eb95c58..5dc13a4b3a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ordereddict/OrderedDictBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ordereddict/OrderedDictBuiltins.java @@ -43,6 +43,7 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.KeyError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.RuntimeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___DICT__; import static com.oracle.graal.python.nodes.SpecialMethodNames.J_ITEMS; import static com.oracle.graal.python.nodes.SpecialMethodNames.J_KEYS; @@ -75,16 +76,20 @@ import com.oracle.graal.python.builtins.objects.common.HashingStorage; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes; import com.oracle.graal.python.builtins.objects.common.ObjectHashMap; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.dict.DictReprBuiltin.ReprOrderedDictItemsNode; import com.oracle.graal.python.builtins.objects.dict.PDict; import com.oracle.graal.python.builtins.objects.function.PKeyword; +import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.ordereddict.POrderedDict.ODictNode; +import com.oracle.graal.python.builtins.objects.tuple.PTuple; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotBinaryOp.BinaryOpBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotMpAssSubscript.MpAssSubscriptBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; -import com.oracle.graal.python.lib.PyDictMerge; +import com.oracle.graal.python.lib.IteratorExhausted; +import com.oracle.graal.python.lib.PyIterNextNode; import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectDelItem; import com.oracle.graal.python.lib.PyObjectGetItem; @@ -98,6 +103,7 @@ import com.oracle.graal.python.lib.RichCmpOp; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PGuards; +import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.call.CallNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; @@ -113,6 +119,7 @@ import com.oracle.graal.python.nodes.object.SetDictNode; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; @@ -205,6 +212,114 @@ static void delitem(VirtualFrame frame, POrderedDict self, Object key, @Suppress } } + @GenerateInline + @GenerateCached(false) + abstract static class UpdateMappingNode extends PNodeWithContext { + abstract void execute(VirtualFrame frame, Node inliningTarget, Object target, Object mapping, Object keysMethod); + + @Specialization + static void doMerge(VirtualFrame frame, Node inliningTarget, Object target, Object mapping, Object keysMethod, + @Cached CallNode callKeys, + @Cached PyObjectGetIter getIter, + @Cached PyIterNextNode next, + @Cached PyObjectGetItem getItem, + @Cached PyObjectSetItem setItem) { + Object keys = callKeys.execute(frame, keysMethod); + Object iterator = getIter.execute(frame, inliningTarget, keys); + while (true) { + Object key; + try { + key = next.execute(frame, inliningTarget, iterator); + } catch (IteratorExhausted e) { + break; + } + Object value = getItem.execute(frame, inliningTarget, mapping, key); + setItem.execute(frame, inliningTarget, target, key, value); + } + } + } + + @GenerateInline + @GenerateCached(false) + abstract static class UpdateFromSequenceNode extends PNodeWithContext { + abstract void execute(VirtualFrame frame, Node inliningTarget, Object target, Object iterable); + + @Specialization + static void doIterable(VirtualFrame frame, Node inliningTarget, Object target, Object iterable, + @Cached PyObjectGetIter getIter, + @Cached PyIterNextNode next, + @Cached SetItemFromSequenceNode setItemFromSequence) { + Object iterator = getIter.execute(frame, inliningTarget, iterable); + while (true) { + Object element; + try { + element = next.execute(frame, inliningTarget, iterator); + } catch (IteratorExhausted e) { + break; + } + setItemFromSequence.execute(frame, inliningTarget, target, element); + } + } + } + + @GenerateInline + @GenerateCached(false) + abstract static class SetItemFromSequenceNode extends PNodeWithContext { + abstract void execute(VirtualFrame frame, Node inliningTarget, Object target, Object element); + + @Specialization + static void doGeneric(VirtualFrame frame, Node inliningTarget, Object target, Object element, + @Cached SequenceStorageNodes.GetItemScalarNode getItem, + @Cached PyObjectGetIter getIter, + @Cached PyIterNextNode next, + @Cached PyObjectSetItem setItem, + @Cached InlinedBranchProfile tupleProfile, + @Cached InlinedBranchProfile listProfile, + @Cached PRaiseNode raiseNode) { + Object key, value; + SequenceStorage storage = null; + if (element instanceof PTuple tuple && PGuards.isBuiltinTuple(tuple)) { + tupleProfile.enter(inliningTarget); + storage = tuple.getSequenceStorage(); + } else if (element instanceof PList list && PGuards.isBuiltinList(list)) { + listProfile.enter(inliningTarget); + storage = list.getSequenceStorage(); + } + if (storage != null) { + int length = storage.length(); + if (length == 0) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.NEED_MORE_THAN_D_VALUES_TO_UNPACK, 0); + } else if (length == 1) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.NEED_MORE_THAN_D_VALUES_TO_UNPACK, 1); + } else if (length > 2) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.TOO_MANY_VALUES_TO_UNPACK, 2); + } + key = getItem.execute(inliningTarget, storage, 0); + value = getItem.execute(inliningTarget, storage, 1); + } else { + // Acts as a profile + Object iterator = getIter.execute(frame, inliningTarget, element); + try { + key = next.execute(frame, inliningTarget, iterator); + } catch (IteratorExhausted e) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.NEED_MORE_THAN_D_VALUES_TO_UNPACK, 0); + } + try { + value = next.execute(frame, inliningTarget, iterator); + } catch (IteratorExhausted e) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.NEED_MORE_THAN_D_VALUES_TO_UNPACK, 1); + } + try { + next.execute(frame, inliningTarget, iterator); + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.TOO_MANY_VALUES_TO_UNPACK, 2); + } catch (IteratorExhausted e) { + // Expected + } + } + setItem.execute(frame, inliningTarget, target, key, value); + } + } + @GenerateInline @GenerateCached(false) abstract static class UpdateFromArgsNode extends Node { @@ -217,8 +332,8 @@ final void execute(VirtualFrame frame, Node inliningTarget, Object self, Object @Specialization static void update(VirtualFrame frame, Node inliningTarget, Object self, Object mapping, PKeyword[] kwargs, @Cached PyObjectLookupAttr lookupKeys, - @Cached PyDictMerge.MappingNode updateMapping, - @Cached PyDictMerge.FromSeq2Node updateFromSequence, + @Cached UpdateMappingNode updateMapping, + @Cached UpdateFromSequenceNode updateFromSequence, @Cached PyObjectSetItem setItem, @Cached HashingStorageNodes.HashingStorageGetIterator getIterator, @Cached HashingStorageNodes.HashingStorageIteratorNext iteratorNext, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDictMerge.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDictMerge.java index 4e0aee1ee3..facda4999e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDictMerge.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyDictMerge.java @@ -45,6 +45,7 @@ import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError; import static com.oracle.graal.python.runtime.exception.PythonErrorType.ValueError; +import com.oracle.graal.python.builtins.objects.common.HashingCollectionNodes; import com.oracle.graal.python.builtins.objects.common.HashingStorage; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetIterator; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageIterator; @@ -146,7 +147,7 @@ static void doMerge(VirtualFrame frame, Node inliningTarget, Object target, Obje @Cached ListNodes.FastConstructListNode materializeKeys, @Cached SequenceStorageNodes.GetItemScalarNode getKey, @Cached PyObjectGetItem getItem, - @Cached PyObjectSetItem setItem, + @Cached HashingCollectionNodes.SetItemNode setItem, @Cached InlinedLoopConditionProfile loopProfile) { PList keys = materializeKeys.execute(frame, inliningTarget, callKeys.execute(frame, keysMethod)); SequenceStorage keysStorage = keys.getSequenceStorage(); @@ -196,7 +197,7 @@ abstract static class SetItemFromSequenceNode extends PNodeWithContext { static void doGeneric(VirtualFrame frame, Node inliningTarget, Object target, Object element, int index, @Cached ListNodes.ConstructListNode createList, @Cached SequenceStorageNodes.GetItemScalarNode getItem, - @Cached PyObjectSetItem setItem, + @Cached HashingCollectionNodes.SetItemNode setItem, @Cached IsBuiltinObjectProfile isTypeErrorProfile, @Cached InlinedBranchProfile tupleProfile, @Cached InlinedBranchProfile listProfile, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index fdcd80ad66..8b674fbcc1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -577,6 +577,7 @@ public abstract class ErrorMessages { public static final TruffleString NEGATIVE_COUNT = tsLiteral("negative count"); public static final TruffleString NEGATIVE_SHIFT_COUNT = tsLiteral("negative shift count"); public static final TruffleString NEGATIVE_SIZE_PASSED = tsLiteral("negative size passed"); + public static final TruffleString NEED_MORE_THAN_D_VALUES_TO_UNPACK = tsLiteral("need more than %d values to unpack"); public static final TruffleString NEW_TAKES_ONE_ARG = tsLiteral("object.__new__() takes exactly one argument (the type to instantiate)"); public static final TruffleString NEW_TAKES_NO_ARGS = tsLiteral("%N() takes no arguments"); public static final TruffleString NO_ACTIVE_EX_TO_RERAISE = tsLiteral("No active exception to reraise"); @@ -813,6 +814,7 @@ public abstract class ErrorMessages { public static final TruffleString UNSUPPORTED_SIZE_WAS = tsLiteral("unsupported %s size; was: %d"); public static final TruffleString UNSUPPORTED_TARGET_SIZE = tsLiteral("Unsupported target size: %d"); public static final TruffleString UNSUPPORTED_USE_OF_SYS_EXECUTABLE = tsLiteral("internal error: unsupported use of sys.executable"); + public static final TruffleString UPDATE_TAKES_AT_MOST_ONE_POSITIONAL_ARGUMENT_D_GIVEN = tsLiteral("update() takes at most 1 positional argument (%d given)"); public static final TruffleString UPDATING_FINALIZED_DIGEST_IS_NOT_SUPPORTED = tsLiteral("internal error: updating a finalized digest is not supported"); public static final TruffleString UTIME_CANNOT_USE_DIR_FD_AND_FOLLOW_SYMLINKS = tsLiteral("utime: cannot use dir_fd and follow_symlinks together on this platform"); public static final TruffleString VALUE_TOO_LARGE_TO_FIT_INTO_INDEX = tsLiteral("value too large to fit into index-sized integer"); diff --git a/mx.graalpython/downstream_tests.py b/mx.graalpython/downstream_tests.py index fe9982c230..430160dc76 100644 --- a/mx.graalpython/downstream_tests.py +++ b/mx.graalpython/downstream_tests.py @@ -147,11 +147,18 @@ def downstream_test_pydantic_core(graalpy, testdir): '--no-install-package', 'pytest-codspeed', '--no-install-package', 'cffi', '--inexact', # GraalPy change: greenlet crashes on import '--no-install-package', 'greenlet', + # The latest rpds-py release does not yet include the PyO3 fix needed by GraalPy + '--no-install-package', 'rpds-py', ], cwd=repo, env=env, ) del env['UV_PYTHON'] + run( + ['uv', 'pip', 'install', 'rpds-py @ git+https://github.com/crate-py/rpds.git@main', '--no-deps', '--force-reinstall'], + cwd=repo, + env=env, + ) run( ['uv', 'pip', 'install', './pydantic-core', '--no-deps', '--force-reinstall'], cwd=repo,