[Mlir-commits] [mlir] [mlir][py] Mark all type caster `from_{cpp, python}` methods as noexcept (PR #143866)

Nicholas Junge llvmlistbot at llvm.org
Fri Jul 11 03:21:55 PDT 2025


https://github.com/nicholasjng updated https://github.com/llvm/llvm-project/pull/143866

>From 35d5b50a0f6b8d41780108f93cefc1788f6da84b Mon Sep 17 00:00:00 2001
From: Nicholas Junge <nicholas.junge at web.de>
Date: Thu, 12 Jun 2025 11:40:42 +0200
Subject: [PATCH 1/4] [mlir][py] Mark all type caster `from_{cpp,python}`
 methods as noexcept

This is mentioned as a "must" in https://nanobind.readthedocs.io/en/latest/porting.html#type-casters when implementing type casters.

While most of the existing `from_cpp` methods were already marked noexcept, many of the `from_python` methods were not.
This commit adds the missing noexcept declarations to all type casters found in `NanobindAdaptors.h`.
---
 .../mlir/Bindings/Python/NanobindAdaptors.h   | 29 ++++++++++---------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
index 2dd35c097c796..e39b1a752f8d6 100644
--- a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
+++ b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
@@ -67,7 +67,7 @@ static nanobind::object mlirApiObjectToCapsule(nanobind::handle apiObject) {
 template <>
 struct type_caster<MlirAffineMap> {
   NB_TYPE_CASTER(MlirAffineMap, const_name("MlirAffineMap"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToAffineMap(capsule.ptr());
     if (mlirAffineMapIsNull(value)) {
@@ -90,7 +90,7 @@ struct type_caster<MlirAffineMap> {
 template <>
 struct type_caster<MlirAttribute> {
   NB_TYPE_CASTER(MlirAttribute, const_name("MlirAttribute"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToAttribute(capsule.ptr());
     return !mlirAttributeIsNull(value);
@@ -111,7 +111,7 @@ struct type_caster<MlirAttribute> {
 template <>
 struct type_caster<MlirBlock> {
   NB_TYPE_CASTER(MlirBlock, const_name("MlirBlock"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToBlock(capsule.ptr());
     return !mlirBlockIsNull(value);
@@ -122,7 +122,7 @@ struct type_caster<MlirBlock> {
 template <>
 struct type_caster<MlirContext> {
   NB_TYPE_CASTER(MlirContext, const_name("MlirContext"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (src.is_none()) {
       // Gets the current thread-bound context.
       // TODO: This raises an error of "No current context" currently.
@@ -142,7 +142,7 @@ struct type_caster<MlirContext> {
 template <>
 struct type_caster<MlirDialectRegistry> {
   NB_TYPE_CASTER(MlirDialectRegistry, const_name("MlirDialectRegistry"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToDialectRegistry(capsule.ptr());
     return !mlirDialectRegistryIsNull(value);
@@ -162,7 +162,7 @@ struct type_caster<MlirDialectRegistry> {
 template <>
 struct type_caster<MlirLocation> {
   NB_TYPE_CASTER(MlirLocation, const_name("MlirLocation"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (src.is_none()) {
       // Gets the current thread-bound context.
       src = nanobind::module_::import_(MAKE_MLIR_PYTHON_QUALNAME("ir"))
@@ -188,7 +188,7 @@ struct type_caster<MlirLocation> {
 template <>
 struct type_caster<MlirModule> {
   NB_TYPE_CASTER(MlirModule, const_name("MlirModule"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToModule(capsule.ptr());
     return !mlirModuleIsNull(value);
@@ -209,12 +209,13 @@ template <>
 struct type_caster<MlirFrozenRewritePatternSet> {
   NB_TYPE_CASTER(MlirFrozenRewritePatternSet,
                  const_name("MlirFrozenRewritePatternSet"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToFrozenRewritePatternSet(capsule.ptr());
     return value.ptr != nullptr;
   }
-  static handle from_cpp(MlirFrozenRewritePatternSet v, rv_policy, handle) {
+  static handle from_cpp(MlirFrozenRewritePatternSet v, rv_policy,
+                         handle) noexcept {
     nanobind::object capsule = nanobind::steal<nanobind::object>(
         mlirPythonFrozenRewritePatternSetToCapsule(v));
     return nanobind::module_::import_(MAKE_MLIR_PYTHON_QUALNAME("rewrite"))
@@ -228,7 +229,7 @@ struct type_caster<MlirFrozenRewritePatternSet> {
 template <>
 struct type_caster<MlirOperation> {
   NB_TYPE_CASTER(MlirOperation, const_name("MlirOperation"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToOperation(capsule.ptr());
     return !mlirOperationIsNull(value);
@@ -250,7 +251,7 @@ struct type_caster<MlirOperation> {
 template <>
 struct type_caster<MlirValue> {
   NB_TYPE_CASTER(MlirValue, const_name("MlirValue"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToValue(capsule.ptr());
     return !mlirValueIsNull(value);
@@ -273,7 +274,7 @@ struct type_caster<MlirValue> {
 template <>
 struct type_caster<MlirPassManager> {
   NB_TYPE_CASTER(MlirPassManager, const_name("MlirPassManager"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToPassManager(capsule.ptr());
     return !mlirPassManagerIsNull(value);
@@ -284,7 +285,7 @@ struct type_caster<MlirPassManager> {
 template <>
 struct type_caster<MlirTypeID> {
   NB_TYPE_CASTER(MlirTypeID, const_name("MlirTypeID"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToTypeID(capsule.ptr());
     return !mlirTypeIDIsNull(value);
@@ -306,7 +307,7 @@ struct type_caster<MlirTypeID> {
 template <>
 struct type_caster<MlirType> {
   NB_TYPE_CASTER(MlirType, const_name("MlirType"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToType(capsule.ptr());
     return !mlirTypeIsNull(value);

>From d7beef99f0a4ecc3287ac1c744ea0eb0ffb416b3 Mon Sep 17 00:00:00 2001
From: Nicholas Junge <nicholas.junge at web.de>
Date: Thu, 10 Jul 2025 16:32:14 +0200
Subject: [PATCH 2/4] refactor: Change `mlirApiObjectToCapsule` to return
 std::optional

Instead of raising a `nanobind::type_error()`. This is necessary to honor the
nanobind type caster API contract, which requires `from_python` and `from_cpp` methods
to be marked `noexcept`.
---
 .../mlir/Bindings/Python/NanobindAdaptors.h   | 88 +++++++++++--------
 .../python/lib/PythonTestModuleNanobind.cpp   |  6 +-
 2 files changed, 56 insertions(+), 38 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
index e39b1a752f8d6..7c21f60f844c4 100644
--- a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
+++ b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
@@ -20,6 +20,7 @@
 #define MLIR_BINDINGS_PYTHON_NANOBINDADAPTORS_H
 
 #include <cstdint>
+#include <optional>
 
 #include "mlir-c/Diagnostics.h"
 #include "mlir-c/IR.h"
@@ -43,18 +44,14 @@ namespace detail {
 /// with a raw handle (unowned). The returned object's lifetime may not extend
 /// beyond the apiObject handle without explicitly having its refcount increased
 /// (i.e. on return).
-static nanobind::object mlirApiObjectToCapsule(nanobind::handle apiObject) {
+static std::optional<nanobind::object>
+mlirApiObjectToCapsule(nanobind::handle apiObject) {
   if (PyCapsule_CheckExact(apiObject.ptr()))
     return nanobind::borrow<nanobind::object>(apiObject);
   nanobind::object api =
       nanobind::getattr(apiObject, MLIR_PYTHON_CAPI_PTR_ATTR, nanobind::none());
-  if (api.is_none()) {
-    std::string repr = nanobind::cast<std::string>(nanobind::repr(apiObject));
-    throw nanobind::type_error(
-        (llvm::Twine("Expected an MLIR object (got ") + repr + ").")
-            .str()
-            .c_str());
-  }
+  if (api.is_none())
+    return std::nullopt;
   return api;
 }
 
@@ -68,11 +65,10 @@ template <>
 struct type_caster<MlirAffineMap> {
   NB_TYPE_CASTER(MlirAffineMap, const_name("MlirAffineMap"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToAffineMap(capsule.ptr());
-    if (mlirAffineMapIsNull(value)) {
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
       return false;
-    }
+    value = mlirPythonCapsuleToAffineMap(capsule.value().ptr());
     return !mlirAffineMapIsNull(value);
   }
   static handle from_cpp(MlirAffineMap v, rv_policy,
@@ -91,8 +87,10 @@ template <>
 struct type_caster<MlirAttribute> {
   NB_TYPE_CASTER(MlirAttribute, const_name("MlirAttribute"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToAttribute(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToAttribute(capsule.value().ptr());
     return !mlirAttributeIsNull(value);
   }
   static handle from_cpp(MlirAttribute v, rv_policy,
@@ -112,8 +110,10 @@ template <>
 struct type_caster<MlirBlock> {
   NB_TYPE_CASTER(MlirBlock, const_name("MlirBlock"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToBlock(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToBlock(capsule.value().ptr());
     return !mlirBlockIsNull(value);
   }
 };
@@ -132,8 +132,8 @@ struct type_caster<MlirContext> {
                 .attr("Context")
                 .attr("current");
     }
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToContext(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    value = mlirPythonCapsuleToContext(capsule.value().ptr());
     return !mlirContextIsNull(value);
   }
 };
@@ -143,8 +143,10 @@ template <>
 struct type_caster<MlirDialectRegistry> {
   NB_TYPE_CASTER(MlirDialectRegistry, const_name("MlirDialectRegistry"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToDialectRegistry(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToDialectRegistry(capsule.value().ptr());
     return !mlirDialectRegistryIsNull(value);
   }
   static handle from_cpp(MlirDialectRegistry v, rv_policy,
@@ -169,8 +171,8 @@ struct type_caster<MlirLocation> {
                 .attr("Location")
                 .attr("current");
     }
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToLocation(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    value = mlirPythonCapsuleToLocation(capsule.value().ptr());
     return !mlirLocationIsNull(value);
   }
   static handle from_cpp(MlirLocation v, rv_policy,
@@ -189,8 +191,10 @@ template <>
 struct type_caster<MlirModule> {
   NB_TYPE_CASTER(MlirModule, const_name("MlirModule"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToModule(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToModule(capsule.value().ptr());
     return !mlirModuleIsNull(value);
   }
   static handle from_cpp(MlirModule v, rv_policy,
@@ -210,8 +214,10 @@ struct type_caster<MlirFrozenRewritePatternSet> {
   NB_TYPE_CASTER(MlirFrozenRewritePatternSet,
                  const_name("MlirFrozenRewritePatternSet"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToFrozenRewritePatternSet(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToFrozenRewritePatternSet(capsule.value().ptr());
     return value.ptr != nullptr;
   }
   static handle from_cpp(MlirFrozenRewritePatternSet v, rv_policy,
@@ -230,8 +236,10 @@ template <>
 struct type_caster<MlirOperation> {
   NB_TYPE_CASTER(MlirOperation, const_name("MlirOperation"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToOperation(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToOperation(capsule.value().ptr());
     return !mlirOperationIsNull(value);
   }
   static handle from_cpp(MlirOperation v, rv_policy,
@@ -252,8 +260,10 @@ template <>
 struct type_caster<MlirValue> {
   NB_TYPE_CASTER(MlirValue, const_name("MlirValue"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToValue(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToValue(capsule.value().ptr());
     return !mlirValueIsNull(value);
   }
   static handle from_cpp(MlirValue v, rv_policy,
@@ -275,8 +285,10 @@ template <>
 struct type_caster<MlirPassManager> {
   NB_TYPE_CASTER(MlirPassManager, const_name("MlirPassManager"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToPassManager(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToPassManager(capsule.value().ptr());
     return !mlirPassManagerIsNull(value);
   }
 };
@@ -286,8 +298,10 @@ template <>
 struct type_caster<MlirTypeID> {
   NB_TYPE_CASTER(MlirTypeID, const_name("MlirTypeID"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToTypeID(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToTypeID(capsule.value().ptr());
     return !mlirTypeIDIsNull(value);
   }
   static handle from_cpp(MlirTypeID v, rv_policy,
@@ -308,8 +322,10 @@ template <>
 struct type_caster<MlirType> {
   NB_TYPE_CASTER(MlirType, const_name("MlirType"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    nanobind::object capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToType(capsule.ptr());
+    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
+    if (!capsule)
+      return false;
+    value = mlirPythonCapsuleToType(capsule.value().ptr());
     return !mlirTypeIsNull(value);
   }
   static handle from_cpp(MlirType t, rv_policy,
diff --git a/mlir/test/python/lib/PythonTestModuleNanobind.cpp b/mlir/test/python/lib/PythonTestModuleNanobind.cpp
index dd0a9f2b66ea6..ac69a3c28a767 100644
--- a/mlir/test/python/lib/PythonTestModuleNanobind.cpp
+++ b/mlir/test/python/lib/PythonTestModuleNanobind.cpp
@@ -113,8 +113,10 @@ NB_MODULE(_mlirPythonTestNanobind, m) {
       .attr(MLIR_PYTHON_CAPI_VALUE_CASTER_REGISTER_ATTR)(
           mlirRankedTensorTypeID)(
           nanobind::cpp_function([valueCls](const nb::object &valueObj) {
-            nb::object capsule = mlirApiObjectToCapsule(valueObj);
-            MlirValue v = mlirPythonCapsuleToValue(capsule.ptr());
+            std::optional<nb::object> capsule =
+                mlirApiObjectToCapsule(valueObj);
+            // TODO(nicholasjng): Can this capsule be std::nullopt?
+            MlirValue v = mlirPythonCapsuleToValue(capsule.value().ptr());
             MlirType t = mlirValueGetType(v);
             // This is hyper-specific in order to exercise/test registering a
             // value caster from cpp (but only for a single test case; see

>From db8e35c82dfffa712f38425db97f438c9a2899f6 Mon Sep 17 00:00:00 2001
From: Nicholas Junge <nicholas.junge at web.de>
Date: Thu, 10 Jul 2025 22:17:14 +0200
Subject: [PATCH 3/4] refactor: Assign in if statements everywhere, adjust test
 expectations

Modeled after a patch posted in the conversation in PR #143866:
https://gist.github.com/makslevental/b224ffca7f15e273a4897975cda28b4c.
---
 .../mlir/Bindings/Python/NanobindAdaptors.h   | 74 +++++++------------
 mlir/test/python/dialects/python_test.py      |  8 +-
 .../python/lib/PythonTestModuleNanobind.cpp   |  2 +-
 3 files changed, 33 insertions(+), 51 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
index 7c21f60f844c4..039831c7b9737 100644
--- a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
+++ b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
@@ -51,7 +51,7 @@ mlirApiObjectToCapsule(nanobind::handle apiObject) {
   nanobind::object api =
       nanobind::getattr(apiObject, MLIR_PYTHON_CAPI_PTR_ATTR, nanobind::none());
   if (api.is_none())
-    return std::nullopt;
+    return {};
   return api;
 }
 
@@ -65,10 +65,8 @@ template <>
 struct type_caster<MlirAffineMap> {
   NB_TYPE_CASTER(MlirAffineMap, const_name("MlirAffineMap"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToAffineMap(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToAffineMap(capsule->ptr());
     return !mlirAffineMapIsNull(value);
   }
   static handle from_cpp(MlirAffineMap v, rv_policy,
@@ -87,10 +85,8 @@ template <>
 struct type_caster<MlirAttribute> {
   NB_TYPE_CASTER(MlirAttribute, const_name("MlirAttribute"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToAttribute(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToAttribute(capsule->ptr());
     return !mlirAttributeIsNull(value);
   }
   static handle from_cpp(MlirAttribute v, rv_policy,
@@ -110,10 +106,8 @@ template <>
 struct type_caster<MlirBlock> {
   NB_TYPE_CASTER(MlirBlock, const_name("MlirBlock"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToBlock(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToBlock(capsule->ptr());
     return !mlirBlockIsNull(value);
   }
 };
@@ -133,7 +127,7 @@ struct type_caster<MlirContext> {
                 .attr("current");
     }
     std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToContext(capsule.value().ptr());
+    value = mlirPythonCapsuleToContext(capsule->ptr());
     return !mlirContextIsNull(value);
   }
 };
@@ -143,10 +137,8 @@ template <>
 struct type_caster<MlirDialectRegistry> {
   NB_TYPE_CASTER(MlirDialectRegistry, const_name("MlirDialectRegistry"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToDialectRegistry(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToDialectRegistry(capsule->ptr());
     return !mlirDialectRegistryIsNull(value);
   }
   static handle from_cpp(MlirDialectRegistry v, rv_policy,
@@ -171,8 +163,8 @@ struct type_caster<MlirLocation> {
                 .attr("Location")
                 .attr("current");
     }
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    value = mlirPythonCapsuleToLocation(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToLocation(capsule->ptr());
     return !mlirLocationIsNull(value);
   }
   static handle from_cpp(MlirLocation v, rv_policy,
@@ -191,10 +183,8 @@ template <>
 struct type_caster<MlirModule> {
   NB_TYPE_CASTER(MlirModule, const_name("MlirModule"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToModule(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToModule(capsule->ptr());
     return !mlirModuleIsNull(value);
   }
   static handle from_cpp(MlirModule v, rv_policy,
@@ -214,10 +204,8 @@ struct type_caster<MlirFrozenRewritePatternSet> {
   NB_TYPE_CASTER(MlirFrozenRewritePatternSet,
                  const_name("MlirFrozenRewritePatternSet"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToFrozenRewritePatternSet(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToFrozenRewritePatternSet(capsule->ptr());
     return value.ptr != nullptr;
   }
   static handle from_cpp(MlirFrozenRewritePatternSet v, rv_policy,
@@ -236,10 +224,8 @@ template <>
 struct type_caster<MlirOperation> {
   NB_TYPE_CASTER(MlirOperation, const_name("MlirOperation"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToOperation(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToOperation(capsule->ptr());
     return !mlirOperationIsNull(value);
   }
   static handle from_cpp(MlirOperation v, rv_policy,
@@ -260,10 +246,8 @@ template <>
 struct type_caster<MlirValue> {
   NB_TYPE_CASTER(MlirValue, const_name("MlirValue"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToValue(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToValue(capsule->ptr());
     return !mlirValueIsNull(value);
   }
   static handle from_cpp(MlirValue v, rv_policy,
@@ -285,10 +269,8 @@ template <>
 struct type_caster<MlirPassManager> {
   NB_TYPE_CASTER(MlirPassManager, const_name("MlirPassManager"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToPassManager(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToPassManager(capsule->ptr());
     return !mlirPassManagerIsNull(value);
   }
 };
@@ -298,10 +280,8 @@ template <>
 struct type_caster<MlirTypeID> {
   NB_TYPE_CASTER(MlirTypeID, const_name("MlirTypeID"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToTypeID(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToTypeID(capsule->ptr());
     return !mlirTypeIDIsNull(value);
   }
   static handle from_cpp(MlirTypeID v, rv_policy,
@@ -322,10 +302,8 @@ template <>
 struct type_caster<MlirType> {
   NB_TYPE_CASTER(MlirType, const_name("MlirType"))
   bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
-    std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
-    if (!capsule)
-      return false;
-    value = mlirPythonCapsuleToType(capsule.value().ptr());
+    if (auto capsule = mlirApiObjectToCapsule(src))
+      value = mlirPythonCapsuleToType(capsule->ptr());
     return !mlirTypeIsNull(value);
   }
   static handle from_cpp(MlirType t, rv_policy,
diff --git a/mlir/test/python/dialects/python_test.py b/mlir/test/python/dialects/python_test.py
index fd678f8321fd9..694616696a9e2 100644
--- a/mlir/test/python/dialects/python_test.py
+++ b/mlir/test/python/dialects/python_test.py
@@ -361,7 +361,9 @@ def testCustomAttribute():
         try:
             TestAttr(42)
         except TypeError as e:
-            assert "Expected an MLIR object" in str(e)
+            assert "Expected an MLIR object (got 42)" in str(e)
+        except ValueError as e:
+            assert "Cannot cast attribute to TestAttr (from 42)" in str(e)
         else:
             raise
 
@@ -406,7 +408,9 @@ def testCustomType():
         try:
             TestType(42)
         except TypeError as e:
-            assert "Expected an MLIR object" in str(e)
+            assert "Expected an MLIR object (got 42)" in str(e)
+        except ValueError as e:
+            assert "Cannot cast type to TestType (from 42)" in str(e)
         else:
             raise
 
diff --git a/mlir/test/python/lib/PythonTestModuleNanobind.cpp b/mlir/test/python/lib/PythonTestModuleNanobind.cpp
index ac69a3c28a767..108df8da86a7e 100644
--- a/mlir/test/python/lib/PythonTestModuleNanobind.cpp
+++ b/mlir/test/python/lib/PythonTestModuleNanobind.cpp
@@ -115,7 +115,7 @@ NB_MODULE(_mlirPythonTestNanobind, m) {
           nanobind::cpp_function([valueCls](const nb::object &valueObj) {
             std::optional<nb::object> capsule =
                 mlirApiObjectToCapsule(valueObj);
-            // TODO(nicholasjng): Can this capsule be std::nullopt?
+            assert(capsule.has_value() && "capsule is not null");
             MlirValue v = mlirPythonCapsuleToValue(capsule.value().ptr());
             MlirType t = mlirValueGetType(v);
             // This is hyper-specific in order to exercise/test registering a

>From 88e80384d3fd83c2f3b7895faa38e96fc5c3e14e Mon Sep 17 00:00:00 2001
From: Nicholas Junge <nicholas.junge at web.de>
Date: Fri, 11 Jul 2025 11:23:45 +0200
Subject: [PATCH 4/4] Relax conversions to `nanobind::try_cast`

This avoids a nb::cast_error throw in certain class conversions.
---
 .../mlir/Bindings/Python/NanobindAdaptors.h     | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
index 039831c7b9737..8dcf91e5806bd 100644
--- a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
+++ b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
@@ -457,9 +457,10 @@ class mlir_attribute_subclass : public pure_subclass {
     nanobind::object newCf = nanobind::cpp_function(
         [superCls, isaFunction, captureTypeName](
             nanobind::object cls, nanobind::object otherAttribute) {
-          MlirAttribute rawAttribute =
-              nanobind::cast<MlirAttribute>(otherAttribute);
-          if (!isaFunction(rawAttribute)) {
+          MlirAttribute rawAttribute;
+          if (!nanobind::try_cast<MlirAttribute>(otherAttribute,
+                                                 rawAttribute) ||
+              !isaFunction(rawAttribute)) {
             auto origRepr =
                 nanobind::cast<std::string>(nanobind::repr(otherAttribute));
             throw std::invalid_argument(
@@ -538,8 +539,9 @@ class mlir_type_subclass : public pure_subclass {
     nanobind::object newCf = nanobind::cpp_function(
         [superCls, isaFunction, captureTypeName](nanobind::object cls,
                                                  nanobind::object otherType) {
-          MlirType rawType = nanobind::cast<MlirType>(otherType);
-          if (!isaFunction(rawType)) {
+          MlirType rawType;
+          if (!nanobind::try_cast<MlirType>(otherType, rawType) ||
+              !isaFunction(rawType)) {
             auto origRepr =
                 nanobind::cast<std::string>(nanobind::repr(otherType));
             throw std::invalid_argument((llvm::Twine("Cannot cast type to ") +
@@ -620,8 +622,9 @@ class mlir_value_subclass : public pure_subclass {
     nanobind::object newCf = nanobind::cpp_function(
         [superCls, isaFunction, captureValueName](nanobind::object cls,
                                                   nanobind::object otherValue) {
-          MlirValue rawValue = nanobind::cast<MlirValue>(otherValue);
-          if (!isaFunction(rawValue)) {
+          MlirValue rawValue;
+          if (!nanobind::try_cast<MlirValue>(otherValue, rawValue) ||
+              !isaFunction(rawValue)) {
             auto origRepr =
                 nanobind::cast<std::string>(nanobind::repr(otherValue));
             throw std::invalid_argument((llvm::Twine("Cannot cast value to ") +



More information about the Mlir-commits mailing list