[Lldb-commits] [lldb] [lldb] Make ScriptedPythonInterface::Dispatch return llvm::Expected<T> (PR #224988)

Med Ismail Bennani via lldb-commits lldb-commits at lists.llvm.org
Sun Sep 20 20:19:00 PDT 2026


https://github.com/medismailben created https://github.com/llvm/llvm-project/pull/224988

Until now, `Dispatch` returned the extracted value and reported failure through a `Status &` out-parameter, which leaves the two outcomes indistinguishable at the value level: a Python method that returns `None` and one that raises both hand back a default-constructed `T`. Only `error` told them apart, and because the signature advertises the value, call sites reached for the value and dropped the error.

Three real instances of that:

 - `ScriptedHookPythonInterface::HandleStop` returned `true` whenever the object was null, discarding the backtrace of a raising `handle_stop` (fixed separately).
 - `GetStatusFromMethod` discarded `Dispatch<Status>`'s return value, so the `SBError` returned by `ScriptedProcess.launch/attach/resume` and the three `ScriptedPlatform` entry points was ignored entirely: a `launch()` returning `SBError("no such file")` reported success.
 - `GetThreadPlanMetadataForStepType` string-matched the exception text to guess whether the method existed, with a FIXME asking for exactly this.

This switches `Dispatch`, `CallStaticMethod` and `ExtractValueFromPythonObject` to `llvm::Expected<T>` and drop the out-parameter. `CheckStructuredDataObject` becomes dead and is removed.

An absent method now yields `UnimplementedError`. Three helpers let each call site state its intent: `DispatchToOptional` for optional methods a script may legitimately omit, which reports an unimplemented method as `std::nullopt` and lets every other failure propagate; `LogAndDefault` for entry points whose return type cannot express failure, which logs the error and falls back to a default-constructed value instead of discarding it; and `LogAndError`, which builds and logs the errors `Dispatch` returns itself.

Widening the `LogAndDefault` entry points to `Expected` touches every caller in ScriptedThread/ScriptedProcess/ScriptedFrame and is left as follow-up.

This patch stops at the interface layer deliberately: it is about surfacing the error and the Python exception out of `Dispatch` and into the scripted interface classes. That has to happen to all of them at once, because they share the same dispatch entry point, which is why this touches every interface class together. Carrying those exceptions the rest of the way to the user is per-extension work, and lands as a follow-up PR for each extension.

>From e632cb25005c69b32c442b858baf506ff050d878 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Sun, 20 Sep 2026 20:08:44 -0700
Subject: [PATCH] [lldb] Make ScriptedPythonInterface::Dispatch return
 llvm::Expected<T>

`Dispatch` returned the extracted value and reported failure through a
`Status &` out-parameter, which leaves the two outcomes indistinguishable
at the value level: a Python method that returns `None` and one that
raises both hand back a default-constructed `T`. Only `error` told them
apart, and because the signature advertises the value, call sites reached
for the value and dropped the error.

Three real instances of that:

 - `ScriptedHookPythonInterface::HandleStop` returned `true` whenever the
   object was null, discarding the backtrace of a raising `handle_stop`
   (fixed separately).
 - `GetStatusFromMethod` discarded `Dispatch<Status>`'s return value, so
   the `SBError` returned by `ScriptedProcess.launch/attach/resume` and
   the three `ScriptedPlatform` entry points was ignored entirely: a
   `launch()` returning `SBError("no such file")` reported success.
 - `GetThreadPlanMetadataForStepType` string-matched the exception text to
   guess whether the method existed, with a FIXME asking for exactly this.

This switches `Dispatch`, `CallStaticMethod` and `ExtractValueFromPythonObject`
to `llvm::Expected<T>` and drop the out-parameter. `CheckStructuredDataObject`
becomes dead and is removed.

An absent method now yields `UnimplementedError`. Three helpers let each call
site state its intent: `DispatchToOptional` for optional methods a script may
legitimately omit, which reports an unimplemented method as `std::nullopt` and
lets every other failure propagate; `LogAndDefault` for entry points whose
return type cannot express failure, which logs the error and falls back to a
default-constructed value instead of discarding it; and `LogAndError`,
which builds and logs the errors `Dispatch` returns itself.

Widening the `LogAndDefault` entry points to `Expected` touches every
caller in ScriptedThread/ScriptedProcess/ScriptedFrame and is left as
follow-up.

This patch stops at the interface layer deliberately: it is about surfacing
the error and the Python exception out of `Dispatch` and into the scripted
interface classes. That has to happen to all of them at once, because
they share the same dispatch entry point, which is why this touches every
interface class together. Carrying those exceptions the rest of the way
to the user is per-extension work, and lands as a follow-up PR for each
extension.

Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
 .../Interfaces/ScriptedInterface.h            |  18 -
 .../Interfaces/ScriptedThreadPlanInterface.h  |   4 +-
 .../OperatingSystemPythonInterface.cpp        |  37 +-
 .../ScriptedBreakpointPythonInterface.cpp     |  78 ++--
 .../ScriptedCommandPythonInterface.cpp        |  90 ++---
 .../ScriptedFrameProviderPythonInterface.cpp  |  34 +-
 .../ScriptedFramePythonInterface.cpp          | 175 ++++-----
 .../ScriptedHookPythonInterface.cpp           |  29 +-
 .../ScriptedPlatformPythonInterface.cpp       |  32 +-
 .../ScriptedProcessPythonInterface.cpp        | 131 +++----
 .../Interfaces/ScriptedPythonInterface.cpp    | 283 ++++++--------
 .../Interfaces/ScriptedPythonInterface.h      | 365 +++++++++++-------
 ...tedStackFrameRecognizerPythonInterface.cpp |  45 +--
 .../ScriptedStringSummaryPythonInterface.cpp  |  15 +-
 ...riptedSyntheticChildrenPythonInterface.cpp |  57 +--
 .../ScriptedThreadPlanPythonInterface.cpp     |  75 ++--
 .../ScriptedThreadPlanPythonInterface.h       |   2 +-
 .../ScriptedThreadPythonInterface.cpp         |  82 ++--
 lldb/source/Target/ScriptedThreadPlan.cpp     |  12 +-
 .../target/stop-hooks/TestStopHookScripted.py |   2 +-
 .../scripted_process/TestScriptedProcess.py   |   6 +-
 21 files changed, 732 insertions(+), 840 deletions(-)

diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h
index 417a143cb3e3d..bacfeb029bda8 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h
@@ -84,24 +84,6 @@ class ScriptedInterface {
     return {};
   }
 
-  template <typename T = StructuredData::ObjectSP>
-  static bool CheckStructuredDataObject(llvm::StringRef caller, T obj,
-                                        Status &error) {
-    if (!obj)
-      return ErrorWithMessage<bool>(caller, "Null Structured Data object",
-                                    error);
-
-    if (!obj->IsValid()) {
-      return ErrorWithMessage<bool>(caller, "Invalid StructuredData object",
-                                    error);
-    }
-
-    if (error.Fail())
-      return ErrorWithMessage<bool>(caller, error.AsCString(), error);
-
-    return true;
-  }
-
   static bool CreateInstance(lldb::ScriptLanguage language,
                              ScriptedInterfaceUsages usages) {
     return false;
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedThreadPlanInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedThreadPlanInterface.h
index 2d51ee306c94f..af143f9e0794e 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedThreadPlanInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedThreadPlanInterface.h
@@ -26,7 +26,9 @@ class ScriptedThreadPlanInterface : virtual public ScriptedInterface {
 
   virtual llvm::Expected<bool> IsStale() { return true; };
 
-  virtual lldb::StateType GetRunState() { return lldb::eStateStepping; }
+  virtual llvm::Expected<lldb::StateType> GetRunState() {
+    return lldb::eStateStepping;
+  }
 
   virtual llvm::Error GetStopDescription(lldb::StreamSP &stream) {
     return llvm::Error::success();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.cpp
index 6a1bcf41ba9d5..709018324d23b 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.cpp
@@ -38,24 +38,20 @@ OperatingSystemPythonInterface::CreatePluginObject(
 StructuredData::DictionarySP
 OperatingSystemPythonInterface::CreateThread(lldb::tid_t tid,
                                              lldb::addr_t context) {
-  Status error;
-  StructuredData::DictionarySP dict = Dispatch<StructuredData::DictionarySP>(
-      "create_thread", error, tid, context);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict,
-                                                    error))
+  StructuredData::DictionarySP dict = LogAndDefault(
+      Dispatch<StructuredData::DictionarySP>("create_thread", tid, context),
+      LLVM_PRETTY_FUNCTION);
+  if (!dict)
     return {};
 
   return dict;
 }
 
 StructuredData::ArraySP OperatingSystemPythonInterface::GetThreadInfo() {
-  Status error;
   StructuredData::ArraySP arr =
-      Dispatch<StructuredData::ArraySP>("get_thread_info", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, arr,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::ArraySP>("get_thread_info"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!arr)
     return {};
 
   return arr;
@@ -67,24 +63,21 @@ StructuredData::DictionarySP OperatingSystemPythonInterface::GetRegisterInfo() {
 
 std::optional<std::string>
 OperatingSystemPythonInterface::GetRegisterContextForTID(lldb::tid_t tid) {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_register_data", error, tid);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_register_data", tid), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
-  return obj->GetAsString()->GetValue().str();
+  return obj->GetStringValue().str();
 }
 
 std::optional<bool> OperatingSystemPythonInterface::DoesPluginReportAllThreads() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("does_plugin_report_all_threads", error);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      Dispatch("does_plugin_report_all_threads"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
-  return obj->GetAsBoolean()->GetValue();
+  return obj->GetBooleanValue();
 }
 
 void OperatingSystemPythonInterface::Initialize() {
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.cpp
index dd93df2cd75bb..c492e4fa23e12 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedBreakpointPythonInterface.cpp
@@ -38,56 +38,38 @@ ScriptedBreakpointPythonInterface::CreatePluginObject(
 
 bool ScriptedBreakpointPythonInterface::OverridesResolver(
     Target &target, StructuredDataImpl &resolver_data) {
-  Status error;
-
   TargetSP target_sp = target.shared_from_this();
 
   StructuredData::ObjectSP obj =
-      Dispatch("overrides_resolver", error, target_sp, resolver_data);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
-    Log *log = GetLog(LLDBLog::Script);
-    LLDB_LOG(log, "Error calling overrides_resolver method: {0}", error);
+      LogAndDefault(Dispatch("overrides_resolver", target_sp, resolver_data),
+                    LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return false;
-  }
+
   return obj->GetBooleanValue();
 }
 
 void ScriptedBreakpointPythonInterface::SetBreakpoint(
     lldb::BreakpointSP break_sp) {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("set_breakpoint", error, break_sp);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
-    Log *log = GetLog(LLDBLog::Script);
-    LLDB_LOG(log, "Error calling set_breakpoint method: {0}", error);
-  }
+  LogAndDefault(Dispatch("set_breakpoint", break_sp), LLVM_PRETTY_FUNCTION);
 }
 
 bool ScriptedBreakpointPythonInterface::ResolverCallback(
     SymbolContext sym_ctx) {
-  Status error;
-
-  StructuredData::ObjectSP obj = Dispatch("__callback__", error, sym_ctx);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
-    Log *log = GetLog(LLDBLog::Script);
-    LLDB_LOG(log, "Error calling __callback__ method: {}", error);
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("__callback__", sym_ctx), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return true;
-  }
+
   return obj->GetBooleanValue();
 }
 
 lldb::SearchDepth ScriptedBreakpointPythonInterface::GetDepth() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("__get_depth__", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("__get_depth__"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return lldb::eSearchDepthModule;
-  }
+
   uint64_t value = obj->GetUnsignedIntegerValue();
   if (value <= lldb::kLastSearchDepthKind)
     return (lldb::SearchDepth)value;
@@ -97,41 +79,37 @@ lldb::SearchDepth ScriptedBreakpointPythonInterface::GetDepth() {
 }
 
 std::optional<std::string> ScriptedBreakpointPythonInterface::GetShortHelp() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_short_help", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_short_help"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
-  }
 
-  return obj->GetAsString()->GetValue().str();
+  return obj->GetStringValue().str();
 }
 
 lldb::BreakpointLocationSP ScriptedBreakpointPythonInterface::WasHit(
     lldb::StackFrameSP frame_sp, lldb::BreakpointLocationSP bp_loc_sp) {
-  Status py_error;
-  lldb::BreakpointLocationSP loc_sp = Dispatch<lldb::BreakpointLocationSP>(
-      "was_hit", py_error, frame_sp, bp_loc_sp);
-
-  if (py_error.Fail())
+  llvm::Expected<lldb::BreakpointLocationSP> loc_or_err =
+      Dispatch<lldb::BreakpointLocationSP>("was_hit", frame_sp, bp_loc_sp);
+  if (!loc_or_err) {
+    LLDB_LOG_ERROR(GetLog(LLDBLog::Script), loc_or_err.takeError(),
+                   "Error calling was_hit method: {0}");
     return bp_loc_sp;
+  }
 
-  return loc_sp;
+  return *loc_or_err;
 }
 
 std::optional<std::string>
 ScriptedBreakpointPythonInterface::GetLocationDescription(
     lldb::BreakpointLocationSP bp_loc_sp, lldb::DescriptionLevel level) {
-  Status error;
   StructuredData::ObjectSP obj =
-      Dispatch("get_location_description", error, bp_loc_sp, level);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+      LogAndDefault(Dispatch("get_location_description", bp_loc_sp, level),
+                    LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
-  return obj->GetAsString()->GetValue().str();
+  return obj->GetStringValue().str();
 }
 
 void ScriptedBreakpointPythonInterface::Initialize() {
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.cpp
index 8d088b44427bc..95283f5d2e364 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.cpp
@@ -69,13 +69,14 @@ bool ScriptedCommandPythonInterface::RunRawCommand(
       debugger_sp, synchronicity);
 
   std::string args_str = args.str();
-  Dispatch("__call__", error, debugger_sp, args_str.c_str(), exe_ctx_ref_sp,
-           &cmd_retobj);
-
-  if (!error.Success() || cmd_retobj.GetStatus() == eReturnStatusFailed)
+  llvm::Expected<StructuredData::ObjectSP> obj_or_err = Dispatch(
+      "__call__", debugger_sp, args_str.c_str(), exe_ctx_ref_sp, &cmd_retobj);
+  if (!obj_or_err) {
+    error = Status::FromError(obj_or_err.takeError());
     return false;
+  }
 
-  return true;
+  return cmd_retobj.GetStatus() != eReturnStatusFailed;
 }
 
 bool ScriptedCommandPythonInterface::RunParsedCommand(
@@ -101,36 +102,35 @@ bool ScriptedCommandPythonInterface::RunParsedCommand(
     args_arr_sp->AddStringItem(entry.ref());
   StructuredDataImpl args_impl(args_arr_sp);
 
-  Dispatch("__call__", error, debugger_sp, args_impl, exe_ctx_ref_sp,
-           &cmd_retobj);
-
-  if (!error.Success() || cmd_retobj.GetStatus() == eReturnStatusFailed)
+  llvm::Expected<StructuredData::ObjectSP> obj_or_err =
+      Dispatch("__call__", debugger_sp, args_impl, exe_ctx_ref_sp, &cmd_retobj);
+  if (!obj_or_err) {
+    error = Status::FromError(obj_or_err.takeError());
     return false;
+  }
 
-  return true;
+  return cmd_retobj.GetStatus() != eReturnStatusFailed;
 }
 
 std::optional<std::string>
 ScriptedCommandPythonInterface::GetRepeatCommand(Args &args) {
   std::string command;
   args.GetQuotedCommandString(command);
-  Status error;
-  StructuredData::ObjectSP obj =
-      Dispatch("get_repeat_command", error, command.c_str());
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      Dispatch("get_repeat_command", command.c_str()), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
+
   return obj->GetStringValue().str();
 }
 
 StructuredData::DictionarySP
 ScriptedCommandPythonInterface::HandleArgumentCompletion(
     std::vector<std::string> &args, size_t args_pos, size_t char_in_arg) {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("handle_argument_completion", error,
-                                          args, args_pos, char_in_arg);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      Dispatch("handle_argument_completion", args, args_pos, char_in_arg),
+      LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
   StructuredData::DictionarySP dict_sp(new StructuredData::Dictionary(obj));
   if (dict_sp->GetType() == lldb::eStructuredDataTypeInvalid)
@@ -141,13 +141,12 @@ ScriptedCommandPythonInterface::HandleArgumentCompletion(
 StructuredData::DictionarySP
 ScriptedCommandPythonInterface::HandleOptionArgumentCompletion(
     llvm::StringRef &long_option, size_t char_in_arg) {
-  Status error;
   std::string long_option_str = long_option.str();
   StructuredData::ObjectSP obj =
-      Dispatch("handle_option_argument_completion", error,
-               long_option_str.c_str(), char_in_arg);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+      LogAndDefault(Dispatch("handle_option_argument_completion",
+                             long_option_str.c_str(), char_in_arg),
+                    LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   // A boolean return means: True means completion handled but no
@@ -168,10 +167,9 @@ ScriptedCommandPythonInterface::HandleOptionArgumentCompletion(
 
 bool ScriptedCommandPythonInterface::GetShortHelp(std::string &dest) {
   dest.clear();
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_short_help", error);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_short_help"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return false;
   dest = obj->GetStringValue().str();
   return !dest.empty();
@@ -179,39 +177,36 @@ bool ScriptedCommandPythonInterface::GetShortHelp(std::string &dest) {
 
 bool ScriptedCommandPythonInterface::GetLongHelp(std::string &dest) {
   dest.clear();
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_long_help", error);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_long_help"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return false;
   dest = obj->GetStringValue().str();
   return !dest.empty();
 }
 
 uint32_t ScriptedCommandPythonInterface::GetFlags() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_flags", error);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_flags"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return 0;
+
   return static_cast<uint32_t>(obj->GetUnsignedIntegerValue());
 }
 
 StructuredData::ObjectSP
 ScriptedCommandPythonInterface::GetOptionsDefinition() {
-  Status error;
-  return Dispatch("get_options_definition", error);
+  return LogAndDefault(Dispatch("get_options_definition"),
+                       LLVM_PRETTY_FUNCTION);
 }
 
 StructuredData::ObjectSP
 ScriptedCommandPythonInterface::GetArgumentsDefinition() {
-  Status error;
-  return Dispatch("get_args_definition", error);
+  return LogAndDefault(Dispatch("get_args_definition"), LLVM_PRETTY_FUNCTION);
 }
 
 void ScriptedCommandPythonInterface::OptionParsingStarted() {
-  Status error;
-  Dispatch("option_parsing_started", error);
+  LogAndDefault(Dispatch("option_parsing_started"), LLVM_PRETTY_FUNCTION);
 }
 
 bool ScriptedCommandPythonInterface::SetOptionValue(ExecutionContext *exe_ctx,
@@ -220,14 +215,13 @@ bool ScriptedCommandPythonInterface::SetOptionValue(ExecutionContext *exe_ctx,
   lldb::ExecutionContextRefSP exe_ctx_ref_sp;
   if (exe_ctx)
     exe_ctx_ref_sp = std::make_shared<ExecutionContextRef>(exe_ctx);
-  Status error;
   std::string long_option_str = long_option.str();
   std::string value_str = value.str();
   StructuredData::ObjectSP obj =
-      Dispatch("set_option_value", error, exe_ctx_ref_sp,
-               long_option_str.c_str(), value_str.c_str());
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+      LogAndDefault(Dispatch("set_option_value", exe_ctx_ref_sp,
+                             long_option_str.c_str(), value_str.c_str()),
+                    LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return false;
   return obj->GetBooleanValue();
 }
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
index f953fb21e0c49..a90f0a9d9d07c 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp
@@ -32,11 +32,10 @@ bool ScriptedFrameProviderPythonInterface::AppliesToThread(
   // If there is any issue with this method, we will just assume it also applies
   // to this thread which is the default behavior.
   constexpr bool fail_value = true;
-  Status error;
-  StructuredData::ObjectSP obj =
-      CallStaticMethod(class_name, "applies_to_thread", error, thread_sp);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      CallStaticMethod(class_name, "applies_to_thread", thread_sp),
+      LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return fail_value;
 
   return obj->GetBooleanValue(fail_value);
@@ -55,11 +54,9 @@ ScriptedFrameProviderPythonInterface::CreatePluginObject(
 
 std::string ScriptedFrameProviderPythonInterface::GetDescription(
     llvm::StringRef class_name) {
-  Status error;
-  StructuredData::ObjectSP obj =
-      CallStaticMethod(class_name, "get_description", error);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      CallStaticMethod(class_name, "get_description"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj->GetStringValue().str();
@@ -67,12 +64,9 @@ std::string ScriptedFrameProviderPythonInterface::GetDescription(
 
 std::optional<uint32_t>
 ScriptedFrameProviderPythonInterface::GetPriority(llvm::StringRef class_name) {
-  Status error;
-  StructuredData::ObjectSP obj =
-      CallStaticMethod(class_name, "get_priority", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      CallStaticMethod(class_name, "get_priority"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return std::nullopt;
 
   // Try to extract as unsigned integer. Return nullopt if Python returned None
@@ -85,11 +79,9 @@ ScriptedFrameProviderPythonInterface::GetPriority(llvm::StringRef class_name) {
 
 StructuredData::ObjectSP
 ScriptedFrameProviderPythonInterface::GetFrameAtIndex(uint32_t index) {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_frame_at_index", error, index);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      Dispatch("get_frame_at_index", index), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj;
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
index e4dc97852abbb..da74133915dca 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
@@ -11,6 +11,7 @@
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Host/Config.h"
 #include "lldb/Target/ExecutionContext.h"
+#include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/ValueObject/ValueObject.h"
 #include "lldb/lldb-enumerations.h"
@@ -41,56 +42,48 @@ ScriptedFramePythonInterface::CreatePluginObject(
 }
 
 lldb::user_id_t ScriptedFramePythonInterface::GetID() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_id", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_id"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return LLDB_INVALID_FRAME_ID;
 
   return obj->GetUnsignedIntegerValue(LLDB_INVALID_FRAME_ID);
 }
 
 lldb::addr_t ScriptedFramePythonInterface::GetPC() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_pc", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_pc"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return LLDB_INVALID_ADDRESS;
 
   return obj->GetUnsignedIntegerValue(LLDB_INVALID_ADDRESS);
 }
 
 lldb::addr_t ScriptedFramePythonInterface::GetCFA() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_cfa", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_cfa"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return LLDB_INVALID_ADDRESS;
 
   return obj->GetUnsignedIntegerValue(LLDB_INVALID_ADDRESS);
 }
 
 std::optional<SymbolContext> ScriptedFramePythonInterface::GetSymbolContext() {
-  Status error;
-  auto sym_ctx = Dispatch<SymbolContext>("get_symbol_context", error);
-
-  if (error.Fail()) {
-    return ErrorWithMessage<SymbolContext>(LLVM_PRETTY_FUNCTION,
-                                           error.AsCString(), error);
+  llvm::Expected<SymbolContext> sym_ctx_or_err =
+      Dispatch<SymbolContext>("get_symbol_context");
+  if (!sym_ctx_or_err) {
+    LLDB_LOG_ERROR(GetLog(LLDBLog::Script), sym_ctx_or_err.takeError(),
+                   "get_symbol_context failed: {0}");
+    return {};
   }
 
-  return sym_ctx;
+  return *sym_ctx_or_err;
 }
 
 std::optional<std::string> ScriptedFramePythonInterface::GetFunctionName() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_function_name", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_function_name"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj->GetStringValue().str();
@@ -98,145 +91,109 @@ std::optional<std::string> ScriptedFramePythonInterface::GetFunctionName() {
 
 std::optional<std::string>
 ScriptedFramePythonInterface::GetDisplayFunctionName() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_display_function_name", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      Dispatch("get_display_function_name"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj->GetStringValue().str();
 }
 
 bool ScriptedFramePythonInterface::IsInlined() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("is_inlined", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("is_inlined"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return false;
 
   return obj->GetBooleanValue();
 }
 
 bool ScriptedFramePythonInterface::IsArtificial() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("is_artificial", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("is_artificial"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return false;
 
   return obj->GetBooleanValue();
 }
 
 bool ScriptedFramePythonInterface::IsHidden() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("is_hidden", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("is_hidden"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return false;
 
   return obj->GetBooleanValue();
 }
 
 StructuredData::DictionarySP ScriptedFramePythonInterface::GetRegisterInfo() {
-  Status error;
   StructuredData::DictionarySP dict =
-      Dispatch<StructuredData::DictionarySP>("get_register_info", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::DictionarySP>("get_register_info"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!dict)
     return {};
 
   return dict;
 }
 
 std::optional<std::string> ScriptedFramePythonInterface::GetRegisterContext() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_register_context", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_register_context"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
-  return obj->GetAsString()->GetValue().str();
+  return obj->GetStringValue().str();
 }
 
 lldb::ValueObjectListSP ScriptedFramePythonInterface::GetVariables() {
-  Status error;
-  auto vals = Dispatch<lldb::ValueObjectListSP>("get_variables", error);
-
-  if (error.Fail()) {
-    return ErrorWithMessage<lldb::ValueObjectListSP>(LLVM_PRETTY_FUNCTION,
-                                                     error.AsCString(), error);
-  }
-
-  return vals;
+  return LogAndDefault(Dispatch<lldb::ValueObjectListSP>("get_variables"),
+                       LLVM_PRETTY_FUNCTION);
 }
 
 std::optional<lldb::ValueType>
 ScriptedFramePythonInterface::GetValueTypeForVariable(
     lldb::ValueObjectSP value) {
-  Status error;
-  auto val = Dispatch<std::optional<lldb::ValueType>>(
-      "get_value_type_for_variable", error, std::move(value));
-
-  if (error.Fail()) {
-    return ErrorWithMessage<std::optional<lldb::ValueType>>(
-        LLVM_PRETTY_FUNCTION, error.AsCString(), error);
-  }
-
-  return val;
+  return LogAndDefault(Dispatch<std::optional<lldb::ValueType>>(
+                           "get_value_type_for_variable", std::move(value)),
+                       LLVM_PRETTY_FUNCTION);
 }
 
 lldb::ValueObjectSP
 ScriptedFramePythonInterface::GetValueObjectForVariableExpression(
     llvm::StringRef expr, uint32_t options, Status &status) {
-  Status dispatch_error;
-  auto val = Dispatch<lldb::ValueObjectSP>("get_value_for_variable_expression",
-                                           dispatch_error, expr.data(), options,
-                                           status);
-
-  if (dispatch_error.Fail()) {
-    return ErrorWithMessage<lldb::ValueObjectSP>(
-        LLVM_PRETTY_FUNCTION, dispatch_error.AsCString(), dispatch_error);
+  llvm::Expected<lldb::ValueObjectSP> val_or_err =
+      Dispatch<lldb::ValueObjectSP>("get_value_for_variable_expression",
+                                    expr.data(), options, status);
+  if (!val_or_err) {
+    status = Status::FromError(val_or_err.takeError());
+    return {};
   }
 
-  return val;
+  return *val_or_err;
 }
 
 llvm::Expected<ScriptedMetadata>
 ScriptedFramePythonInterface::GetThreadPlanMetadataForStepType(
     lldb::StepType step_type) {
-  Status error;
-
   ScriptedMetadata no_plan_return("", StructuredData::DictionarySP());
-  StructuredData::DictionarySP dict_sp = Dispatch<StructuredData::DictionarySP>(
-      "get_plan_spec_for_step_type", error, step_type);
-  if (error.Fail()) {
-    // There are two cases here.  The `get_plan_spec_for_step_type` didn't
-    // exist, in which case we should return no_plan_return
-    // FIXME - Dispatch should distinguish between these two cases in a way
-    // that's more definitive than this.
-    llvm::StringRef err_str(error.AsCString());
-    if (err_str.contains(
-            "object has no attribute 'get_plan_spec_for_step_type'"))
-      return no_plan_return;
-    else
-      return llvm::createStringError(
-          "error dispatching get_plan_spec_for_step_type: %s",
-          error.AsCString());
-  }
+
+  // A frame that doesn't implement `get_plan_spec_for_step_type` simply has no
+  // plan to offer, which Dispatch reports as an UnimplementedError. Any other
+  // failure - notably an exception raised inside the method - propagates.
+  llvm::Expected<std::optional<StructuredData::DictionarySP>> dict_or_err =
+      DispatchToOptional<StructuredData::DictionarySP>(
+          "get_plan_spec_for_step_type", step_type);
+  if (!dict_or_err)
+    return llvm::joinErrors(
+        llvm::createStringError(
+            "error dispatching get_plan_spec_for_step_type"),
+        dict_or_err.takeError());
 
   // The return value is an StructuredData::Dictionary with the class name and
   // the extra args for the call:
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION,
-                                                    dict_sp, error))
-    return llvm::createStringError(
-        "return from get_plan_spec_for_step_type not a valid object: %s",
-        error.AsCString());
+  StructuredData::DictionarySP dict_sp = dict_or_err->value_or(nullptr);
+  if (!dict_sp || !dict_sp->IsValid())
+    return no_plan_return;
 
   StructuredData::ObjectSP obj = dict_sp->GetValueForKey("class_name");
   if (!obj)
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
index df4146657c53e..729ec97396e19 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
@@ -62,14 +62,16 @@ ScriptedHookPythonInterface::CreatePluginObject(
 
 void ScriptedHookPythonInterface::HandleModuleLoaded(
     lldb::StreamSP &output_sp) {
-  Status error;
-  Dispatch("handle_module_loaded", error, output_sp);
+  // This entry point has no error channel, so a failure can only be logged.
+  LogAndDefault(Dispatch("handle_module_loaded", output_sp),
+                LLVM_PRETTY_FUNCTION);
 }
 
 void ScriptedHookPythonInterface::HandleModuleUnloaded(
     lldb::StreamSP &output_sp) {
-  Status error;
-  Dispatch("handle_module_unloaded", error, output_sp);
+  // This entry point has no error channel, so a failure can only be logged.
+  LogAndDefault(Dispatch("handle_module_unloaded", output_sp),
+                LLVM_PRETTY_FUNCTION);
 }
 
 llvm::Expected<bool>
@@ -77,16 +79,15 @@ ScriptedHookPythonInterface::HandleStop(ExecutionContext &exe_ctx,
                                         lldb::StreamSP &output_sp) {
   ExecutionContextRefSP exe_ctx_ref_sp =
       std::make_shared<ExecutionContextRef>(exe_ctx);
-  Status error;
-  StructuredData::ObjectSP obj =
-      Dispatch("handle_stop", error, exe_ctx_ref_sp, output_sp);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
-    if (!obj)
-      return true;
-    return error.ToError();
-  }
+  llvm::Expected<std::optional<StructuredData::ObjectSP>> obj_or_err =
+      DispatchToOptional("handle_stop", exe_ctx_ref_sp, output_sp);
+  if (!obj_or_err)
+    return obj_or_err.takeError();
+
+  // Absent or None: the hook expressed no preference, so stay stopped.
+  StructuredData::ObjectSP obj = obj_or_err->value_or(nullptr);
+  if (!obj || !obj->IsValid())
+    return true;
 
   return obj->GetBooleanValue();
 }
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.cpp
index ff0237e788e75..230eb2d00368a 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.cpp
@@ -39,36 +39,22 @@ ScriptedPlatformPythonInterface::CreatePluginObject(
 }
 
 StructuredData::DictionarySP ScriptedPlatformPythonInterface::ListProcesses() {
-  Status error;
   StructuredData::DictionarySP dict_sp =
-      Dispatch<StructuredData::DictionarySP>("list_processes", error);
-
-  if (!dict_sp || !dict_sp->IsValid() || error.Fail()) {
-    return ScriptedInterface::ErrorWithMessage<StructuredData::DictionarySP>(
-        LLVM_PRETTY_FUNCTION,
-        llvm::Twine("Null or invalid object (" +
-                    llvm::Twine(error.AsCString()) + llvm::Twine(")."))
-            .str(),
-        error);
-  }
+      LogAndDefault(Dispatch<StructuredData::DictionarySP>("list_processes"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!dict_sp || !dict_sp->IsValid())
+    return {};
 
   return dict_sp;
 }
 
 StructuredData::DictionarySP
 ScriptedPlatformPythonInterface::GetProcessInfo(lldb::pid_t pid) {
-  Status error;
-  StructuredData::DictionarySP dict_sp =
-      Dispatch<StructuredData::DictionarySP>("get_process_info", error, pid);
-
-  if (!dict_sp || !dict_sp->IsValid() || error.Fail()) {
-    return ScriptedInterface::ErrorWithMessage<StructuredData::DictionarySP>(
-        LLVM_PRETTY_FUNCTION,
-        llvm::Twine("Null or invalid object (" +
-                    llvm::Twine(error.AsCString()) + llvm::Twine(")."))
-            .str(),
-        error);
-  }
+  StructuredData::DictionarySP dict_sp = LogAndDefault(
+      Dispatch<StructuredData::DictionarySP>("get_process_info", pid),
+      LLVM_PRETTY_FUNCTION);
+  if (!dict_sp || !dict_sp->IsValid())
+    return {};
 
   return dict_sp;
 }
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.cpp
index 8d5e7f4e80756..96189a9a2958c 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.cpp
@@ -42,12 +42,10 @@ ScriptedProcessPythonInterface::CreatePluginObject(
 }
 
 StructuredData::DictionarySP ScriptedProcessPythonInterface::GetCapabilities() {
-  Status error;
   StructuredData::DictionarySP dict =
-      Dispatch<StructuredData::DictionarySP>("get_capabilities", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::DictionarySP>("get_capabilities"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!dict)
     return {};
 
   return dict;
@@ -55,12 +53,10 @@ StructuredData::DictionarySP ScriptedProcessPythonInterface::GetCapabilities() {
 
 StructuredData::DictionarySP
 ScriptedProcessPythonInterface::GetAddressableBits() {
-  Status error;
-  StructuredData::DictionarySP dict =
-      Dispatch<StructuredData::DictionarySP>("get_addressable_bits", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict,
-                                                    error))
+  StructuredData::DictionarySP dict = LogAndDefault(
+      Dispatch<StructuredData::DictionarySP>("get_addressable_bits"),
+      LLVM_PRETTY_FUNCTION);
+  if (!dict)
     return {};
 
   return dict;
@@ -85,24 +81,22 @@ Status ScriptedProcessPythonInterface::Resume() {
 std::optional<MemoryRegionInfo>
 ScriptedProcessPythonInterface::GetMemoryRegionContainingAddress(
     lldb::addr_t address, Status &error) {
-  auto mem_region = Dispatch<std::optional<MemoryRegionInfo>>(
-      "get_memory_region_containing_address", error, address);
-
-  if (error.Fail()) {
-    return ErrorWithMessage<MemoryRegionInfo>(LLVM_PRETTY_FUNCTION,
-                                              error.AsCString(), error);
+  llvm::Expected<std::optional<MemoryRegionInfo>> mem_region_or_err =
+      Dispatch<std::optional<MemoryRegionInfo>>(
+          "get_memory_region_containing_address", address);
+  if (!mem_region_or_err) {
+    error = Status::FromError(mem_region_or_err.takeError());
+    return {};
   }
 
-  return mem_region;
+  return *mem_region_or_err;
 }
 
 StructuredData::DictionarySP ScriptedProcessPythonInterface::GetThreadsInfo() {
-  Status error;
   StructuredData::DictionarySP dict =
-      Dispatch<StructuredData::DictionarySP>("get_threads_info", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::DictionarySP>("get_threads_info"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!dict)
     return {};
 
   return dict;
@@ -110,16 +104,16 @@ StructuredData::DictionarySP ScriptedProcessPythonInterface::GetThreadsInfo() {
 
 bool ScriptedProcessPythonInterface::CreateBreakpoint(lldb::addr_t addr,
                                                       Status &error) {
-  Status py_error;
-  StructuredData::ObjectSP obj =
-      Dispatch("create_breakpoint", py_error, addr, error);
-
+  llvm::Expected<StructuredData::ObjectSP> obj_or_err =
+      Dispatch("create_breakpoint", addr, error);
   // If there was an error on the python call, surface it to the user.
-  if (py_error.Fail())
-    error = std::move(py_error);
+  if (!obj_or_err) {
+    error = Status::FromError(obj_or_err.takeError());
+    return {};
+  }
 
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = *obj_or_err;
+  if (!obj || !obj->IsValid())
     return {};
 
   return obj->GetBooleanValue();
@@ -127,63 +121,58 @@ bool ScriptedProcessPythonInterface::CreateBreakpoint(lldb::addr_t addr,
 
 lldb::DataExtractorSP ScriptedProcessPythonInterface::ReadMemoryAtAddress(
     lldb::addr_t address, size_t size, Status &error) {
-  Status py_error;
-  lldb::DataExtractorSP data_sp = Dispatch<lldb::DataExtractorSP>(
-      "read_memory_at_address", py_error, address, size, error);
-
+  llvm::Expected<lldb::DataExtractorSP> data_or_err =
+      Dispatch<lldb::DataExtractorSP>("read_memory_at_address", address, size,
+                                      error);
   // If there was an error on the python call, surface it to the user.
-  if (py_error.Fail())
-    error = std::move(py_error);
+  if (!data_or_err) {
+    error = Status::FromError(data_or_err.takeError());
+    return {};
+  }
 
-  return data_sp;
+  return *data_or_err;
 }
 
 lldb::offset_t ScriptedProcessPythonInterface::WriteMemoryAtAddress(
     lldb::addr_t addr, lldb::DataExtractorSP data_sp, Status &error) {
-  Status py_error;
-  StructuredData::ObjectSP obj =
-      Dispatch("write_memory_at_address", py_error, addr, data_sp, error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  llvm::Expected<StructuredData::ObjectSP> obj_or_err =
+      Dispatch("write_memory_at_address", addr, data_sp, error);
+  // If there was an error on the python call, surface it to the user.
+  if (!obj_or_err) {
+    error = Status::FromError(obj_or_err.takeError());
     return LLDB_INVALID_OFFSET;
+  }
 
-  // If there was an error on the python call, surface it to the user.
-  if (py_error.Fail())
-    error = std::move(py_error);
+  StructuredData::ObjectSP obj = *obj_or_err;
+  if (!obj || !obj->IsValid())
+    return LLDB_INVALID_OFFSET;
 
   return obj->GetUnsignedIntegerValue(LLDB_INVALID_OFFSET);
 }
 
 StructuredData::ArraySP ScriptedProcessPythonInterface::GetLoadedImages() {
-  Status error;
   StructuredData::ArraySP array =
-      Dispatch<StructuredData::ArraySP>("get_loaded_images", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, array,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::ArraySP>("get_loaded_images"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!array)
     return {};
 
   return array;
 }
 
 lldb::pid_t ScriptedProcessPythonInterface::GetProcessID() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_process_id", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_process_id"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return LLDB_INVALID_PROCESS_ID;
 
   return obj->GetUnsignedIntegerValue(LLDB_INVALID_PROCESS_ID);
 }
 
 bool ScriptedProcessPythonInterface::IsAlive() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("is_alive", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("is_alive"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj->GetBooleanValue();
@@ -191,11 +180,9 @@ bool ScriptedProcessPythonInterface::IsAlive() {
 
 std::optional<std::string>
 ScriptedProcessPythonInterface::GetScriptedThreadPluginName() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_scripted_thread_plugin", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      Dispatch("get_scripted_thread_plugin"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj->GetStringValue().str();
@@ -207,12 +194,10 @@ ScriptedProcessPythonInterface::CreateScriptedThreadInterface() {
 }
 
 StructuredData::DictionarySP ScriptedProcessPythonInterface::GetMetadata() {
-  Status error;
-  StructuredData::DictionarySP dict =
-      Dispatch<StructuredData::DictionarySP>("get_process_metadata", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict,
-                                                    error))
+  StructuredData::DictionarySP dict = LogAndDefault(
+      Dispatch<StructuredData::DictionarySP>("get_process_metadata"),
+      LLVM_PRETTY_FUNCTION);
+  if (!dict)
     return {};
 
   return dict;
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index fe87b2b1f1e60..9fbef2f206c67 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -29,335 +29,302 @@ ScriptedPythonInterface::ScriptedPythonInterface(
     : ScriptedInterface(), m_interpreter(interpreter) {}
 
 template <>
-StructuredData::ArraySP
+llvm::Expected<StructuredData::ArraySP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<StructuredData::ArraySP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   python::PythonList result_list(python::PyRefType::Borrowed, p.get());
   return result_list.CreateStructuredArray();
 }
 
 template <>
-StructuredData::DictionarySP
+llvm::Expected<StructuredData::DictionarySP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    StructuredData::DictionarySP>(python::PythonObject &p, Status &error) {
+    StructuredData::DictionarySP>(python::PythonObject &p) {
   python::PythonDictionary result_dict(python::PyRefType::Borrowed, p.get());
   return result_dict.CreateStructuredDictionary();
 }
 
 template <>
-Status ScriptedPythonInterface::ExtractValueFromPythonObject<Status>(
-    python::PythonObject &p, Status &error) {
+llvm::Expected<Status>
+ScriptedPythonInterface::ExtractValueFromPythonObject<Status>(
+    python::PythonObject &p) {
   if (lldb::SBError *sb_error = reinterpret_cast<lldb::SBError *>(
           python::LLDBSWIGPython_CastPyObjectToSBError(p.get())))
     return ScriptInterpreterBridge::GetStatus(*sb_error);
-  error =
-      Status::FromErrorString("Couldn't cast lldb::SBError to lldb::Status.");
-
-  return {};
+  return llvm::createStringError(
+      "couldn't cast lldb::SBError to lldb::Status");
 }
 
 template <>
-Event *ScriptedPythonInterface::ExtractValueFromPythonObject<Event *>(
-    python::PythonObject &p, Status &error) {
+llvm::Expected<Event *>
+ScriptedPythonInterface::ExtractValueFromPythonObject<Event *>(
+    python::PythonObject &p) {
   if (lldb::SBEvent *sb_event = reinterpret_cast<lldb::SBEvent *>(
           python::LLDBSWIGPython_CastPyObjectToSBEvent(p.get())))
     return ScriptInterpreterBridge::GetEvent(*sb_event);
-  error = Status::FromErrorString(
-      "Couldn't cast lldb::SBEvent to lldb_private::Event.");
-
-  return nullptr;
+  return llvm::createStringError(
+      "couldn't cast lldb::SBEvent to lldb_private::Event");
 }
 
 template <>
-CommandReturnObject *
+llvm::Expected<CommandReturnObject *>
 ScriptedPythonInterface::ExtractValueFromPythonObject<CommandReturnObject *>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   if (lldb::SBCommandReturnObject *sb_cmd_retobj =
           reinterpret_cast<lldb::SBCommandReturnObject *>(
               python::LLDBSWIGPython_CastPyObjectToSBCommandReturnObject(
                   p.get())))
     return ScriptInterpreterBridge::GetCommandReturnObject(*sb_cmd_retobj);
-  error =
-      Status::FromErrorString("couldn't cast lldb::SBCommandReturnObject to "
-                              "lldb_private::CommandReturnObject.");
-  return nullptr;
+  return llvm::createStringError("couldn't cast lldb::SBCommandReturnObject to "
+                                 "lldb_private::CommandReturnObject");
 }
 
 template <>
-lldb::StreamSP
+llvm::Expected<lldb::StreamSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   if (lldb::SBStream *sb_stream = reinterpret_cast<lldb::SBStream *>(
           python::LLDBSWIGPython_CastPyObjectToSBStream(p.get())))
     return ScriptInterpreterBridge::GetStream(*sb_stream);
-  error = Status::FromErrorString(
-      "Couldn't cast lldb::SBStream to lldb_private::Stream.");
-
-  return nullptr;
+  return llvm::createStringError(
+      "couldn't cast lldb::SBStream to lldb_private::Stream");
 }
 
 template <>
-lldb::StackFrameSP
+llvm::Expected<lldb::StackFrameSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   if (lldb::SBFrame *sb_frame = reinterpret_cast<lldb::SBFrame *>(
           python::LLDBSWIGPython_CastPyObjectToSBFrame(p.get())))
     return ScriptInterpreterBridge::GetStackFrame(*sb_frame);
-  error = Status::FromErrorString(
-      "Couldn't cast lldb::SBFrame to lldb_private::StackFrame.");
-
-  return nullptr;
+  return llvm::createStringError(
+      "couldn't cast lldb::SBFrame to lldb_private::StackFrame");
 }
 
 template <>
-lldb::ThreadSP
+llvm::Expected<lldb::ThreadSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   if (lldb::SBThread *sb_thread = reinterpret_cast<lldb::SBThread *>(
           python::LLDBSWIGPython_CastPyObjectToSBThread(p.get())))
     return ScriptInterpreterBridge::GetThread(*sb_thread);
-  error = Status::FromErrorString(
-      "Couldn't cast lldb::SBThread to lldb_private::Thread.");
-
-  return nullptr;
+  return llvm::createStringError(
+      "couldn't cast lldb::SBThread to lldb_private::Thread");
 }
 
 template <>
-SymbolContext
+llvm::Expected<SymbolContext>
 ScriptedPythonInterface::ExtractValueFromPythonObject<SymbolContext>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   if (lldb::SBSymbolContext *sb_symbol_context =
           reinterpret_cast<lldb::SBSymbolContext *>(
               python::LLDBSWIGPython_CastPyObjectToSBSymbolContext(p.get())))
     return ScriptInterpreterBridge::GetSymbolContext(*sb_symbol_context);
-  error = Status::FromErrorString(
-      "Couldn't cast lldb::SBSymbolContext to lldb_private::SymbolContext.");
-
-  return {};
+  return llvm::createStringError(
+      "couldn't cast lldb::SBSymbolContext to lldb_private::SymbolContext");
 }
 
 template <>
-lldb::DataExtractorSP
+llvm::Expected<lldb::DataExtractorSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DataExtractorSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   lldb::SBData *sb_data = reinterpret_cast<lldb::SBData *>(
       python::LLDBSWIGPython_CastPyObjectToSBData(p.get()));
 
   if (!sb_data) {
-    error = Status::FromErrorStringWithFormat(
-        "Couldn't cast lldb::SBData to lldb::DataExtractorSP.");
-    return nullptr;
+    return llvm::createStringError(
+        "couldn't cast lldb::SBData to lldb::DataExtractorSP");
   }
 
   return ScriptInterpreterBridge::GetDataExtractor(*sb_data);
 }
 
 template <>
-lldb::BreakpointSP
+llvm::Expected<lldb::BreakpointSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   lldb::SBBreakpoint *sb_breakpoint = reinterpret_cast<lldb::SBBreakpoint *>(
       python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(p.get()));
 
   if (!sb_breakpoint) {
-    error = Status::FromErrorStringWithFormat(
-        "Couldn't cast lldb::SBBreakpoint to lldb::BreakpointSP.");
-    return nullptr;
+    return llvm::createStringError(
+        "couldn't cast lldb::SBBreakpoint to lldb::BreakpointSP");
   }
 
   return ScriptInterpreterBridge::GetBreakpoint(*sb_breakpoint);
 }
 
 template <>
-lldb::BreakpointLocationSP
+llvm::Expected<lldb::BreakpointLocationSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::BreakpointLocationSP>(python::PythonObject &p, Status &error) {
+    lldb::BreakpointLocationSP>(python::PythonObject &p) {
   lldb::SBBreakpointLocation *sb_break_loc =
       reinterpret_cast<lldb::SBBreakpointLocation *>(
           python::LLDBSWIGPython_CastPyObjectToSBBreakpointLocation(p.get()));
 
   if (!sb_break_loc) {
-    error = Status::FromErrorStringWithFormat(
-        "Couldn't cast lldb::SBBreakpointLocation to "
-        "lldb::BreakpointLocationSP.");
-    return nullptr;
+    return llvm::createStringError(
+        "couldn't cast lldb::SBBreakpointLocation to "
+        "lldb::BreakpointLocationSP");
   }
 
   return ScriptInterpreterBridge::GetBreakpointLocation(*sb_break_loc);
 }
 
 template <>
-lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error) {
+llvm::Expected<lldb::ProcessAttachInfoSP>
+ScriptedPythonInterface::ExtractValueFromPythonObject<
+    lldb::ProcessAttachInfoSP>(python::PythonObject &p) {
   lldb::SBAttachInfo *sb_attach_info = reinterpret_cast<lldb::SBAttachInfo *>(
       python::LLDBSWIGPython_CastPyObjectToSBAttachInfo(p.get()));
 
   if (!sb_attach_info) {
-    error = Status::FromErrorStringWithFormat(
-        "Couldn't cast lldb::SBAttachInfo to lldb::ProcessAttachInfoSP.");
-    return nullptr;
+    return llvm::createStringError(
+        "couldn't cast lldb::SBAttachInfo to lldb::ProcessAttachInfoSP");
   }
 
   return ScriptInterpreterBridge::GetProcessAttachInfo(*sb_attach_info);
 }
 
 template <>
-lldb::ProcessLaunchInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::ProcessLaunchInfoSP>(python::PythonObject &p, Status &error) {
+llvm::Expected<lldb::ProcessLaunchInfoSP>
+ScriptedPythonInterface::ExtractValueFromPythonObject<
+    lldb::ProcessLaunchInfoSP>(python::PythonObject &p) {
   lldb::SBLaunchInfo *sb_launch_info = reinterpret_cast<lldb::SBLaunchInfo *>(
       python::LLDBSWIGPython_CastPyObjectToSBLaunchInfo(p.get()));
 
   if (!sb_launch_info) {
-    error = Status::FromErrorStringWithFormat(
-        "Couldn't cast lldb::SBLaunchInfo to lldb::ProcessLaunchInfoSP.");
-    return nullptr;
+    return llvm::createStringError(
+        "couldn't cast lldb::SBLaunchInfo to lldb::ProcessLaunchInfoSP");
   }
 
   return ScriptInterpreterBridge::GetProcessLaunchInfo(*sb_launch_info);
 }
 
 template <>
-lldb::ThreadPlanSP
+llvm::Expected<lldb::ThreadPlanSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadPlanSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   lldb::SBThreadPlan *sb_thread_plan = reinterpret_cast<lldb::SBThreadPlan *>(
       python::LLDBSWIGPython_CastPyObjectToSBThreadPlan(p.get()));
 
   if (!sb_thread_plan) {
-    error = Status::FromErrorStringWithFormat(
-        "Couldn't cast lldb::SBThreadPlan to lldb::ThreadPlanSP.");
-    return {};
+    return llvm::createStringError(
+        "couldn't cast lldb::SBThreadPlan to lldb::ThreadPlanSP");
   }
 
   return ScriptInterpreterBridge::GetThreadPlan(*sb_thread_plan);
 }
 
 template <>
-std::optional<MemoryRegionInfo>
+llvm::Expected<std::optional<MemoryRegionInfo>>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    std::optional<MemoryRegionInfo>>(python::PythonObject &p, Status &error) {
+    std::optional<MemoryRegionInfo>>(python::PythonObject &p) {
 
   lldb::SBMemoryRegionInfo *sb_mem_reg_info =
       reinterpret_cast<lldb::SBMemoryRegionInfo *>(
           python::LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(p.get()));
 
   if (!sb_mem_reg_info) {
-    error = Status::FromErrorStringWithFormat(
-        "Couldn't cast lldb::SBMemoryRegionInfo to "
-        "lldb_private::MemoryRegionInfo.");
-    return {};
+    return llvm::createStringError("couldn't cast lldb::SBMemoryRegionInfo to "
+                                   "lldb_private::MemoryRegionInfo");
   }
 
   return ScriptInterpreterBridge::GetMemoryRegionInfo(*sb_mem_reg_info);
 }
 
 template <>
-lldb::ExecutionContextRefSP
+llvm::Expected<lldb::ExecutionContextRefSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::ExecutionContextRefSP>(python::PythonObject &p, Status &error) {
+    lldb::ExecutionContextRefSP>(python::PythonObject &p) {
 
   lldb::SBExecutionContext *sb_exe_ctx =
       reinterpret_cast<lldb::SBExecutionContext *>(
           python::LLDBSWIGPython_CastPyObjectToSBExecutionContext(p.get()));
 
   if (!sb_exe_ctx) {
-    error = Status::FromErrorStringWithFormat(
-        "Couldn't cast lldb::SBExecutionContext to "
-        "lldb::ExecutionContextRefSP.");
-    return {};
+    return llvm::createStringError("couldn't cast lldb::SBExecutionContext to "
+                                   "lldb::ExecutionContextRefSP");
   }
 
   return ScriptInterpreterBridge::GetExecutionContextRef(*sb_exe_ctx);
 }
 
 template <>
-lldb::DescriptionLevel
+llvm::Expected<lldb::DescriptionLevel>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>(
-    python::PythonObject &p, Status &error) {
-  lldb::DescriptionLevel ret_val = lldb::eDescriptionLevelBrief;
+    python::PythonObject &p) {
   llvm::Expected<unsigned long long> unsigned_or_err = p.AsUnsignedLongLong();
-  if (!unsigned_or_err) {
-    error = (Status::FromError(unsigned_or_err.takeError()));
-    return ret_val;
-  }
+  if (!unsigned_or_err)
+    return unsigned_or_err.takeError();
   unsigned long long unsigned_val = *unsigned_or_err;
-  if (unsigned_val >= lldb::DescriptionLevel::kNumDescriptionLevels) {
-    error = Status("value too large for lldb::DescriptionLevel.");
-    return ret_val;
-  }
+  if (unsigned_val >= lldb::DescriptionLevel::kNumDescriptionLevels)
+    return llvm::createStringError(
+        "value too large for lldb::DescriptionLevel");
   return static_cast<lldb::DescriptionLevel>(unsigned_val);
 }
 
 template <>
-lldb::StepType
+llvm::Expected<lldb::StepType>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StepType>(
-    python::PythonObject &p, Status &error) {
-  lldb::StepType ret_val = lldb::eStepTypeNone;
-
+    python::PythonObject &p) {
   llvm::Expected<unsigned long long> unsigned_or_err = p.AsUnsignedLongLong();
-  if (!unsigned_or_err) {
-    error = (Status::FromError(unsigned_or_err.takeError()));
-    return ret_val;
-  }
+  if (!unsigned_or_err)
+    return unsigned_or_err.takeError();
   unsigned long long unsigned_val = *unsigned_or_err;
-  if (unsigned_val >= lldb::eStepTypeScripted) {
-    error = Status("value too large for lldb::StepType.");
-    return ret_val;
-  }
+  if (unsigned_val >= lldb::eStepTypeScripted)
+    return llvm::createStringError("value too large for lldb::StepType");
   return static_cast<lldb::StepType>(unsigned_val);
 }
 
 template <>
-lldb::StackFrameListSP
+llvm::Expected<lldb::StackFrameListSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
 
   lldb::SBFrameList *sb_frame_list = reinterpret_cast<lldb::SBFrameList *>(
       python::LLDBSWIGPython_CastPyObjectToSBFrameList(p.get()));
 
   if (!sb_frame_list) {
-    error = Status::FromErrorStringWithFormat(
-        "couldn't cast lldb::SBFrameList to lldb::StackFrameListSP.");
-    return {};
+    return llvm::createStringError(
+        "couldn't cast lldb::SBFrameList to lldb::StackFrameListSP");
   }
 
   return ScriptInterpreterBridge::GetStackFrameList(*sb_frame_list);
 }
 
 template <>
-lldb::ValueObjectSP
+llvm::Expected<lldb::ValueObjectSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   lldb::SBValue *sb_value = reinterpret_cast<lldb::SBValue *>(
       python::LLDBSWIGPython_CastPyObjectToSBValue(p.get()));
   if (!sb_value) {
-    error = Status::FromErrorStringWithFormat(
+    return llvm::createStringError(
         "couldn't cast lldb::SBValue to lldb::ValueObjectSP");
-    return {};
   }
 
   return ScriptInterpreterBridge::GetValueObject(*sb_value);
 }
 
 template <>
-lldb::TargetSP
+llvm::Expected<lldb::TargetSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::TargetSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   lldb::SBTarget *sb_target = reinterpret_cast<lldb::SBTarget *>(
       python::LLDBSWIGPython_CastPyObjectToSBTarget(p.get()));
   if (!sb_target) {
-    error = Status::FromErrorStringWithFormat(
+    return llvm::createStringError(
         "couldn't cast lldb::SBTarget to lldb::TargetSP");
-    return {};
   }
 
   return ScriptInterpreterBridge::GetTarget(*sb_target);
 }
 
 template <>
-lldb::ValueObjectListSP
+llvm::Expected<lldb::ValueObjectListSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   // Two Python return shapes are accepted here so callers can go through
   // Dispatch<ValueObjectListSP>() uniformly: an `SBValueList` wrapper
   // (what most extension methods return) and a plain Python `list` of
@@ -381,16 +348,20 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>(
   StructuredData::Array *arr = structured ? structured->GetAsArray() : nullptr;
   if (arr) {
     size_t index = 0;
-    bool aborted = false;
+    llvm::Error extract_error = llvm::Error::success();
     arr->ForEach([&](StructuredData::Object *item) {
+      const size_t item_index = index++;
       StructuredData::Generic *generic = item ? item->GetAsGeneric() : nullptr;
       if (!generic) {
-        error = Status::FromErrorStringWithFormatv(
-            "ValueObjectList item at index {0} is not a "
-            "StructuredData::Generic",
-            index);
-        aborted = true;
-        return false;
+        // Keep walking the list so a malformed one names every bad item
+        // rather than only the first.
+        extract_error =
+            llvm::joinErrors(std::move(extract_error),
+                             llvm::createStringError(llvm::formatv(
+                                 "ValueObjectList item at index {0} is not a "
+                                 "StructuredData::Generic",
+                                 item_index)));
+        return true;
       }
       auto *sb_value = reinterpret_cast<lldb::SBValue *>(
           python::LLDBSWIGPython_CastPyObjectToSBValue(
@@ -398,64 +369,56 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>(
       if (sb_value)
         if (auto valobj_sp = ScriptInterpreterBridge::GetValueObject(*sb_value))
           out->Append(valobj_sp);
-      ++index;
       return true;
     });
-    if (aborted)
-      return {};
+    if (extract_error)
+      return std::move(extract_error);
     return out;
   }
 
-  error = Status::FromErrorStringWithFormat(
+  return llvm::createStringError(
       "couldn't extract ValueObjectList from Python return value");
-  return {};
 }
 
 template <>
-std::optional<lldb::ValueType>
+llvm::Expected<std::optional<lldb::ValueType>>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    std::optional<lldb::ValueType>>(python::PythonObject &p, Status &error) {
+    std::optional<lldb::ValueType>>(python::PythonObject &p) {
   if (p.IsNone())
     return std::nullopt;
 
   llvm::Expected<unsigned long long> val = p.AsUnsignedLongLong();
-  if (!val) {
-    error = Status::FromError(val.takeError());
-    return std::nullopt;
-  }
+  if (!val)
+    return val.takeError();
   unsigned long long unmasked = *val & ~kValueTypeFlagsMask;
   unsigned long long flags = *val & kValueTypeFlagsMask;
-  if (unmasked == eValueTypeInvalid || unmasked > kLastValueType) {
-    error = Status::FromErrorStringWithFormatv(
-        "value type invalid or too large (got {0} | {1:x})", unmasked, flags);
-    return std::nullopt;
-  }
+  if (unmasked == eValueTypeInvalid || unmasked > kLastValueType)
+    return llvm::createStringError(llvm::formatv(
+        "value type invalid or too large (got {0} | {1:x})", unmasked, flags));
 
   return static_cast<ValueType>(unmasked | flags);
 }
 
 template <>
-lldb::DebuggerSP
+llvm::Expected<lldb::DebuggerSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DebuggerSP>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   if (lldb::SBDebugger *sb_dbg = reinterpret_cast<lldb::SBDebugger *>(
           python::LLDBSWIGPython_CastPyObjectToSBDebugger(p.get())))
     return ScriptInterpreterBridge::GetDebugger(*sb_dbg);
-  error = Status::FromErrorString(
-      "couldn't cast lldb::SBDebugger to lldb::DebuggerSP.");
-  return {};
+  return llvm::createStringError(
+      "couldn't cast lldb::SBDebugger to lldb::DebuggerSP");
 }
 
 template <>
-std::vector<std::string>
+llvm::Expected<std::vector<std::string>>
 ScriptedPythonInterface::ExtractValueFromPythonObject<std::vector<std::string>>(
-    python::PythonObject &p, Status &error) {
+    python::PythonObject &p) {
   std::vector<std::string> result;
   python::PythonList list(python::PyRefType::Borrowed, p.get());
   if (!list.IsValid()) {
-    error = Status::FromErrorString(
-        "couldn't extract std::vector<std::string>: not a Python list.");
-    return result;
+    return llvm::createStringError(
+        "couldn't extract std::vector<std::string>: not a Python list");
   }
 
   const uint32_t size = list.GetSize();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index f3510c2d6c3f2..4d1a5f0411d10 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -190,11 +190,11 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
         !(llvm::StringRef(m_interpreter.GetDictionaryName()).empty());
     if (!has_class_name && !has_interpreter_dict && !script_obj) {
       if (!has_class_name)
-        return create_error("Missing script class name.");
+        return create_error("missing script class name");
       else if (!has_interpreter_dict)
-        return create_error("Invalid script interpreter dictionary.");
+        return create_error("invalid script interpreter dictionary");
       else
-        return create_error("Missing scripting object.");
+        return create_error("missing scripting object");
     }
 
     std::optional<PolicyStack::Guard> policy_guard;
@@ -214,14 +214,14 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
           PythonModule::MainModule().ResolveName<python::PythonDictionary>(
               m_interpreter.GetDictionaryName());
       if (!dict.IsAllocated())
-        return create_error("Could not find interpreter dictionary: {0}",
+        return create_error("could not find interpreter dictionary: {0}",
                             m_interpreter.GetDictionaryName());
 
       auto init =
           PythonObject::ResolveNameWithDictionary<python::PythonCallable>(
               class_name, dict);
       if (!init.IsAllocated())
-        return create_error("Could not find script class: {0}",
+        return create_error("could not find script class: {0}",
                             class_name.data());
 
       std::tuple<Args...> original_args = std::forward_as_tuple(args...);
@@ -241,7 +241,7 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
       }
 
       llvm::Expected<PythonObject> expected_return_object =
-          create_error("Resulting object is not initialized.");
+          create_error("resulting object is not initialized");
 
       // This relax the requirement on the number of argument for
       // initializing scripting extension if the size of the interface
@@ -263,8 +263,8 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
           // before we return with a different error, or its destructor
           // will abort.
           llvm::consumeError(expected_return_object.takeError());
-          return create_error("Passed arguments ({0}) doesn't match the number "
-                              "of expected arguments ({1}).",
+          return create_error("passed arguments ({0}) doesn't match the number "
+                              "of expected arguments ({1})",
                               num_args, arg_info->max_positional_args);
         }
 
@@ -296,16 +296,16 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
     }
 
     if (!result.IsValid())
-      return create_error("Resulting object is not a valid Python Object.");
+      return create_error("resulting object is not a valid Python Object");
     if (!result.HasAttribute("__class__"))
-      return create_error("Resulting object doesn't have '__class__' member.");
+      return create_error("resulting object doesn't have '__class__' member");
 
     PythonObject obj_class = result.GetAttributeValue("__class__");
     if (!obj_class.IsValid())
-      return create_error("Resulting class object is not a valid.");
+      return create_error("resulting class object is not a valid");
     if (!obj_class.HasAttribute("__name__"))
       return create_error(
-          "Resulting object class doesn't have '__name__' member.");
+          "resulting object class doesn't have '__name__' member");
     PythonString obj_class_name =
         obj_class.GetAttributeValue("__name__").AsType<PythonString>();
 
@@ -319,21 +319,21 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
       case AbstractMethodCheckerCases::eNotImplemented:
         abstract_method_errors = llvm::joinErrors(
             std::move(abstract_method_errors),
-            std::move(create_error("Abstract method {0}.{1} not implemented.",
+            std::move(create_error("abstract method {0}.{1} not implemented",
                                    obj_class_name.GetString(),
                                    method_checker.first)));
         break;
       case AbstractMethodCheckerCases::eNotAllocated:
         abstract_method_errors = llvm::joinErrors(
             std::move(abstract_method_errors),
-            std::move(create_error("Abstract method {0}.{1} not allocated.",
+            std::move(create_error("abstract method {0}.{1} not allocated",
                                    obj_class_name.GetString(),
                                    method_checker.first)));
         break;
       case AbstractMethodCheckerCases::eNotCallable:
         abstract_method_errors = llvm::joinErrors(
             std::move(abstract_method_errors),
-            std::move(create_error("Abstract method {0}.{1} not callable.",
+            std::move(create_error("abstract method {0}.{1} not callable",
                                    obj_class_name.GetString(),
                                    method_checker.first)));
         break;
@@ -355,7 +355,7 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
           abstract_method_errors = llvm::joinErrors(
               std::move(abstract_method_errors),
               std::move(create_error(
-                  "Abstract method {0}.{1} has unexpected argument count.",
+                  "abstract method {0}.{1} has unexpected argument count",
                   obj_class_name.GetString(), method_checker.first)));
         } else {
           auto payload = std::get<
@@ -364,8 +364,8 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
           abstract_method_errors = llvm::joinErrors(
               std::move(abstract_method_errors),
               std::move(
-                  create_error("Abstract method {0}.{1} has unexpected "
-                               "argument count (expected {2} but has {3}).",
+                  create_error("abstract method {0}.{1} has unexpected "
+                               "argument count (expected {2} but has {3})",
                                obj_class_name.GetString(), method_checker.first,
                                payload.required_argument_count,
                                payload.actual_argument_count)));
@@ -397,14 +397,13 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
   ///
   /// \param class_name The fully-qualified name of the Python class.
   /// \param method_name The name of the static method to call.
-  /// \param error Output parameter to receive error information if the call
-  /// fails.
   /// \param args Arguments to pass to the static method.
   ///
-  /// \return The return value of the static method call, or an error value.
+  /// \return The return value of the static method call, or an error.
   template <typename T = StructuredData::ObjectSP, typename... Args>
-  T CallStaticMethod(llvm::StringRef class_name, llvm::StringRef method_name,
-                     Status &error, Args &&...args) {
+  llvm::Expected<T> CallStaticMethod(llvm::StringRef class_name,
+                                     llvm::StringRef method_name,
+                                     Args &&...args) {
     using namespace python;
     using Locker = ScriptInterpreterPythonImpl::Locker;
 
@@ -415,8 +414,7 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
             .str();
 
     if (class_name.empty())
-      return ErrorWithMessage<T>(caller_signature, "missing script class name",
-                                 error);
+      return LogAndError(caller_signature, "missing script class name");
 
     std::optional<PolicyStack::Guard> policy_guard;
     if (!UserCanRunDirectly())
@@ -430,40 +428,28 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
         PythonModule::MainModule().ResolveName<python::PythonDictionary>(
             m_interpreter.GetDictionaryName());
     if (!dict.IsAllocated())
-      return ErrorWithMessage<T>(
-          caller_signature,
-          llvm::formatv("could not find interpreter dictionary: {0}",
-                        m_interpreter.GetDictionaryName())
-              .str(),
-          error);
+      return LogAndError(caller_signature,
+                         "could not find interpreter dictionary: {0}",
+                         m_interpreter.GetDictionaryName());
 
     // Resolve the class.
     auto class_obj =
         PythonObject::ResolveNameWithDictionary<python::PythonCallable>(
             class_name, dict);
     if (!class_obj.IsAllocated())
-      return ErrorWithMessage<T>(
-          caller_signature,
-          llvm::formatv("could not find script class: {0}", class_name).str(),
-          error);
+      return LogAndError(caller_signature, "could not find script class: {0}",
+                         class_name);
 
     // Get the static method from the class.
     if (!class_obj.HasAttribute(method_name))
-      return ErrorWithMessage<T>(
-          caller_signature,
-          llvm::formatv("class {0} does not have method {1}", class_name,
-                        method_name)
-              .str(),
-          error);
+      return llvm::make_error<UnimplementedError>(
+          llvm::formatv("{0}.{1}", class_name, method_name).str());
 
     PythonCallable method =
         class_obj.GetAttributeValue(method_name).AsType<PythonCallable>();
     if (!method.IsAllocated())
-      return ErrorWithMessage<T>(caller_signature,
-                                 llvm::formatv("method {0}.{1} is not callable",
-                                               class_name, method_name)
-                                     .str(),
-                                 error);
+      return LogAndError(caller_signature, "method {0}.{1} is not callable",
+                         class_name, method_name);
 
     // Transform the arguments.
     std::tuple<Args...> original_args = std::forward_as_tuple(args...);
@@ -486,14 +472,9 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
       // `read_memory_at_address(0x500000000, 4)`). Requires a SFINAE
       // helper that falls back to a placeholder for types without a
       // format_provider / operator<<.
-      error = Status::FromErrorString(ExtractPythonError(std::move(e)).c_str());
-
-      return ErrorWithMessage<T>(
-          caller_signature,
-          llvm::formatv("python exception in {0} method '{1}'", class_name,
-                        method_name)
-              .str(),
-          error);
+      return LogAndError(
+          caller_signature, "python exception in {0} method '{1}': {2}",
+          class_name, method_name, ExtractPythonError(std::move(e)));
     }
 
     PythonObject py_return = std::move(expected_return_object.get());
@@ -501,12 +482,14 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
     // Re-assign reference and pointer arguments if needed.
     if (sizeof...(Args) > 0)
       if (!ReassignPtrsOrRefsArgs(original_args, transformed_args))
-        return ErrorWithMessage<T>(
+        return LogAndError(
             caller_signature,
-            "couldn't re-assign reference and pointer arguments", error);
+            "couldn't re-assign reference and pointer arguments");
 
     // Extract value from Python object (handles unallocated case).
-    return ExtractValueFromPythonObject<T>(py_return, error);
+    if (!py_return.IsAllocated())
+      return T{};
+    return ExtractValueFromPythonObject<T>(py_return);
   }
 
 protected:
@@ -527,13 +510,84 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
     return error_msg;
   }
 
+  /// Log \a message against \a caller_name and return it as an error.
+  ///
+  /// The `Expected`-returning counterpart of
+  /// `ScriptedInterface::ErrorWithMessage`: it reports the failure instead of
+  /// folding it into a default-constructed value.
+  template <typename... Ts>
+  static llvm::Error LogAndError(llvm::StringRef caller_name,
+                                 const char *format, Ts &&...ts) {
+    std::string message = llvm::formatv(format, std::forward<Ts>(ts)...).str();
+    LLDB_LOGF(GetLog(LLDBLog::Script), "%s ERROR = %s", caller_name.data(),
+              message.c_str());
+    return llvm::createStringError(message);
+  }
+
+  /// Log the failure in \a value_or_err and fall back to a default-constructed
+  /// \c T.
+  ///
+  /// For entry points whose return type cannot express failure. Prefer
+  /// propagating the error wherever the signature can carry it; this is the
+  /// stop-gap, and it is at least strictly better than dropping the error.
+  ///
+  /// Every failure is logged, including an unimplemented method: a call site
+  /// that hasn't declared the callback optional - by going through
+  /// DispatchToOptional - has no business staying quiet about a method it
+  /// expected to find.
+  template <typename T>
+  static T LogAndDefault(llvm::Expected<T> value_or_err,
+                         llvm::StringRef caller) {
+    if (value_or_err)
+      return std::move(*value_or_err);
+
+    // Consume the error before logging: LLDB_LOGF doesn't evaluate its
+    // arguments when the channel is disabled, which would leave the error
+    // unchecked and abort.
+    std::string message = llvm::toString(value_or_err.takeError());
+    LLDB_LOGF(GetLog(LLDBLog::Script), "%s failed: %s", caller.str().c_str(),
+              message.c_str());
+    return T{};
+  }
+
+  /// Call an optional \a method_name, reporting "the script doesn't implement
+  /// it" as \c std::nullopt rather than as a failure.
+  ///
+  /// Use this for callbacks a script may legitimately leave out. Any other
+  /// failure - in particular an exception raised inside the method -
+  /// propagates; this never papers over a method that ran and failed.
+  template <typename T = StructuredData::ObjectSP, typename... Args>
+  llvm::Expected<std::optional<T>>
+  DispatchToOptional(llvm::StringRef method_name, Args &&...args) {
+    llvm::Expected<T> value_or_err =
+        Dispatch<T>(method_name, std::forward<Args>(args)...);
+    if (value_or_err)
+      return std::move(*value_or_err);
+
+    if (value_or_err.template errorIsA<UnimplementedError>()) {
+      llvm::consumeError(value_or_err.takeError());
+      return std::nullopt;
+    }
+    return value_or_err.takeError();
+  }
+
   template <typename T = StructuredData::ObjectSP>
-  T ExtractValueFromPythonObject(python::PythonObject &p, Status &error) {
+  llvm::Expected<T> ExtractValueFromPythonObject(python::PythonObject &p) {
     return p.CreateStructuredObject();
   }
 
+  /// Call \a method_name on the scripted object.
+  ///
+  /// The returned \c Expected separates the two outcomes that a plain return
+  /// value cannot: a successfully extracted value (which may legitimately be
+  /// empty or null, e.g. when the Python method returns \c None) and a
+  /// failure to call the method at all.
+  ///
+  /// A class that simply doesn't implement \a method_name fails with an
+  /// \c UnimplementedError, so callers of optional callbacks can recognize
+  /// and ignore that case without also swallowing real exceptions.
   template <typename T = StructuredData::ObjectSP, typename... Args>
-  T Dispatch(llvm::StringRef method_name, Status &error, Args &&...args) {
+  llvm::Expected<T> Dispatch(llvm::StringRef method_name, Args &&...args) {
     using namespace python;
     using Locker = ScriptInterpreterPythonImpl::Locker;
 
@@ -542,8 +596,7 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
                     llvm::Twine(method_name) + llvm::Twine(")"))
             .str();
     if (!m_object_instance_sp)
-      return ErrorWithMessage<T>(caller_signature, "python object ill-formed",
-                                 error);
+      return LogAndError(caller_signature, "python object ill-formed");
 
     std::optional<PolicyStack::Guard> policy_guard;
     if (!UserCanRunDirectly())
@@ -556,11 +609,17 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
                              (PyObject *)m_object_instance_sp->GetValue());
 
     if (!implementor.IsAllocated())
-      return llvm::is_contained(GetAbstractMethods(), method_name)
-                 ? ErrorWithMessage<T>(caller_signature,
-                                       "python implementor not allocated",
-                                       error)
-                 : T{};
+      return LogAndError(caller_signature, "python implementor not allocated");
+
+    PythonObject py_method = implementor.GetAttributeValue(method_name);
+    if (!py_method.IsAllocated())
+      return llvm::make_error<UnimplementedError>(
+          llvm::formatv("{0}.{1}",
+                        GetScriptedMetadata()
+                            ? GetScriptedMetadata()->GetClassName()
+                            : "<unknown>",
+                        method_name)
+              .str());
 
     std::tuple<Args...> original_args = std::forward_as_tuple(args...);
     auto transformed_args = TransformArgs(original_args);
@@ -569,19 +628,16 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
     // parameters than we're passing (e.g. `num_children(self)` vs.
     // `num_children(self, max_count)`).
     size_t call_arity = sizeof...(Args);
-    if (PythonObject py_method = implementor.GetAttributeValue(method_name);
-        py_method.IsAllocated()) {
-      PythonCallable callable = py_method.AsType<PythonCallable>();
-      if (callable.IsAllocated()) {
-        if (llvm::Expected<PythonCallable::ArgInfo> arg_info =
-                callable.GetArgInfo()) {
-          if (arg_info->max_positional_args !=
-                  PythonCallable::ArgInfo::UNBOUNDED &&
-              arg_info->max_positional_args < call_arity)
-            call_arity = arg_info->max_positional_args;
-        } else {
-          llvm::consumeError(arg_info.takeError());
-        }
+    if (PythonCallable callable = py_method.AsType<PythonCallable>();
+        callable.IsAllocated()) {
+      if (llvm::Expected<PythonCallable::ArgInfo> arg_info =
+              callable.GetArgInfo()) {
+        if (arg_info->max_positional_args !=
+                PythonCallable::ArgInfo::UNBOUNDED &&
+            arg_info->max_positional_args < call_arity)
+          call_arity = arg_info->max_positional_args;
+      } else {
+        llvm::consumeError(arg_info.takeError());
       }
     }
 
@@ -603,17 +659,15 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
       // `read_memory_at_address(0x500000000, 4)`). Requires a SFINAE
       // helper that falls back to a placeholder for types without a
       // format_provider / operator<<.
-      error = Status::FromErrorString(ExtractPythonError(std::move(e)).c_str());
-
-      return ErrorWithMessage<T>(
-          caller_signature,
-          llvm::formatv("python exception in {0} method '{1}'",
-                        GetScriptedMetadata()
-                            ? GetScriptedMetadata()->GetClassName()
-                            : "<unknown>",
-                        method_name)
-              .str(),
-          error);
+      //
+      // Drain the Python exception into a plain string while the GIL is
+      // still held: `PythonException` owns raw `PyObject*` references, and
+      // `py_lock` is released as this function returns.
+      return LogAndError(
+          caller_signature, "python exception in {0} method '{1}': {2}",
+          GetScriptedMetadata() ? GetScriptedMetadata()->GetClassName()
+                                : "<unknown>",
+          method_name, ExtractPythonError(std::move(e)));
     }
 
     PythonObject py_return = std::move(expected_return_object.get());
@@ -624,21 +678,26 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
     // the original parameter pack as references or pointers.
     if (sizeof...(Args) > 0)
       if (!ReassignPtrsOrRefsArgs(original_args, transformed_args))
-        return ErrorWithMessage<T>(
+        return LogAndError(
             caller_signature,
-            "couldn't re-assign reference and pointer arguments", error);
+            "couldn't re-assign reference and pointer arguments");
 
     if (!py_return.IsAllocated())
-      return {};
-    return ExtractValueFromPythonObject<T>(py_return, error);
+      return T{};
+    return ExtractValueFromPythonObject<T>(py_return);
   }
 
+  /// Call \a method_name and fold both failure channels into one `Status`:
+  /// a failure to call the method at all, and the `SBError` the method
+  /// returned. The latter used to be dropped on the floor.
   template <typename... Args>
   Status GetStatusFromMethod(llvm::StringRef method_name, Args &&...args) {
-    Status error;
-    Dispatch<Status>(method_name, error, std::forward<Args>(args)...);
+    llvm::Expected<Status> status_or_err =
+        Dispatch<Status>(method_name, std::forward<Args>(args)...);
+    if (!status_or_err)
+      return Status::FromError(status_or_err.takeError());
 
-    return error;
+    return std::move(*status_or_err);
   }
 
   template <typename T> T Transform(T object) {
@@ -769,7 +828,13 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
   template <typename T>
   void ReverseTransform(T &original_arg, python::PythonObject transformed_arg,
                         Status &error) {
-    original_arg = ExtractValueFromPythonObject<T>(transformed_arg, error);
+    llvm::Expected<T> value_or_err =
+        ExtractValueFromPythonObject<T>(transformed_arg);
+    if (!value_or_err) {
+      error = Status::FromError(value_or_err.takeError());
+      return;
+    }
+    original_arg = std::move(*value_or_err);
   }
 
   // Read-only arguments (passed as `const T&`) have nothing to write back:
@@ -880,125 +945,129 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
 };
 
 template <>
-StructuredData::ArraySP
+llvm::Expected<StructuredData::ArraySP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<StructuredData::ArraySP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-StructuredData::DictionarySP
+llvm::Expected<StructuredData::DictionarySP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    StructuredData::DictionarySP>(python::PythonObject &p, Status &error);
+    StructuredData::DictionarySP>(python::PythonObject &p);
 
 template <>
-Status ScriptedPythonInterface::ExtractValueFromPythonObject<Status>(
-    python::PythonObject &p, Status &error);
+llvm::Expected<Status>
+ScriptedPythonInterface::ExtractValueFromPythonObject<Status>(
+    python::PythonObject &p);
 
 template <>
-Event *ScriptedPythonInterface::ExtractValueFromPythonObject<Event *>(
-    python::PythonObject &p, Status &error);
+llvm::Expected<Event *>
+ScriptedPythonInterface::ExtractValueFromPythonObject<Event *>(
+    python::PythonObject &p);
 
 template <>
-SymbolContext
+llvm::Expected<SymbolContext>
 ScriptedPythonInterface::ExtractValueFromPythonObject<SymbolContext>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::StreamSP
+llvm::Expected<lldb::StreamSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::ThreadSP
+llvm::Expected<lldb::ThreadSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::StackFrameSP
+llvm::Expected<lldb::StackFrameSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::BreakpointSP
+llvm::Expected<lldb::BreakpointSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::BreakpointLocationSP
+llvm::Expected<lldb::BreakpointLocationSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::BreakpointLocationSP>(python::PythonObject &p, Status &error);
+    lldb::BreakpointLocationSP>(python::PythonObject &p);
 
 template <>
-lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error);
+llvm::Expected<lldb::ProcessAttachInfoSP>
+ScriptedPythonInterface::ExtractValueFromPythonObject<
+    lldb::ProcessAttachInfoSP>(python::PythonObject &p);
 
 template <>
-lldb::ProcessLaunchInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::ProcessLaunchInfoSP>(python::PythonObject &p, Status &error);
+llvm::Expected<lldb::ProcessLaunchInfoSP>
+ScriptedPythonInterface::ExtractValueFromPythonObject<
+    lldb::ProcessLaunchInfoSP>(python::PythonObject &p);
 
 template <>
-lldb::DataExtractorSP
+llvm::Expected<lldb::DataExtractorSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DataExtractorSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::ThreadPlanSP
+llvm::Expected<lldb::ThreadPlanSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadPlanSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-std::optional<MemoryRegionInfo>
+llvm::Expected<std::optional<MemoryRegionInfo>>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    std::optional<MemoryRegionInfo>>(python::PythonObject &p, Status &error);
+    std::optional<MemoryRegionInfo>>(python::PythonObject &p);
 
 template <>
-lldb::ExecutionContextRefSP
+llvm::Expected<lldb::ExecutionContextRefSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::ExecutionContextRefSP>(python::PythonObject &p, Status &error);
+    lldb::ExecutionContextRefSP>(python::PythonObject &p);
 
 template <>
-lldb::DescriptionLevel
+llvm::Expected<lldb::DescriptionLevel>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::StepType
+llvm::Expected<lldb::StepType>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StepType>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::StackFrameListSP
+llvm::Expected<lldb::StackFrameListSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::ValueObjectSP
+llvm::Expected<lldb::ValueObjectSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::TargetSP
+llvm::Expected<lldb::TargetSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::TargetSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-lldb::ValueObjectListSP
+llvm::Expected<lldb::ValueObjectListSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-std::optional<lldb::ValueType>
+llvm::Expected<std::optional<lldb::ValueType>>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
-    std::optional<lldb::ValueType>>(python::PythonObject &p, Status &error);
+    std::optional<lldb::ValueType>>(python::PythonObject &p);
 
 template <>
-lldb::DebuggerSP
+llvm::Expected<lldb::DebuggerSP>
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DebuggerSP>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 template <>
-std::vector<std::string>
+llvm::Expected<std::vector<std::string>>
 ScriptedPythonInterface::ExtractValueFromPythonObject<std::vector<std::string>>(
-    python::PythonObject &p, Status &error);
+    python::PythonObject &p);
 
 } // namespace lldb_private
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
index 7cdfc99a6b4c8..07b7e4c241618 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
@@ -39,18 +39,16 @@ ScriptedStackFrameRecognizerPythonInterface::CreatePluginObject(
 lldb::ValueObjectListSP
 ScriptedStackFrameRecognizerPythonInterface::GetRecognizedArguments(
     lldb::StackFrameSP frame_sp) {
-  Status error;
-  return Dispatch<lldb::ValueObjectListSP>("get_recognized_arguments", error,
-                                           frame_sp);
+  return LogAndDefault(
+      Dispatch<lldb::ValueObjectListSP>("get_recognized_arguments", frame_sp),
+      LLVM_PRETTY_FUNCTION);
 }
 
 bool ScriptedStackFrameRecognizerPythonInterface::ShouldHide(
     lldb::StackFrameSP frame_sp) {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("should_hide", error, frame_sp);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("should_hide", frame_sp), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return false;
 
   return obj->GetBooleanValue();
@@ -59,41 +57,38 @@ bool ScriptedStackFrameRecognizerPythonInterface::ShouldHide(
 lldb::StackFrameSP
 ScriptedStackFrameRecognizerPythonInterface::SelectMostRelevantFrame(
     lldb::StackFrameSP frame_sp) {
-  Status error;
-  return Dispatch<lldb::StackFrameSP>("select_most_relevant_frame", error,
-                                      frame_sp);
+  return LogAndDefault(
+      Dispatch<lldb::StackFrameSP>("select_most_relevant_frame", frame_sp),
+      LLVM_PRETTY_FUNCTION);
 }
 
 lldb::ValueObjectSP ScriptedStackFrameRecognizerPythonInterface::GetException(
     lldb::StackFrameSP frame_sp) {
-  Status error;
-  return Dispatch<lldb::ValueObjectSP>("get_exception", error, frame_sp);
+  return LogAndDefault(Dispatch<lldb::ValueObjectSP>("get_exception", frame_sp),
+                       LLVM_PRETTY_FUNCTION);
 }
 
 std::string ScriptedStackFrameRecognizerPythonInterface::GetStopDescription(
     lldb::StackFrameSP frame_sp) {
-  Status error;
-  StructuredData::ObjectSP obj =
-      Dispatch("get_stop_description", error, frame_sp);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      Dispatch("get_stop_description", frame_sp), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return "";
+
   return obj->GetStringValue().str();
 }
 
 lldb::ThreadPlanSP
 ScriptedStackFrameRecognizerPythonInterface::GetStepThroughPlan(
     lldb::ThreadSP thread_sp) {
-  Status error;
-  StructuredData::DictionarySP dict_sp = Dispatch<StructuredData::DictionarySP>(
-      "get_step_through_plan", error, thread_sp);
-  if (error.Fail())
-    return {};
+  StructuredData::DictionarySP dict_sp =
+      LogAndDefault(Dispatch<StructuredData::DictionarySP>(
+                        "get_step_through_plan", thread_sp),
+                    LLVM_PRETTY_FUNCTION);
 
   // The return value is an StructuredData::Dictionary with the class name and
   // the extra args for the call:
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION,
-                                                    dict_sp, error))
+  if (!dict_sp || !dict_sp->IsValid())
     return {};
 
   StructuredData::ObjectSP obj = dict_sp->GetValueForKey("class_name");
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStringSummaryPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStringSummaryPythonInterface.cpp
index b0efa7615af96..2dda34e7520d5 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStringSummaryPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStringSummaryPythonInterface.cpp
@@ -35,12 +35,15 @@ ScriptedStringSummaryPythonInterface::CreatePluginObject(
 
 llvm::Expected<std::string> ScriptedStringSummaryPythonInterface::GetSummary(
     ValueObject &valobj, const TypeSummaryOptions &options) {
-  Status error;
-  StructuredData::ObjectSP obj =
-      Dispatch("get_summary", error, valobj.GetSP(), options);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
-    return error.ToError();
+  llvm::Expected<StructuredData::ObjectSP> obj_or_err =
+      Dispatch("get_summary", valobj.GetSP(), options);
+  if (!obj_or_err)
+    return obj_or_err.takeError();
+
+  StructuredData::ObjectSP obj = *obj_or_err;
+  if (!obj || !obj->IsValid())
+    return llvm::createStringError("get_summary returned no summary string");
+
   return obj->GetStringValue().str();
 }
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp
index 4e45111fafc1c..93729ed6ccc39 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedSyntheticChildrenPythonInterface.cpp
@@ -57,10 +57,15 @@ ScriptedSyntheticChildrenPythonInterface::CreatePluginObject(
 
 llvm::Expected<uint32_t>
 ScriptedSyntheticChildrenPythonInterface::CalculateNumChildren(uint32_t max) {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("num_children", error, max);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  // This interface requires no abstract methods, so a provider that doesn't
+  // implement `num_children` simply has no children rather than being broken.
+  llvm::Expected<std::optional<StructuredData::ObjectSP>> obj_or_err =
+      DispatchToOptional("num_children", max);
+  if (!obj_or_err)
+    return obj_or_err.takeError();
+
+  StructuredData::ObjectSP obj = obj_or_err->value_or(nullptr);
+  if (!obj || !obj->IsValid())
     return 0;
   // Cap at max in case the provider ignores the argument (e.g. defines
   // `num_children(self)`) and returns an unbounded count.
@@ -69,18 +74,22 @@ ScriptedSyntheticChildrenPythonInterface::CalculateNumChildren(uint32_t max) {
 
 lldb::ValueObjectSP
 ScriptedSyntheticChildrenPythonInterface::GetChildAtIndex(uint32_t idx) {
-  Status error;
-  return Dispatch<lldb::ValueObjectSP>("get_child_at_index", error, idx);
+  return LogAndDefault(Dispatch<lldb::ValueObjectSP>("get_child_at_index", idx),
+                       LLVM_PRETTY_FUNCTION);
 }
 
 llvm::Expected<uint32_t>
 ScriptedSyntheticChildrenPythonInterface::GetIndexOfChildWithName(
     ConstString name) {
-  Status error;
-  StructuredData::ObjectSP obj =
-      Dispatch("get_child_index", error, name.GetCString());
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  // A provider without `get_child_index` has no child of that name, which is
+  // a friendlier answer than "the method is missing".
+  llvm::Expected<std::optional<StructuredData::ObjectSP>> obj_or_err =
+      DispatchToOptional("get_child_index", name.GetCString());
+  if (!obj_or_err)
+    return obj_or_err.takeError();
+
+  StructuredData::ObjectSP obj = obj_or_err->value_or(nullptr);
+  if (!obj || !obj->IsValid())
     return llvm::createStringErrorV("type has no child named '{0}'", name);
 
   // `CreateStructuredObject` only produces a `SignedInteger` for values that
@@ -93,37 +102,35 @@ ScriptedSyntheticChildrenPythonInterface::GetIndexOfChildWithName(
 }
 
 lldb::ChildCacheState ScriptedSyntheticChildrenPythonInterface::Update() {
-  Status error;
   // update() is optional; a missing method means "always refetch".
-  StructuredData::ObjectSP obj = Dispatch("update", error);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("update"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return lldb::eRefetch;
   return obj->GetBooleanValue() ? lldb::eReuse : lldb::eRefetch;
 }
 
 bool ScriptedSyntheticChildrenPythonInterface::MightHaveChildren() {
-  Status error;
   // has_children() is optional and defaults to True when missing.
-  StructuredData::ObjectSP obj = Dispatch("has_children", error);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("has_children"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return true;
   return obj->GetBooleanValue();
 }
 
 lldb::ValueObjectSP
 ScriptedSyntheticChildrenPythonInterface::GetSyntheticValue() {
-  Status error;
-  return Dispatch<lldb::ValueObjectSP>("get_value", error);
+  return LogAndDefault(Dispatch<lldb::ValueObjectSP>("get_value"),
+                       LLVM_PRETTY_FUNCTION);
 }
 
 ConstString ScriptedSyntheticChildrenPythonInterface::GetSyntheticTypeName() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_type_name", error);
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_type_name"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
+
   return ConstString(obj->GetStringValue());
 }
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp
index 2d0f6fd9d9fe9..21002513ae342 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.cpp
@@ -38,54 +38,57 @@ ScriptedThreadPlanPythonInterface::CreatePluginObject(
 
 llvm::Expected<bool>
 ScriptedThreadPlanPythonInterface::ExplainsStop(Event *event) {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("explains_stop", error, event);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
-    if (!obj)
-      return false;
-    return error.ToError();
-  }
+  llvm::Expected<std::optional<StructuredData::ObjectSP>> obj_or_err =
+      DispatchToOptional("explains_stop", event);
+  if (!obj_or_err)
+    return obj_or_err.takeError();
+
+  // Absent or None: the plan doesn't claim to explain the stop.
+  StructuredData::ObjectSP obj = obj_or_err->value_or(nullptr);
+  if (!obj || !obj->IsValid())
+    return false;
 
   return obj->GetBooleanValue();
 }
 
 llvm::Expected<bool>
 ScriptedThreadPlanPythonInterface::ShouldStop(Event *event) {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("should_stop", error, event);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
-    if (!obj)
-      return false;
-    return error.ToError();
-  }
+  llvm::Expected<std::optional<StructuredData::ObjectSP>> obj_or_err =
+      DispatchToOptional("should_stop", event);
+  if (!obj_or_err)
+    return obj_or_err.takeError();
+
+  // Absent or None: the plan doesn't ask to stop.
+  StructuredData::ObjectSP obj = obj_or_err->value_or(nullptr);
+  if (!obj || !obj->IsValid())
+    return false;
 
   return obj->GetBooleanValue();
 }
 
 llvm::Expected<bool> ScriptedThreadPlanPythonInterface::IsStale() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("is_stale", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error)) {
-    if (!obj)
-      return false;
-    return error.ToError();
-  }
+  llvm::Expected<std::optional<StructuredData::ObjectSP>> obj_or_err =
+      DispatchToOptional("is_stale");
+  if (!obj_or_err)
+    return obj_or_err.takeError();
+
+  // Absent or None: assume the plan is still fresh.
+  StructuredData::ObjectSP obj = obj_or_err->value_or(nullptr);
+  if (!obj || !obj->IsValid())
+    return false;
 
   return obj->GetBooleanValue();
 }
 
-lldb::StateType ScriptedThreadPlanPythonInterface::GetRunState() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("should_step", error);
+llvm::Expected<lldb::StateType>
+ScriptedThreadPlanPythonInterface::GetRunState() {
+  llvm::Expected<std::optional<StructuredData::ObjectSP>> obj_or_err =
+      DispatchToOptional("should_step");
+  if (!obj_or_err)
+    return obj_or_err.takeError();
 
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = obj_or_err->value_or(nullptr);
+  if (!obj || !obj->IsValid())
     return lldb::eStateStepping;
 
   // A thread plan's run state can formally be eStateSuspended, but that state
@@ -105,13 +108,7 @@ lldb::StateType ScriptedThreadPlanPythonInterface::GetRunState() {
 
 llvm::Error
 ScriptedThreadPlanPythonInterface::GetStopDescription(lldb::StreamSP &stream) {
-  Status error;
-  Dispatch("stop_description", error, stream);
-
-  if (error.Fail())
-    return error.ToError();
-
-  return llvm::Error::success();
+  return DispatchToOptional("stop_description", stream).takeError();
 }
 
 void ScriptedThreadPlanPythonInterface::Initialize() {
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.h
index efb3225b36df7..0ed92efd62de9 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPlanPythonInterface.h
@@ -37,7 +37,7 @@ class ScriptedThreadPlanPythonInterface : public ScriptedThreadPlanInterface,
 
   llvm::Expected<bool> IsStale() override;
 
-  lldb::StateType GetRunState() override;
+  llvm::Expected<lldb::StateType> GetRunState() override;
 
   llvm::Error GetStopDescription(lldb::StreamSP &stream) override;
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.cpp
index 4abb52182961d..cc012a85679ec 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.cpp
@@ -40,103 +40,85 @@ ScriptedThreadPythonInterface::CreatePluginObject(
 }
 
 lldb::tid_t ScriptedThreadPythonInterface::GetThreadID() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_thread_id", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_thread_id"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return LLDB_INVALID_THREAD_ID;
 
   return obj->GetUnsignedIntegerValue(LLDB_INVALID_THREAD_ID);
 }
 
 std::optional<std::string> ScriptedThreadPythonInterface::GetName() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_name", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_name"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj->GetStringValue().str();
 }
 
 lldb::StateType ScriptedThreadPythonInterface::GetState() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_state", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_state"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return eStateInvalid;
 
   return static_cast<StateType>(obj->GetUnsignedIntegerValue(eStateInvalid));
 }
 
 std::optional<std::string> ScriptedThreadPythonInterface::GetQueue() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_queue", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_queue"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj->GetStringValue().str();
 }
 
 StructuredData::DictionarySP ScriptedThreadPythonInterface::GetStopReason() {
-  Status error;
   StructuredData::DictionarySP dict =
-      Dispatch<StructuredData::DictionarySP>("get_stop_reason", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::DictionarySP>("get_stop_reason"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!dict)
     return {};
 
   return dict;
 }
 
 StructuredData::ArraySP ScriptedThreadPythonInterface::GetStackFrames() {
-  Status error;
   StructuredData::ArraySP arr =
-      Dispatch<StructuredData::ArraySP>("get_stackframes", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, arr,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::ArraySP>("get_stackframes"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!arr)
     return {};
 
   return arr;
 }
 
 StructuredData::DictionarySP ScriptedThreadPythonInterface::GetRegisterInfo() {
-  Status error;
   StructuredData::DictionarySP dict =
-      Dispatch<StructuredData::DictionarySP>("get_register_info", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::DictionarySP>("get_register_info"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!dict)
     return {};
 
   return dict;
 }
 
 std::optional<std::string> ScriptedThreadPythonInterface::GetRegisterContext() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_register_context", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj =
+      LogAndDefault(Dispatch("get_register_context"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
-  return obj->GetAsString()->GetValue().str();
+  return obj->GetStringValue().str();
 }
 
 StructuredData::ArraySP ScriptedThreadPythonInterface::GetExtendedInfo() {
-  Status error;
   StructuredData::ArraySP arr =
-      Dispatch<StructuredData::ArraySP>("get_extended_info", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, arr,
-                                                    error))
+      LogAndDefault(Dispatch<StructuredData::ArraySP>("get_extended_info"),
+                    LLVM_PRETTY_FUNCTION);
+  if (!arr)
     return {};
 
   return arr;
@@ -144,11 +126,9 @@ StructuredData::ArraySP ScriptedThreadPythonInterface::GetExtendedInfo() {
 
 std::optional<std::string>
 ScriptedThreadPythonInterface::GetScriptedFramePluginName() {
-  Status error;
-  StructuredData::ObjectSP obj = Dispatch("get_scripted_frame_plugin", error);
-
-  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
-                                                    error))
+  StructuredData::ObjectSP obj = LogAndDefault(
+      Dispatch("get_scripted_frame_plugin"), LLVM_PRETTY_FUNCTION);
+  if (!obj)
     return {};
 
   return obj->GetStringValue().str();
diff --git a/lldb/source/Target/ScriptedThreadPlan.cpp b/lldb/source/Target/ScriptedThreadPlan.cpp
index 517076d4eaeb0..b44c12b9afe2f 100644
--- a/lldb/source/Target/ScriptedThreadPlan.cpp
+++ b/lldb/source/Target/ScriptedThreadPlan.cpp
@@ -173,8 +173,16 @@ lldb::StateType ScriptedThreadPlan::GetPlanRunState() {
   Log *log = GetLog(LLDBLog::Thread);
   LLDB_LOG(log, "called on Scripted Thread Plan: {0}", GetScriptClassName());
   lldb::StateType run_state = eStateRunning;
-  if (m_implementation_sp)
-    run_state = m_interface->GetRunState();
+  if (m_implementation_sp) {
+    llvm::Expected<lldb::StateType> run_state_or_err =
+        m_interface->GetRunState();
+    if (!run_state_or_err) {
+      LLDB_LOG_ERROR(log, run_state_or_err.takeError(),
+                     "Can't call ScriptedThreadPlan::GetRunState: {0}");
+      return run_state;
+    }
+    run_state = *run_state_or_err;
+  }
   return run_state;
 }
 
diff --git a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
index 8e91781b87a39..16e1e7b0828e8 100644
--- a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
+++ b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
@@ -43,7 +43,7 @@ def test_bad_handler(self):
         self.interp.HandleCommand(command, result)
         self.assertFalse(result.Succeeded(), "Set the target stop hook")
         self.assertIn(
-            "Abstract method no_handle_stop.handle_stop not implemented",
+            "abstract method no_handle_stop.handle_stop not implemented",
             result.GetError(),
             "Got the right error",
         )
diff --git a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
index 10419931b451e..4f0206f7de009 100644
--- a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
+++ b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
@@ -101,15 +101,15 @@ def cleanup():
             log = f.read()
 
         self.assertIn(
-            "Abstract method MissingMethodsScriptedProcess.read_memory_at_address not implemented",
+            "abstract method MissingMethodsScriptedProcess.read_memory_at_address not implemented",
             log,
         )
         self.assertIn(
-            "Abstract method MissingMethodsScriptedProcess.is_alive not implemented",
+            "abstract method MissingMethodsScriptedProcess.is_alive not implemented",
             log,
         )
         self.assertIn(
-            "Abstract method MissingMethodsScriptedProcess.get_scripted_thread_plugin not implemented",
+            "abstract method MissingMethodsScriptedProcess.get_scripted_thread_plugin not implemented",
             log,
         )
 



More information about the lldb-commits mailing list