Skip to content

Commit 79fa251

Browse files
committed
Ensure that HeldClass honors __str__ and __repr__
1 parent a6bf326 commit 79fa251

File tree

4 files changed

+58
-1
lines changed

4 files changed

+58
-1
lines changed

typed_python/ClassType.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ void Class::repr(instance_ptr self, ReprAccumulator& stream, bool isStr) {
157157
);
158158
}
159159

160-
161160
layout& l = *instanceToLayout(self);
162161
m_heldClass->repr(l.data, stream, isStr, true /* isClassNotHeldClass */);
163162
}

typed_python/HeldClassType.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,42 @@ bool HeldClass::cmp(instance_ptr left, instance_ptr right, int pyComparisonOp, b
8787
}
8888

8989
void HeldClass::repr(instance_ptr self, ReprAccumulator& stream, bool isStr, bool isClassNotHeldClass) {
90+
auto it = getMemberFunctions().find(isStr ? "__str__" : "__repr__");
91+
92+
if (it != getMemberFunctions().end()) {
93+
PyEnsureGilAcquired acquireTheGil;
94+
95+
PyObjectStealer selfAsPyObj(PyInstance::extractPythonObject(self, this));
96+
97+
std::pair<bool, PyObject*> res = PyFunctionInstance::tryToCall(
98+
it->second,
99+
nullptr,
100+
selfAsPyObj
101+
);
102+
103+
if (res.first) {
104+
if (!res.second) {
105+
throw PythonExceptionSet();
106+
}
107+
if (!PyUnicode_Check(res.second)) {
108+
decref(res.second);
109+
throw std::runtime_error(
110+
stream.isStrCall() ? "__str__ returned a non-string" : "__repr__ returned a non-string"
111+
);
112+
}
113+
114+
stream << PyUnicode_AsUTF8(res.second);
115+
decref(res.second);
116+
117+
return;
118+
}
119+
120+
throw std::runtime_error(
121+
stream.isStrCall() ? "Found a __str__ method but failed to call it with 'self'"
122+
: "Found a __repr__ method but failed to call it with 'self'"
123+
);
124+
}
125+
90126
PushReprState isNew(stream, self);
91127

92128
std::string name = isClassNotHeldClass ? getClassType()->name() : m_name;

typed_python/compiler/tests/held_class_compilation_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ def getX(self):
2929
def pointerToSelf(self):
3030
return pointerTo(self)
3131

32+
def __repr__(self):
33+
return "ReprForH"
34+
35+
def __str__(self):
36+
return "StrForH"
37+
3238

3339
Complex = Forward("Complex")
3440

@@ -73,6 +79,14 @@ def callPointerTo(h):
7379
assert callPointerTo(h) == pointerTo(h)
7480
assert h.pointerToSelf() == pointerTo(h)
7581

82+
def test_held_class_repr(self):
83+
assert repr(H()) == "ReprForH"
84+
self.checkCompiler(lambda x: repr(x), H())
85+
86+
def test_held_class_str(self):
87+
assert str(H()) == "StrForH"
88+
self.checkCompiler(lambda x: str(x), H())
89+
7690
def test_held_class_entrypointed_methods(self):
7791
h1 = H(x=2, y=3)
7892
h2 = H(x=2, y=3)

typed_python/compiler/tests/held_class_interpreter_semantics_test.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ def increment(self):
2525

2626

2727
class TestHeldClassInterpreterSemantics(unittest.TestCase):
28+
def test_held_class_str(self):
29+
@Held
30+
class H(Class):
31+
def __str__(self):
32+
return "HI"
33+
34+
assert str(H()) == "HI"
35+
2836
def test_can_return_held_class_from_typed_function(self):
2937
@Function
3038
def f(x, y) -> H:

0 commit comments

Comments
 (0)