Skip to content

[Bug]: repr(slice(...)) leaks the internal storage representation for non-integer members #1071

Description

@jseop-lim

Describe the bug

slice.__repr__ formats its three members with Java's String.valueOf instead of Python repr(). Any member that is a tuple, list, or string prints its internal Java representation, so slice(()) gives slice(None, tuple(EmptySequenceStorage[]), None) where CPython gives slice(None, (), None). With a string member the output is not even well formed.

Operating system

Linux

CPU architecture

ARM64

GraalPy version

25.2.4 (Python 3.12.8); also reproduced on 25.0.2

JDK version

No response

Context configuration

No response

Steps to reproduce

print(slice(()))
print(slice((1,)))
print(slice([]))
print(slice(""))

GraalPy 25.2.4:

slice(None, tuple(EmptySequenceStorage[]), None)
slice(None, tuple(IntSequenceStorage[1]), None)
slice(None, list(EmptySequenceStorage[]), None)
slice(None, , None)

Expected behavior

CPython 3.12.13:

slice(None, (), None)
slice(None, (1,), None)
slice(None, [], None)
slice(None, '', None)

Stack trace

Additional context

int and None members are unaffected, since their Java toString() happens to match their Python repr, which is why the common slice(1, 2, 3) case looks fine. Calling repr() on the same tuple directly still gives (); it changes only once the tuple sits inside a slice.

Root Cause

The tp_repr slot at SliceBuiltins.java#L121-L129 delegates to the Java debug toString():

@Specialization
@TruffleBoundary
public static TruffleString repr(PSlice self) {
    return toTruffleStringUncached(self.toString());
}

PSlice.java#L49-L58 appends each member to a StringBuilder, so every member goes through Java String.valueOf rather than Python repr(). Whatever that member's debug toString() produces is what shows up: PTuple.java#L57-L61 formats as tuple(<storage>), EmptySequenceStorage.java#L65-L69 as EmptySequenceStorage[], and ArrayBasedSequenceStorage.java#L69-L82 as the storage class name plus its elements. A TruffleString member stringifies to its bare content, which is why slice("") prints slice(None, , None).

CPython formats every member with %R, i.e. PyObject_Repr, in Objects/sliceobject.c#L373-L377, installed as tp_repr at #L688:

static PyObject *
slice_repr(PySliceObject *r)
{
    return PyUnicode_FromFormat("slice(%R, %R, %R)", r->start, r->stop, r->step);
}

The eval(repr(x)) == x convention described in object.__repr__ does not hold for the GraalPy output.

Fix Suggestion

Apply PyObjectReprAsTruffleStringNode to getStart() / getStop() / getStep() and assemble the result with SimpleTruffleStringFormatNode, matching CPython's slice(%R, %R, %R). RangeBuiltins.java#L331-L347 already does this for range, whose CPython repr formats its three members the same way. One difference from range: a slice member can be any object with a user-defined __repr__, so the repr node needs the real frame rather than null.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions