Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
26dfe58
Remove GetNativeTupleStorage node
fangerer Jun 15, 2026
65d91f1
Some "surrogateescape" fixups
fangerer Jun 18, 2026
e4bdd7f
Don't use node to read item of NativeByteSequenceStorage
fangerer Jun 15, 2026
7d82d4b
Propagate type TruffleString for "errors"
fangerer Jun 15, 2026
216605f
Make CallErrorCallbackNode an inline node
fangerer Jun 15, 2026
9443523
Honor native tuple in SplitLongToSAndNsNode
fangerer Jun 15, 2026
dedcc26
Accept native tuple fds in fork_exec
fangerer Jun 15, 2026
9d81814
Accept native tuples in warning filters
fangerer Jun 15, 2026
212a550
Accept native tuple integer ratios
fangerer Jun 15, 2026
9de55b1
Accept native decoder state tuples
fangerer Jun 15, 2026
186cecc
Accept native tuples
fangerer Jun 16, 2026
6b5ab8c
Add TruffleBoundary to setState methods
fangerer Jun 17, 2026
5312875
Introduce and use EnsureManagedTupleNode
fangerer Jun 17, 2026
f74c2c9
Replace redundant tuple check profiling
fangerer Jun 18, 2026
a424290
Allow native tuples as sequences
fangerer Jun 18, 2026
de624bb
Accept native tuples storage builtins
fangerer Jun 18, 2026
3f07b7b
Allow native tuples in IteratorNodes.ToArrayNode
fangerer Jun 18, 2026
15bdeca
Improve PyTupleCheckExactNode
fangerer Jun 18, 2026
27fec84
Fix MultibyteStreamWriterBuiltins.WritelinesNode
fangerer Jun 18, 2026
7081122
Fix style
fangerer Jun 18, 2026
5329f80
Accept native tuple in PyException_SetArgs
fangerer Jun 25, 2026
c4dd79d
Address review comments
fangerer Jun 25, 2026
fc00ce4
Use cached tuple check nodes
fangerer Jun 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import _io
import codecs
import functools
import io
import itertools
import json
import os
import pickle
import tempfile
import unittest

from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionOutVars, unhandled_error_compare, CPyExtType, \
Expand Down Expand Up @@ -379,3 +388,121 @@ def test_memoryview_cast_shape_native_tuple(self):
assert is_native_object(shape)
view = memoryview(b"abcd").cast("B", shape)
assert view.shape == (2, 2)

def test_utime_ns_divmod_native_tuple(self):
class NativeDivmod:
def __divmod__(self, other):
assert other == 1000000000
result = TupleSubclass(1, 234567890)
assert is_native_object(result)
return result

with tempfile.NamedTemporaryFile() as f:
os.utime(f.name, ns=(NativeDivmod(), NativeDivmod()))
stat = os.stat(f.name)
expected = 1234567890
# We don't care about the exact value. It's about if native tuples are accepted.
tolerance = 1_000_000_000
assert abs(stat.st_atime_ns - expected) <= tolerance
assert abs(stat.st_mtime_ns - expected) <= tolerance

def test_percent_format_native_tuple(self):
args = TupleSubclass("native", 7)
assert is_native_object(args)
assert "%s:%d" % args == "native:7"

bytes_args = TupleSubclass(b"native", 7)
assert is_native_object(bytes_args)
assert b"%s:%d" % bytes_args == b"native:7"

def test_incremental_newline_decoder_getstate_native_tuple(self):
class Decoder:
def getstate(self):
state = TupleSubclass(b"", 0)
assert is_native_object(state)
return state

decoder = _io.IncrementalNewlineDecoder(Decoder(), translate=False)
assert decoder.getstate() == (b"", 0)

def test_incremental_newline_decoder_setstate_native_tuple(self):
state = TupleSubclass(b"buffer", 1)
assert is_native_object(state)

decoder = _io.IncrementalNewlineDecoder(None, translate=False)
decoder.setstate(state)
assert decoder.getstate() == (b"", 1)

class Decoder:
def setstate(self, state):
self.state = state

def getstate(self):
return self.state

state = TupleSubclass(b"buffer", 3)
assert is_native_object(state)

wrapped_decoder = Decoder()
decoder = _io.IncrementalNewlineDecoder(wrapped_decoder, translate=False)
decoder.setstate(state)
assert wrapped_decoder.state == (b"buffer", 1)
assert decoder.getstate() == (b"buffer", 3)

def test_functools_partial_setstate_native_tuple_args(self):
args = TupleSubclass(2, 5)
assert is_native_object(args)
partial = functools.partial(int)
partial.__setstate__((pow, args, None, None))
assert partial() == 32

def test_pickle_memo_accepts_native_tuple_value(self):
value = TupleSubclass(11, "memo")
assert is_native_object(value)
pickler = pickle.Pickler(io.BytesIO())
pickler.memo = {0: value}

def test_json_encodes_native_tuple(self):
array = TupleSubclass("native", 7)
assert is_native_object(array)
assert json.dumps(array, separators=(",", ":")) == '["native",7]'

def test_itertools_cycle_setstate_native_tuple(self):
state = TupleSubclass([], 0)
assert is_native_object(state)
cycle = itertools.cycle([1])
cycle.__setstate__(state)
assert next(cycle) == 1

def test_reversed_native_tuple(self):
values = TupleSubclass(1, 2, 3)
assert is_native_object(values)
assert list(reversed(values)) == [3, 2, 1]

def test_base_exception_group_native_tuple(self):
exceptions = TupleSubclass(ValueError("native"), TypeError("tuple"))
assert is_native_object(exceptions)
group = BaseExceptionGroup("msg", exceptions)
assert group.message == "msg"
assert tuple(type(e) for e in group.exceptions) == (ValueError, TypeError)

def test_base_exception_group_copies_native_tuple_notes(self):
group = BaseExceptionGroup("msg", (ValueError("native"), TypeError("tuple")))
notes = TupleSubclass("note 1", "note 2")
assert is_native_object(notes)
group.__notes__ = notes

match, rest = group.split(ValueError)
assert match.__notes__ == ["note 1", "note 2"]
assert rest.__notes__ == ["note 1", "note 2"]
assert match.__notes__ is not notes
assert rest.__notes__ is not notes
assert match.__notes__ is not rest.__notes__

def test_multibyte_stream_writer_writelines_native_tuple(self):
lines = TupleSubclass("native", " tuple")
assert is_native_object(lines)
stream = io.BytesIO()
writer = codecs.getwriter("euc_jp")(stream)
writer.writelines(lines)
assert stream.getvalue() == b"native tuple"
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@
import com.oracle.graal.python.lib.PyObjectSizeNode;
import com.oracle.graal.python.lib.PyObjectStrAsObjectNode;
import com.oracle.graal.python.lib.PyObjectStrAsTruffleStringNode;
import com.oracle.graal.python.lib.PyTupleCheckNode;
import com.oracle.graal.python.lib.PyTupleGetItem;
import com.oracle.graal.python.lib.PyTupleSizeNode;
import com.oracle.graal.python.lib.PyUnicodeCheckNode;
import com.oracle.graal.python.lib.PyUnicodeFSDecoderNode;
import com.oracle.graal.python.lib.RichCmpOp;
Expand Down Expand Up @@ -2137,6 +2140,9 @@ static Object[] update(Object[] bases,
@Bind PythonLanguage language,
@Cached PyObjectLookupAttr getMroEntries,
@Cached CallUnaryMethodNode callMroEntries,
@Cached PyTupleCheckNode tupleCheckNode,
@Cached PyTupleSizeNode tupleSize,
@Cached PyTupleGetItem tupleGetItem,
@Cached PRaiseNode raiseNode) {
CompilerAsserts.neverPartOfCompilation();
PTuple originalBases = null;
Expand All @@ -2163,10 +2169,9 @@ static Object[] update(Object[] bases,
originalBases = PFactory.createTuple(language, bases);
}
Object newBase = callMroEntries.executeObject(null, meth, originalBases);
if (!PGuards.isPTuple(newBase)) {
if (!tupleCheckNode.execute(inliningTarget, newBase)) {
throw raiseNode.raise(inliningTarget, PythonErrorType.TypeError, ErrorMessages.MRO_ENTRIES_MUST_RETURN_TUPLE);
}
PTuple newBaseTuple = (PTuple) newBase;
if (newBases == null) {
// If this is a first successful replacement, create new_bases list and copy
// previously encountered bases.
Expand All @@ -2175,9 +2180,9 @@ static Object[] update(Object[] bases,
newBases.add(bases[j]);
}
}
SequenceStorage storage = newBaseTuple.getSequenceStorage();
for (int j = 0; j < storage.length(); j++) {
newBases.add(SequenceStorageNodes.GetItemScalarNode.executeUncached(storage, j));
int newBaseLen = tupleSize.execute(inliningTarget, newBase);
for (int j = 0; j < newBaseLen; j++) {
newBases.add(tupleGetItem.execute(inliningTarget, newBase, j));
}
}
if (newBases == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
import com.oracle.graal.python.builtins.objects.common.EmptyStorage;
import com.oracle.graal.python.builtins.objects.common.HashingStorage;
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageLen;
import com.oracle.graal.python.builtins.objects.common.SequenceNodes.GetSequenceStorageNode;
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
import com.oracle.graal.python.builtins.objects.dict.PDict;
import com.oracle.graal.python.builtins.objects.exception.OSErrorEnum;
Expand All @@ -141,6 +142,7 @@
import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs;
import com.oracle.graal.python.lib.PyObjectGetItem;
import com.oracle.graal.python.lib.PyObjectStrAsTruffleStringNode;
import com.oracle.graal.python.lib.PyTupleCheckNode;
import com.oracle.graal.python.nodes.ErrorMessages;
import com.oracle.graal.python.nodes.PConstructAndRaiseNode;
import com.oracle.graal.python.nodes.PRaiseNode;
Expand Down Expand Up @@ -178,8 +180,6 @@
import com.oracle.graal.python.runtime.exception.PythonExitException;
import com.oracle.graal.python.runtime.object.PFactory;
import com.oracle.graal.python.runtime.sequence.PSequence;
import com.oracle.graal.python.runtime.sequence.storage.NativePrimitiveSequenceStorage;
import com.oracle.graal.python.runtime.sequence.storage.NativeSequenceStorage;
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
import com.oracle.graal.python.util.PythonUtils;
import com.oracle.truffle.api.CompilerAsserts;
Expand All @@ -193,7 +193,6 @@
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Exclusive;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
import com.oracle.truffle.api.dsl.NeverDefault;
Expand Down Expand Up @@ -1021,31 +1020,55 @@ private void validate(HashingStorage dictStorage) {
public abstract static class GetStorageStrategyNode extends PythonUnaryBuiltinNode {
@Specialization
@TruffleBoundary
TruffleString doSet(PSequence seq) {
return PythonUtils.toTruffleStringUncached(seq.getSequenceStorage().getClass().getSimpleName());
static TruffleString doSet(Object object,
@Bind Node inliningTarget,
@Cached PyTupleCheckNode tupleCheckNode) {
if (tupleCheckNode.isTupleOrList(inliningTarget, object)) {
SequenceStorage sequenceStorage = GetSequenceStorageNode.executeUncached(object);
return PythonUtils.toTruffleStringUncached(sequenceStorage.getClass().getSimpleName());
}
throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.BAD_ARG_TO_INTERNAL_FUNC);
}
}

@Builtin(name = "storage_to_native", minNumOfPositionalArgs = 1)
@GenerateNodeFactory
abstract static class StorageToNative extends PythonUnaryBuiltinNode {

@Specialization
@TruffleBoundary
Object toNative(PArray array) {
CApiContext.ensureCapiWasLoaded("internal API");
NativeSequenceStorage newStorage = ToNativeStorageNode.executeUncached(array.getSequenceStorage(), true);
array.setSequenceStorage(newStorage);
return array;
@Specialization
static Object doGeneric(Object object,
@Bind Node inliningTarget,
@Cached PyTupleCheckNode tupleCheckNode) {
if (object instanceof PArray array) {
array.setSequenceStorage(ToNativeStorageNode.executeUncached(array.getSequenceStorage(), true));
} else if (object instanceof PSequence sequence) {
sequence.setSequenceStorage(ToNativeStorageNode.executeUncached(sequence.getSequenceStorage(), sequence instanceof PBytesLike));
} else if (!tupleCheckNode.isTupleOrList(inliningTarget, object)) {
throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.BAD_ARG_TO_INTERNAL_FUNC);
}
return object;
}
}

@Builtin(name = "storage_to_native_primitive", minNumOfPositionalArgs = 1)
@GenerateNodeFactory
abstract static class StorageToNativePrimitive extends PythonUnaryBuiltinNode {

@Specialization
@TruffleBoundary
Object toNative(PSequence sequence) {
@Specialization
static Object doGeneric(Object object,
@Bind Node inliningTarget,
@Cached PyTupleCheckNode tupleCheckNode) {
CApiContext.ensureCapiWasLoaded("internal API");
NativeSequenceStorage newStorage = ToNativeStorageNode.executeUncached(sequence.getSequenceStorage(), sequence instanceof PBytesLike);
sequence.setSequenceStorage(newStorage);
return sequence;
if (object instanceof PArray array) {
array.setSequenceStorage(ToNativePrimitiveStorageNode.executeUncached(array.getSequenceStorage()));
} else if (object instanceof PSequence sequence) {
sequence.setSequenceStorage(ToNativePrimitiveStorageNode.executeUncached(sequence.getSequenceStorage()));
} else if (!tupleCheckNode.isTupleOrList(inliningTarget, object)) {
throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.BAD_ARG_TO_INTERNAL_FUNC);
}
return object;
}
}

Expand Down Expand Up @@ -1340,29 +1363,6 @@ TruffleString get() {
}
}

@Builtin(name = "storage_to_native_primitive", minNumOfPositionalArgs = 1)
@GenerateNodeFactory
abstract static class StorageToNativePrimitive extends PythonUnaryBuiltinNode {

@Specialization
static Object doArray(PArray array,
@Shared @Cached ToNativePrimitiveStorageNode toNativePrimitiveNode,
@Bind Node inliningTarget) {
NativePrimitiveSequenceStorage newStorage = toNativePrimitiveNode.execute(inliningTarget, array.getSequenceStorage());
array.setSequenceStorage(newStorage);
return array;
}

@Specialization
static Object doSequence(PSequence sequence,
@Shared @Cached ToNativePrimitiveStorageNode toNativePrimitiveNode,
@Bind Node inliningTarget) {
NativePrimitiveSequenceStorage newStorage = toNativePrimitiveNode.execute(inliningTarget, sequence.getSequenceStorage());
sequence.setSequenceStorage(newStorage);
return sequence;
}
}

@Builtin(name = "clear_interop_type_registry", maxNumOfPositionalArgs = 0)
@GenerateNodeFactory
public abstract static class ClearInteropTypeRegistry extends PythonBuiltinNode {
Expand Down
Loading
Loading