diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_tuple.py b/graalpython/com.oracle.graal.python.test/src/tests/test_tuple.py index 4f0094f8b5..4d41ba3a38 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_tuple.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_tuple.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, 2024, Oracle and/or its affiliates. +# Copyright (c) 2018, 2026, Oracle and/or its affiliates. # Copyright (C) 1996-2017 Python Software Foundation # # Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 @@ -283,6 +283,38 @@ def maketuple(t): self.assertTrue(tuple(b) is b) self.assertFalse(tuple(a) is a) + def test_constructor_validates_length_hint_before_iteration(self): + class BadLengthHint: + def __getitem__(self, index): + raise AssertionError("__getitem__ should not be called") + + def __length_hint__(self): + return None + + with self.assertRaisesRegex(TypeError, "__length_hint__ must be an integer"): + tuple(BadLengthHint()) + + def test_constructor_validates_len_before_iteration(self): + class BadLen: + def __getitem__(self, index): + raise AssertionError("__getitem__ should not be called") + + def __len__(self): + return -1 + + with self.assertRaisesRegex(ValueError, "__len__\\(\\) should return >= 0"): + tuple(BadLen()) + + def test_constructor_length_hint_too_small(self): + class SmallLengthHint: + def __iter__(self): + return iter((1, 2, 3)) + + def __length_hint__(self): + return 1 + + self.assertEqual(tuple(SmallLengthHint()), (1, 2, 3)) + class TupleCompareTest(CompareTest): diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/iterator/IteratorNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/iterator/IteratorNodes.java index 0c559e09e8..558ca91b62 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/iterator/IteratorNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/iterator/IteratorNodes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -111,6 +111,7 @@ public abstract class IteratorNodes { */ @GenerateInline @GenerateCached(false) + @GenerateUncached @ImportStatic({PGuards.class, SpecialMethodNames.class}) public abstract static class GetLength extends PNodeWithContext { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java index 7c4b1893d3..2bf71294a5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -49,6 +49,7 @@ import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.CreateStorageFromIteratorNode; +import com.oracle.graal.python.builtins.objects.iterator.IteratorNodes; import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.tuple.PTuple; import com.oracle.graal.python.lib.PyObjectGetIter; @@ -101,10 +102,12 @@ static PTuple list(PList iterable, static PTuple generic(VirtualFrame frame, Object iterable, @Bind Node inliningTarget, @Bind PythonLanguage language, + @Cached IteratorNodes.GetLength lenNode, @Cached CreateStorageFromIteratorNode storageNode, @Cached PyObjectGetIter getIter) { + int len = lenNode.execute(frame, inliningTarget, iterable); Object iterObj = getIter.execute(frame, inliningTarget, iterable); - return PFactory.createTuple(language, storageNode.execute(frame, iterObj)); + return PFactory.createTuple(language, storageNode.execute(frame, iterObj, len)); } @NeverDefault