From c7a56af3072c2fa89f0968d7f00b22f7bff0812b Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Thu, 9 Jun 2022 08:37:02 -0700 Subject: [PATCH] [lldb][bindings] Implement __repr__ instead of __str__ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using the `script` Python repl, SB objects are printed in a way that gives the user no information. The simplest example is: ``` (lldb) script lldb.debugger > ``` This output comes from the Python repl printing the `repr()` of an object. None of the SB classes implement `__repr__`, and all print like the above. However, many (most?, all?) SB classes implement `__str__`. Because they implement `__str__`, a more detailed output can be had by `print`ing the object, for example: ``` (lldb) script print(lldb.debugger) Debugger (instance: "debugger_1", id: 1) ``` For convenience, this change switches all SB classes that implement to `__str__` to instead implement `__repr__`. **The result is that `str()` and `repr()` will produce the same output**. This is because `str` calls `__repr__` for classes that have no `__str__` method. The benefit being that when writing a `script` invocation, you don't need to remember to wrap in `print()`. If that isn't enough motivation, consider the case where your Python expression results in a list of SB objects, in that case you'd have to `map` or use a list comprehension like `[str(x) for x in ]` in order to see the details of the objects in the list. For reference, the docs for `repr` say: > repr(object) > Return a string containing a printable representation of an object. For > many types, this function makes an attempt to return a string that would > yield an object with the same value when passed to eval(); otherwise, the > representation is a string enclosed in angle brackets that contains the > name of the type of the object together with additional information often > including the name and address of the object. A class can control what this > function returns for its instances by defining a __repr__() method. and the docs for `__repr__` say: > object.__repr__(self) > Called by the repr() built-in function to compute the “official” string > representation of an object. If at all possible, this should look like a > valid Python expression that could be used to recreate an object with the > same value (given an appropriate environment). If this is not possible, a > string of the form <...some useful description...> should be returned. The > return value must be a string object. If a class defines __repr__() but not > __str__(), then __repr__() is also used when an “informal” string > representation of instances of that class is required. > > This is typically used for debugging, so it is important that the > representation is information-rich and unambiguous. Even if it were convenient to construct Python expressions for SB classes so that they could be `eval`'d, however for typical lldb usage, I can't think of a motivating reason to do so. As it stands, the only action the docs say to do, that this change doesn't do, is wrap the `repr` string in `<>` angle brackets. An alternative implementation is to change lldb's python repl to apply `str()` to the top level result. While this would work well in the case of a single SB object, it doesn't work for a list of SB objects, since `str([x])` uses `repr` to convert each list element to a string. Differential Revision: https://reviews.llvm.org/D127458 --- lldb/bindings/macros.swig | 4 ++-- lldb/test/API/sanity/TestReprStrEquality.py | 18 ++++++++++++++++++ lldb/test/Shell/Driver/Inputs/convenience.in | 3 +++ .../Shell/Driver/TestConvenienceVariables.test | 6 ++++++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 lldb/test/API/sanity/TestReprStrEquality.py diff --git a/lldb/bindings/macros.swig b/lldb/bindings/macros.swig index 6793a11c5bd9c..84f2442457380 100644 --- a/lldb/bindings/macros.swig +++ b/lldb/bindings/macros.swig @@ -1,6 +1,6 @@ %define STRING_EXTENSION_LEVEL(Class, Level) %extend { - std::string lldb:: ## Class ## ::__str__(){ + std::string lldb:: ## Class ## ::__repr__(){ lldb::SBStream stream; $self->GetDescription (stream, Level); const char *desc = stream.GetData(); @@ -15,7 +15,7 @@ %define STRING_EXTENSION(Class) %extend { - std::string lldb:: ## Class ## ::__str__(){ + std::string lldb:: ## Class ## ::__repr__(){ lldb::SBStream stream; $self->GetDescription (stream); const char *desc = stream.GetData(); diff --git a/lldb/test/API/sanity/TestReprStrEquality.py b/lldb/test/API/sanity/TestReprStrEquality.py new file mode 100644 index 0000000000000..798c0f6d7eb8a --- /dev/null +++ b/lldb/test/API/sanity/TestReprStrEquality.py @@ -0,0 +1,18 @@ +""" +This is a sanity check that verifies that `repr(sbobject)` and `str(sbobject)` +produce the same string. +""" + + +import lldb +from lldbsuite.test.lldbtest import * + + +class TestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + def test(self): + self.assertEqual(repr(self.dbg), str(self.dbg)) diff --git a/lldb/test/Shell/Driver/Inputs/convenience.in b/lldb/test/Shell/Driver/Inputs/convenience.in index 6d5603c435135..f68dc7423d8b8 100644 --- a/lldb/test/Shell/Driver/Inputs/convenience.in +++ b/lldb/test/Shell/Driver/Inputs/convenience.in @@ -1,8 +1,11 @@ breakpoint set -f hello.cpp -p Hello run script print(lldb.debugger) +script lldb.debugger script print(lldb.target) +script lldb.target script print(lldb.process) +script lldb.process script print(lldb.thread.GetStopDescription(100)) script lldb.frame.GetLineEntry().GetLine() script lldb.frame.GetLineEntry().GetFileSpec().GetFilename() diff --git a/lldb/test/Shell/Driver/TestConvenienceVariables.test b/lldb/test/Shell/Driver/TestConvenienceVariables.test index d9c8f7581bea0..63ba15ec7468c 100644 --- a/lldb/test/Shell/Driver/TestConvenienceVariables.test +++ b/lldb/test/Shell/Driver/TestConvenienceVariables.test @@ -6,10 +6,16 @@ RUN: %lldb %t/target.out -s %p/Inputs/convenience.in -o quit | FileCheck %s CHECK: stop reason = breakpoint 1.1 CHECK: script print(lldb.debugger) CHECK-NEXT: Debugger (instance: {{.*}}, id: {{[0-9]+}}) +CHECK: script lldb.debugger +CHECK-NEXT: Debugger (instance: {{.*}}, id: {{[0-9]+}}) CHECK: script print(lldb.target) CHECK-NEXT: target.out +CHECK: script lldb.target +CHECK-NEXT: target.out CHECK: script print(lldb.process) CHECK-NEXT: SBProcess: pid = {{[0-9]+}}, state = stopped, threads = {{[0-9]+}}, executable = target.out +CHECK: script lldb.process +CHECK-NEXT: SBProcess: pid = {{[0-9]+}}, state = stopped, threads = {{[0-9]+}}, executable = target.out CHECK: script print(lldb.thread.GetStopDescription(100)) CHECK-NEXT: breakpoint 1.1 CHECK: script lldb.frame.GetLineEntry().GetLine()