[Mlir-commits] [mlir] [mlir][python] expose remaining Location inspection API (PR #192630)

Soowon Jeong llvmlistbot at llvm.org
Fri Apr 17 06:58:48 PDT 2026


https://github.com/swjng updated https://github.com/llvm/llvm-project/pull/192630

>From 98b987dcf83e6681c514252ec99ca59328cc34aa Mon Sep 17 00:00:00 2001
From: Soowon Jeong <soowon1106 at gmail.com>
Date: Fri, 17 Apr 2026 17:40:15 +0900
Subject: [PATCH] [mlir][python] expose Location subclasses via isinstance

Currently `Location` in the Python bindings is a single class with
predicate methods (`is_a_file`, `is_a_name`, etc.) to discriminate
between LocationAttr kinds, and field accessors for every kind live on
the base class. Several kinds (UnknownLoc, OpaqueLoc, FusedLoc metadata)
have no Python-side accessors at all. Addresses #53169.

Mirrors the pattern used for Attribute subclasses: introduces a
`PyConcreteLocation<T>` CRTP template and concrete Python classes
`UnknownLoc`, `FileLineColLoc`, `NameLoc`, `CallSiteLoc`, `FusedLoc`,
`OpaqueLoc`, each registered as a nanobind subclass of `Location`.
Downcasting is driven by TypeID lookup in `PyLocation::maybeDownCast`,
called at the boundaries that return Location objects (`op.location`,
`value.location`, `Location.from_attr`, and the subclass getters
themselves). Existing `Location.unknown()`/`file()`/`name()`/
`callsite()`/`fused()` factories remain as aliases that return the
concrete subclass instance, so existing user code and tests keep
working.

Motivation: a Python tool walking arbitrary op locations can now use
`isinstance(loc, OpaqueLoc)` / `isinstance(loc, FusedLoc)`, read
`.metadata` on `FusedLoc`, or recover the raw `uintptr_t` tag from an
`OpaqueLoc` for frontends that attach their own source-location objects
(e.g. a Python-frontend DSL that stores the owning Python object via
`id(obj)` and recovers it in a diagnostic handler via `ctypes`).

C API additions required by this wiring:
- `mlirLocationIsAUnknown`, `mlirLocationUnknownGetTypeID`
- `mlirLocationOpaqueGet`, `mlirLocationOpaqueGetUnderlyingLocation`,
  `mlirLocationOpaqueGetUnderlyingTypeID`,
  `mlirLocationOpaqueGetFallbackLocation`,
  `mlirLocationOpaqueGetTypeID`, `mlirLocationIsAOpaque`

Assisted-by: Claude (Anthropic)
---
 mlir/include/mlir-c/IR.h                   |  30 ++
 mlir/include/mlir/Bindings/Python/IRCore.h | 129 +++++++
 mlir/lib/Bindings/Python/IRCore.cpp        | 415 +++++++++++++++------
 mlir/lib/CAPI/IR/IR.cpp                    |  36 ++
 mlir/test/CAPI/ir.c                        |  36 ++
 mlir/test/python/ir/location.py            | 145 +++++--
 6 files changed, 637 insertions(+), 154 deletions(-)

diff --git a/mlir/include/mlir-c/IR.h b/mlir/include/mlir-c/IR.h
index 8d30051d615f4..7765e9566fe2a 100644
--- a/mlir/include/mlir-c/IR.h
+++ b/mlir/include/mlir-c/IR.h
@@ -363,6 +363,36 @@ MLIR_CAPI_EXPORTED bool mlirLocationIsAName(MlirLocation location);
 /// Creates a location with unknown position owned by the given context.
 MLIR_CAPI_EXPORTED MlirLocation mlirLocationUnknownGet(MlirContext context);
 
+/// TypeID Getter for Unknown.
+MLIR_CAPI_EXPORTED MlirTypeID mlirLocationUnknownGetTypeID(void);
+
+/// Checks whether the given location is an Unknown.
+MLIR_CAPI_EXPORTED bool mlirLocationIsAUnknown(MlirLocation location);
+
+/// Creates an opaque location with an underlying pointer, a TypeID tag, and a
+/// fallback location.
+MLIR_CAPI_EXPORTED MlirLocation
+mlirLocationOpaqueGet(uintptr_t underlyingLocation, MlirTypeID underlyingTypeID,
+                      MlirLocation fallbackLocation);
+
+/// Getter for underlyingLocation of Opaque.
+MLIR_CAPI_EXPORTED uintptr_t
+mlirLocationOpaqueGetUnderlyingLocation(MlirLocation location);
+
+/// Getter for underlyingTypeID of Opaque.
+MLIR_CAPI_EXPORTED MlirTypeID
+mlirLocationOpaqueGetUnderlyingTypeID(MlirLocation location);
+
+/// Getter for fallbackLocation of Opaque.
+MLIR_CAPI_EXPORTED MlirLocation
+mlirLocationOpaqueGetFallbackLocation(MlirLocation location);
+
+/// TypeID Getter for Opaque.
+MLIR_CAPI_EXPORTED MlirTypeID mlirLocationOpaqueGetTypeID(void);
+
+/// Checks whether the given location is an Opaque.
+MLIR_CAPI_EXPORTED bool mlirLocationIsAOpaque(MlirLocation location);
+
 /// Gets the context that a location was created with.
 MLIR_CAPI_EXPORTED MlirContext mlirLocationGetContext(MlirLocation location);
 
diff --git a/mlir/include/mlir/Bindings/Python/IRCore.h b/mlir/include/mlir/Bindings/Python/IRCore.h
index b2edcace7298e..b0f2381f62c7c 100644
--- a/mlir/include/mlir/Bindings/Python/IRCore.h
+++ b/mlir/include/mlir/Bindings/Python/IRCore.h
@@ -337,6 +337,10 @@ class MLIR_PYTHON_API_EXPORTED PyLocation : public BaseContextObject {
   /// is taken by calling this function.
   static PyLocation createFromCapsule(nanobind::object capsule);
 
+  /// Returns a Python object of the most-derived Location subclass if a type
+  /// caster is registered for this Location's TypeID, otherwise returns self.
+  nanobind::typed<nanobind::object, PyLocation> maybeDownCast();
+
 private:
   MlirLocation loc;
 };
@@ -1164,6 +1168,131 @@ class MLIR_PYTHON_API_EXPORTED PyStringAttribute
   static void bindDerived(ClassTy &c);
 };
 
+/// CRTP base class for Python classes that subclass Location and should be
+/// castable from it (i.e. via something like FileLineColLoc(loc)).
+template <typename DerivedTy, typename BaseTy = PyLocation>
+class MLIR_PYTHON_API_EXPORTED PyConcreteLocation : public BaseTy {
+public:
+  // Derived classes must define statics for:
+  //   IsAFunctionTy isaFunction
+  //   const char *pyClassName
+  using ClassTy = nanobind::class_<DerivedTy, BaseTy>;
+  using IsAFunctionTy = bool (*)(MlirLocation);
+  using GetTypeIDFunctionTy = MlirTypeID (*)();
+  static constexpr GetTypeIDFunctionTy getTypeIdFunction = nullptr;
+  using Base = PyConcreteLocation;
+
+  PyConcreteLocation() = default;
+  PyConcreteLocation(PyMlirContextRef contextRef, MlirLocation loc)
+      : BaseTy(std::move(contextRef), loc) {}
+  PyConcreteLocation(PyLocation &orig)
+      : PyConcreteLocation(orig.getContext(), castFrom(orig)) {}
+
+  static MlirLocation castFrom(PyLocation &orig) {
+    if (!DerivedTy::isaFunction(orig.get())) {
+      throw nanobind::value_error(
+          (std::string("Cannot cast location to ") + DerivedTy::pyClassName)
+              .c_str());
+    }
+    return orig.get();
+  }
+
+  static void bind(nanobind::module_ &m) {
+    ClassTy cls(m, DerivedTy::pyClassName);
+    cls.def(nanobind::init<PyLocation &>(), nanobind::keep_alive<0, 1>(),
+            nanobind::arg("cast_from_loc"));
+    cls.def_prop_ro_static("static_typeid", [](nanobind::object & /*class*/) {
+      if (DerivedTy::getTypeIdFunction)
+        return PyTypeID(DerivedTy::getTypeIdFunction());
+      throw nanobind::attribute_error(
+          (DerivedTy::pyClassName + std::string(" has no typeid.")).c_str());
+    });
+    if (DerivedTy::getTypeIdFunction) {
+      PyGlobals::get().registerTypeCaster(
+          DerivedTy::getTypeIdFunction(),
+          nanobind::cast<nanobind::callable>(nanobind::cpp_function(
+              [](PyLocation pyLoc) -> DerivedTy { return pyLoc; })),
+          /*replace*/ true);
+    }
+    DerivedTy::bindDerived(cls);
+  }
+
+  /// Implemented by derived classes to add methods to the Python subclass.
+  static void bindDerived(ClassTy &m) {}
+};
+
+class MLIR_PYTHON_API_EXPORTED PyUnknownLocation
+    : public PyConcreteLocation<PyUnknownLocation> {
+public:
+  static constexpr IsAFunctionTy isaFunction = mlirLocationIsAUnknown;
+  static constexpr const char *pyClassName = "UnknownLoc";
+  using PyConcreteLocation::PyConcreteLocation;
+  static constexpr GetTypeIDFunctionTy getTypeIdFunction =
+      mlirLocationUnknownGetTypeID;
+
+  static void bindDerived(ClassTy &c);
+};
+
+class MLIR_PYTHON_API_EXPORTED PyFileLineColLocation
+    : public PyConcreteLocation<PyFileLineColLocation> {
+public:
+  static constexpr IsAFunctionTy isaFunction = mlirLocationIsAFileLineColRange;
+  static constexpr const char *pyClassName = "FileLineColLoc";
+  using PyConcreteLocation::PyConcreteLocation;
+  static constexpr GetTypeIDFunctionTy getTypeIdFunction =
+      mlirLocationFileLineColRangeGetTypeID;
+
+  static void bindDerived(ClassTy &c);
+};
+
+class MLIR_PYTHON_API_EXPORTED PyNameLocation
+    : public PyConcreteLocation<PyNameLocation> {
+public:
+  static constexpr IsAFunctionTy isaFunction = mlirLocationIsAName;
+  static constexpr const char *pyClassName = "NameLoc";
+  using PyConcreteLocation::PyConcreteLocation;
+  static constexpr GetTypeIDFunctionTy getTypeIdFunction =
+      mlirLocationNameGetTypeID;
+
+  static void bindDerived(ClassTy &c);
+};
+
+class MLIR_PYTHON_API_EXPORTED PyCallSiteLocation
+    : public PyConcreteLocation<PyCallSiteLocation> {
+public:
+  static constexpr IsAFunctionTy isaFunction = mlirLocationIsACallSite;
+  static constexpr const char *pyClassName = "CallSiteLoc";
+  using PyConcreteLocation::PyConcreteLocation;
+  static constexpr GetTypeIDFunctionTy getTypeIdFunction =
+      mlirLocationCallSiteGetTypeID;
+
+  static void bindDerived(ClassTy &c);
+};
+
+class MLIR_PYTHON_API_EXPORTED PyFusedLocation
+    : public PyConcreteLocation<PyFusedLocation> {
+public:
+  static constexpr IsAFunctionTy isaFunction = mlirLocationIsAFused;
+  static constexpr const char *pyClassName = "FusedLoc";
+  using PyConcreteLocation::PyConcreteLocation;
+  static constexpr GetTypeIDFunctionTy getTypeIdFunction =
+      mlirLocationFusedGetTypeID;
+
+  static void bindDerived(ClassTy &c);
+};
+
+class MLIR_PYTHON_API_EXPORTED PyOpaqueLocation
+    : public PyConcreteLocation<PyOpaqueLocation> {
+public:
+  static constexpr IsAFunctionTy isaFunction = mlirLocationIsAOpaque;
+  static constexpr const char *pyClassName = "OpaqueLoc";
+  using PyConcreteLocation::PyConcreteLocation;
+  static constexpr GetTypeIDFunctionTy getTypeIdFunction =
+      mlirLocationOpaqueGetTypeID;
+
+  static void bindDerived(ClassTy &c);
+};
+
 /// Wrapper around the generic MlirValue.
 /// Values are managed completely by the operation that resulted in their
 /// definition. For op result value, this is the operation that defines the
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index b52328a37e5f1..74189a67d2430 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -1887,6 +1887,23 @@ nb::typed<nb::object, PyAttribute> PyAttribute::maybeDownCast() {
   return typeCaster.value()(thisObj);
 }
 
+//------------------------------------------------------------------------------
+// PyLocation::maybeDownCast.
+//------------------------------------------------------------------------------
+
+nb::typed<nb::object, PyLocation> PyLocation::maybeDownCast() {
+  MlirAttribute locAttr = mlirLocationGetAttribute(this->get());
+  MlirTypeID mlirTypeID = mlirAttributeGetTypeID(locAttr);
+  assert(!mlirTypeIDIsNull(mlirTypeID) &&
+         "mlirTypeID was expected to be non-null.");
+  std::optional<nb::callable> typeCaster = PyGlobals::get().lookupTypeCaster(
+      mlirTypeID, mlirAttributeGetDialect(locAttr));
+  nb::object thisObj = nb::cast(this, nb::rv_policy::move);
+  if (!typeCaster)
+    return thisObj;
+  return typeCaster.value()(thisObj);
+}
+
 //------------------------------------------------------------------------------
 // PyNamedAttribute.
 //------------------------------------------------------------------------------
@@ -2965,6 +2982,223 @@ void populateRoot(nb::module_ &m) {
       "Register a value caster for casting MLIR values to custom user values.");
 }
 
+//------------------------------------------------------------------------------
+// Location subclass bindDerived implementations.
+//------------------------------------------------------------------------------
+
+void PyUnknownLocation::bindDerived(ClassTy &c) {
+  c.def_static(
+      "get",
+      [](DefaultingPyMlirContext context) {
+        return PyUnknownLocation(context->getRef(),
+                                 mlirLocationUnknownGet(context->get()));
+      },
+      "context"_a = nb::none(),
+      "Gets a Location representing an unknown location.");
+}
+
+void PyFileLineColLocation::bindDerived(ClassTy &c) {
+  c.def_static(
+      "get",
+      [](std::string filename, int line, int col,
+         DefaultingPyMlirContext context) {
+        return PyFileLineColLocation(
+            context->getRef(),
+            mlirLocationFileLineColGet(context->get(),
+                                       toMlirStringRef(filename), line, col));
+      },
+      "filename"_a, "line"_a, "col"_a, "context"_a = nb::none(),
+      "Gets a FileLineColLoc for a file, line, and column.");
+  c.def_static(
+      "get",
+      [](std::string filename, int startLine, int startCol, int endLine,
+         int endCol, DefaultingPyMlirContext context) {
+        return PyFileLineColLocation(
+            context->getRef(), mlirLocationFileLineColRangeGet(
+                                   context->get(), toMlirStringRef(filename),
+                                   startLine, startCol, endLine, endCol));
+      },
+      "filename"_a, "start_line"_a, "start_col"_a, "end_line"_a, "end_col"_a,
+      "context"_a = nb::none(),
+      "Gets a FileLineColLoc spanning a file and line/column range.");
+  c.def_prop_ro(
+      "filename",
+      [](PyFileLineColLocation &self) {
+        return mlirIdentifierStr(
+            mlirLocationFileLineColRangeGetFilename(self.get()));
+      },
+      "Gets the filename from a `FileLineColLoc`.");
+  c.def_prop_ro(
+      "start_line",
+      [](PyFileLineColLocation &self) {
+        return mlirLocationFileLineColRangeGetStartLine(self.get());
+      },
+      "Gets the start line number from a `FileLineColLoc`.");
+  c.def_prop_ro(
+      "start_col",
+      [](PyFileLineColLocation &self) {
+        return mlirLocationFileLineColRangeGetStartColumn(self.get());
+      },
+      "Gets the start column number from a `FileLineColLoc`.");
+  c.def_prop_ro(
+      "end_line",
+      [](PyFileLineColLocation &self) {
+        return mlirLocationFileLineColRangeGetEndLine(self.get());
+      },
+      "Gets the end line number from a `FileLineColLoc`.");
+  c.def_prop_ro(
+      "end_col",
+      [](PyFileLineColLocation &self) {
+        return mlirLocationFileLineColRangeGetEndColumn(self.get());
+      },
+      "Gets the end column number from a `FileLineColLoc`.");
+}
+
+void PyNameLocation::bindDerived(ClassTy &c) {
+  c.def_static(
+      "get",
+      [](std::string name, std::optional<PyLocation> childLoc,
+         DefaultingPyMlirContext context) {
+        return PyNameLocation(
+            context->getRef(),
+            mlirLocationNameGet(context->get(), toMlirStringRef(name),
+                                childLoc
+                                    ? childLoc->get()
+                                    : mlirLocationUnknownGet(context->get())));
+      },
+      "name"_a, "child_loc"_a = nb::none(), "context"_a = nb::none(),
+      "Gets a NameLoc with an optional child location.");
+  c.def_prop_ro(
+      "name_str",
+      [](PyNameLocation &self) {
+        return mlirIdentifierStr(mlirLocationNameGetName(self.get()));
+      },
+      "Gets the name string from a `NameLoc`.");
+  c.def_prop_ro(
+      "child_loc",
+      [](PyNameLocation &self) {
+        return PyLocation(self.getContext(),
+                          mlirLocationNameGetChildLoc(self.get()))
+            .maybeDownCast();
+      },
+      "Gets the child location from a `NameLoc`.");
+}
+
+void PyCallSiteLocation::bindDerived(ClassTy &c) {
+  c.def_static(
+      "get",
+      [](PyLocation callee, const std::vector<PyLocation> &frames,
+         DefaultingPyMlirContext context) {
+        if (frames.empty())
+          throw nb::value_error("No caller frames provided.");
+        MlirLocation caller = frames.back().get();
+        for (size_t index = frames.size() - 1; index-- > 0;) {
+          caller = mlirLocationCallSiteGet(frames[index].get(), caller);
+        }
+        return PyCallSiteLocation(
+            context->getRef(), mlirLocationCallSiteGet(callee.get(), caller));
+      },
+      "callee"_a, "frames"_a, "context"_a = nb::none(),
+      "Gets a CallSiteLoc chaining a callee and one or more caller frames.");
+  c.def_prop_ro(
+      "callee",
+      [](PyCallSiteLocation &self) {
+        return PyLocation(self.getContext(),
+                          mlirLocationCallSiteGetCallee(self.get()))
+            .maybeDownCast();
+      },
+      "Gets the callee location from a `CallSiteLoc`.");
+  c.def_prop_ro(
+      "caller",
+      [](PyCallSiteLocation &self) {
+        return PyLocation(self.getContext(),
+                          mlirLocationCallSiteGetCaller(self.get()))
+            .maybeDownCast();
+      },
+      "Gets the caller location from a `CallSiteLoc`.");
+}
+
+void PyFusedLocation::bindDerived(ClassTy &c) {
+  c.def_static(
+      "get",
+      [](const std::vector<PyLocation> &pyLocations,
+         std::optional<PyAttribute> metadata, DefaultingPyMlirContext context) {
+        std::vector<MlirLocation> locations;
+        locations.reserve(pyLocations.size());
+        for (const PyLocation &pyLocation : pyLocations)
+          locations.push_back(pyLocation.get());
+        MlirLocation location = mlirLocationFusedGet(
+            context->get(), locations.size(), locations.data(),
+            metadata ? metadata->get() : MlirAttribute{0});
+        // FusedLoc construction may collapse to a plain location (e.g. a single
+        // location with no metadata), so downcast to the actual subclass.
+        return PyLocation(context->getRef(), location).maybeDownCast();
+      },
+      "locations"_a, "metadata"_a = nb::none(), "context"_a = nb::none(),
+      "Gets a FusedLoc from an array of locations and optional metadata. "
+      "If the fuse collapses (e.g. single location, no metadata), returns "
+      "the underlying location instead.");
+  c.def_prop_ro(
+      "locations",
+      [](PyFusedLocation &self) {
+        unsigned numLocations = mlirLocationFusedGetNumLocations(self.get());
+        std::vector<MlirLocation> locations(numLocations);
+        if (numLocations)
+          mlirLocationFusedGetLocations(self.get(), locations.data());
+        std::vector<nb::object> pyLocations;
+        pyLocations.reserve(numLocations);
+        for (unsigned i = 0; i < numLocations; ++i)
+          pyLocations.push_back(
+              PyLocation(self.getContext(), locations[i]).maybeDownCast());
+        return pyLocations;
+      },
+      "Gets the list of locations from a `FusedLoc`.");
+  c.def_prop_ro(
+      "metadata",
+      [](PyFusedLocation &self) -> std::optional<PyAttribute> {
+        MlirAttribute metadata = mlirLocationFusedGetMetadata(self.get());
+        if (mlirAttributeIsNull(metadata))
+          return std::nullopt;
+        return PyAttribute(self.getContext(), metadata);
+      },
+      "Gets the metadata attribute from a `FusedLoc`, or None if absent.");
+}
+
+void PyOpaqueLocation::bindDerived(ClassTy &c) {
+  c.def_static(
+      "get",
+      [](uintptr_t underlyingLocation, PyTypeID &underlyingTypeID,
+         PyLocation &fallbackLocation) {
+        return PyOpaqueLocation(fallbackLocation.getContext(),
+                                mlirLocationOpaqueGet(underlyingLocation,
+                                                      underlyingTypeID.get(),
+                                                      fallbackLocation.get()));
+      },
+      "underlying_location"_a, "underlying_type_id"_a, "fallback_location"_a,
+      "Gets an OpaqueLoc wrapping a pointer-typed underlying location "
+      "tagged with a TypeID, plus a fallback location.");
+  c.def_prop_ro(
+      "underlying_location",
+      [](PyOpaqueLocation &self) -> uintptr_t {
+        return mlirLocationOpaqueGetUnderlyingLocation(self.get());
+      },
+      "Gets the underlying pointer from an `OpaqueLoc` as an integer.");
+  c.def_prop_ro(
+      "underlying_type_id",
+      [](PyOpaqueLocation &self) {
+        return PyTypeID(mlirLocationOpaqueGetUnderlyingTypeID(self.get()));
+      },
+      "Gets the TypeID tag from an `OpaqueLoc`.");
+  c.def_prop_ro(
+      "fallback_location",
+      [](PyOpaqueLocation &self) {
+        return PyLocation(self.getContext(),
+                          mlirLocationOpaqueGetFallbackLocation(self.get()))
+            .maybeDownCast();
+      },
+      "Gets the fallback location from an `OpaqueLoc`.");
+}
+
 //------------------------------------------------------------------------------
 // Populates the core exports of the 'ir' submodule.
 //------------------------------------------------------------------------------
@@ -3337,122 +3571,53 @@ void populateIRCore(nb::module_ &m) {
           // clang-format on
           "Gets the Location bound to the current thread or raises ValueError.")
       .def_static(
-          "unknown",
-          [](DefaultingPyMlirContext context) {
+          "from_attr",
+          [](PyAttribute &attribute, DefaultingPyMlirContext context) {
             return PyLocation(context->getRef(),
-                              mlirLocationUnknownGet(context->get()));
+                              mlirLocationFromAttribute(attribute))
+                .maybeDownCast();
           },
-          "context"_a = nb::none(),
-          "Gets a Location representing an unknown location.")
+          "attribute"_a, "context"_a = nb::none(),
+          "Gets a Location from a `LocationAttr`.")
+      // Factory shims. Prefer the corresponding subclass `.get()` methods in
+      // new code; these remain for backward compatibility and return the
+      // concrete subclass.
       .def_static(
-          "callsite",
-          [](PyLocation callee, const std::vector<PyLocation> &frames,
-             DefaultingPyMlirContext context) {
-            if (frames.empty())
-              throw nb::value_error("No caller frames provided.");
-            MlirLocation caller = frames.back().get();
-            for (size_t index = frames.size() - 1; index-- > 0;) {
-              caller = mlirLocationCallSiteGet(frames[index].get(), caller);
-            }
-            return PyLocation(context->getRef(),
-                              mlirLocationCallSiteGet(callee.get(), caller));
-          },
-          "callee"_a, "frames"_a, "context"_a = nb::none(),
-          "Gets a Location representing a caller and callsite.")
-      .def("is_a_callsite", mlirLocationIsACallSite,
-           "Returns True if this location is a CallSiteLoc.")
-      .def_prop_ro(
-          "callee",
-          [](PyLocation &self) {
-            return PyLocation(self.getContext(),
-                              mlirLocationCallSiteGetCallee(self));
-          },
-          "Gets the callee location from a CallSiteLoc.")
-      .def_prop_ro(
-          "caller",
-          [](PyLocation &self) {
-            return PyLocation(self.getContext(),
-                              mlirLocationCallSiteGetCaller(self));
+          "unknown",
+          [](DefaultingPyMlirContext context) {
+            return PyUnknownLocation(context->getRef(),
+                                     mlirLocationUnknownGet(context->get()));
           },
-          "Gets the caller location from a CallSiteLoc.")
+          "context"_a = nb::none(), "Alias for `UnknownLoc.get()`.")
       .def_static(
           "file",
           [](std::string filename, int line, int col,
              DefaultingPyMlirContext context) {
-            return PyLocation(
+            return PyFileLineColLocation(
                 context->getRef(),
                 mlirLocationFileLineColGet(
                     context->get(), toMlirStringRef(filename), line, col));
           },
           "filename"_a, "line"_a, "col"_a, "context"_a = nb::none(),
-          "Gets a Location representing a file, line and column.")
+          "Alias for `FileLineColLoc.get()`.")
       .def_static(
           "file",
           [](std::string filename, int startLine, int startCol, int endLine,
              int endCol, DefaultingPyMlirContext context) {
-            return PyLocation(context->getRef(),
-                              mlirLocationFileLineColRangeGet(
-                                  context->get(), toMlirStringRef(filename),
-                                  startLine, startCol, endLine, endCol));
+            return PyFileLineColLocation(
+                context->getRef(),
+                mlirLocationFileLineColRangeGet(
+                    context->get(), toMlirStringRef(filename), startLine,
+                    startCol, endLine, endCol));
           },
           "filename"_a, "start_line"_a, "start_col"_a, "end_line"_a,
           "end_col"_a, "context"_a = nb::none(),
-          "Gets a Location representing a file, line and column range.")
-      .def("is_a_file", mlirLocationIsAFileLineColRange,
-           "Returns True if this location is a FileLineColLoc.")
-      .def_prop_ro(
-          "filename",
-          [](PyLocation loc) {
-            return mlirIdentifierStr(
-                mlirLocationFileLineColRangeGetFilename(loc));
-          },
-          "Gets the filename from a FileLineColLoc.")
-      .def_prop_ro("start_line", mlirLocationFileLineColRangeGetStartLine,
-                   "Gets the start line number from a `FileLineColLoc`.")
-      .def_prop_ro("start_col", mlirLocationFileLineColRangeGetStartColumn,
-                   "Gets the start column number from a `FileLineColLoc`.")
-      .def_prop_ro("end_line", mlirLocationFileLineColRangeGetEndLine,
-                   "Gets the end line number from a `FileLineColLoc`.")
-      .def_prop_ro("end_col", mlirLocationFileLineColRangeGetEndColumn,
-                   "Gets the end column number from a `FileLineColLoc`.")
-      .def_static(
-          "fused",
-          [](const std::vector<PyLocation> &pyLocations,
-             std::optional<PyAttribute> metadata,
-             DefaultingPyMlirContext context) {
-            std::vector<MlirLocation> locations;
-            locations.reserve(pyLocations.size());
-            for (const PyLocation &pyLocation : pyLocations)
-              locations.push_back(pyLocation.get());
-            MlirLocation location = mlirLocationFusedGet(
-                context->get(), locations.size(), locations.data(),
-                metadata ? metadata->get() : MlirAttribute{0});
-            return PyLocation(context->getRef(), location);
-          },
-          "locations"_a, "metadata"_a = nb::none(), "context"_a = nb::none(),
-          "Gets a Location representing a fused location with optional "
-          "metadata.")
-      .def("is_a_fused", mlirLocationIsAFused,
-           "Returns True if this location is a `FusedLoc`.")
-      .def_prop_ro(
-          "locations",
-          [](PyLocation &self) {
-            unsigned numLocations = mlirLocationFusedGetNumLocations(self);
-            std::vector<MlirLocation> locations(numLocations);
-            if (numLocations)
-              mlirLocationFusedGetLocations(self, locations.data());
-            std::vector<PyLocation> pyLocations{};
-            pyLocations.reserve(numLocations);
-            for (unsigned i = 0; i < numLocations; ++i)
-              pyLocations.emplace_back(self.getContext(), locations[i]);
-            return pyLocations;
-          },
-          "Gets the list of locations from a `FusedLoc`.")
+          "Alias for `FileLineColLoc.get()` over a range.")
       .def_static(
           "name",
           [](std::string name, std::optional<PyLocation> childLoc,
              DefaultingPyMlirContext context) {
-            return PyLocation(
+            return PyNameLocation(
                 context->getRef(),
                 mlirLocationNameGet(
                     context->get(), toMlirStringRef(name),
@@ -3460,31 +3625,38 @@ void populateIRCore(nb::module_ &m) {
                              : mlirLocationUnknownGet(context->get())));
           },
           "name"_a, "childLoc"_a = nb::none(), "context"_a = nb::none(),
-          "Gets a Location representing a named location with optional child "
-          "location.")
-      .def("is_a_name", mlirLocationIsAName,
-           "Returns True if this location is a `NameLoc`.")
-      .def_prop_ro(
-          "name_str",
-          [](PyLocation loc) {
-            return mlirIdentifierStr(mlirLocationNameGetName(loc));
-          },
-          "Gets the name string from a `NameLoc`.")
-      .def_prop_ro(
-          "child_loc",
-          [](PyLocation &self) {
-            return PyLocation(self.getContext(),
-                              mlirLocationNameGetChildLoc(self));
+          "Alias for `NameLoc.get()`.")
+      .def_static(
+          "callsite",
+          [](PyLocation callee, const std::vector<PyLocation> &frames,
+             DefaultingPyMlirContext context) {
+            if (frames.empty())
+              throw nb::value_error("No caller frames provided.");
+            MlirLocation caller = frames.back().get();
+            for (size_t index = frames.size() - 1; index-- > 0;)
+              caller = mlirLocationCallSiteGet(frames[index].get(), caller);
+            return PyCallSiteLocation(
+                context->getRef(),
+                mlirLocationCallSiteGet(callee.get(), caller));
           },
-          "Gets the child location from a `NameLoc`.")
+          "callee"_a, "frames"_a, "context"_a = nb::none(),
+          "Alias for `CallSiteLoc.get()`.")
       .def_static(
-          "from_attr",
-          [](PyAttribute &attribute, DefaultingPyMlirContext context) {
-            return PyLocation(context->getRef(),
-                              mlirLocationFromAttribute(attribute));
+          "fused",
+          [](const std::vector<PyLocation> &pyLocations,
+             std::optional<PyAttribute> metadata,
+             DefaultingPyMlirContext context) {
+            std::vector<MlirLocation> locations;
+            locations.reserve(pyLocations.size());
+            for (const PyLocation &pyLocation : pyLocations)
+              locations.push_back(pyLocation.get());
+            MlirLocation location = mlirLocationFusedGet(
+                context->get(), locations.size(), locations.data(),
+                metadata ? metadata->get() : MlirAttribute{0});
+            return PyLocation(context->getRef(), location).maybeDownCast();
           },
-          "attribute"_a, "context"_a = nb::none(),
-          "Gets a Location from a `LocationAttr`.")
+          "locations"_a, "metadata"_a = nb::none(), "context"_a = nb::none(),
+          "Alias for `FusedLoc.get()` (may collapse to a non-fused location).")
       .def_prop_ro(
           "context",
           [](PyLocation &self) -> nb::typed<nb::object, PyMlirContext> {
@@ -3519,6 +3691,13 @@ void populateIRCore(nb::module_ &m) {
           },
           "Returns the assembly representation of the location.");
 
+  PyUnknownLocation::bind(m);
+  PyFileLineColLocation::bind(m);
+  PyNameLocation::bind(m);
+  PyCallSiteLocation::bind(m);
+  PyFusedLocation::bind(m);
+  PyOpaqueLocation::bind(m);
+
   //----------------------------------------------------------------------------
   // Mapping of Module
   //----------------------------------------------------------------------------
@@ -3727,7 +3906,8 @@ void populateIRCore(nb::module_ &m) {
           [](PyOperationBase &self) {
             PyOperation &operation = self.getOperation();
             return PyLocation(operation.getContext(),
-                              mlirOperationGetLocation(operation.get()));
+                              mlirOperationGetLocation(operation.get()))
+                .maybeDownCast();
           },
           [](PyOperationBase &self, const PyLocation &location) {
             PyOperation &operation = self.getOperation();
@@ -4905,8 +5085,9 @@ void populateIRCore(nb::module_ &m) {
           "location",
           [](PyValue self) {
             return PyLocation(
-                PyMlirContext::forContext(mlirValueGetContext(self)),
-                mlirValueGetLocation(self));
+                       PyMlirContext::forContext(mlirValueGetContext(self)),
+                       mlirValueGetLocation(self))
+                .maybeDownCast();
           },
           "Returns the source location of the value.");
 
diff --git a/mlir/lib/CAPI/IR/IR.cpp b/mlir/lib/CAPI/IR/IR.cpp
index 481130e5069db..100d28b179e6a 100644
--- a/mlir/lib/CAPI/IR/IR.cpp
+++ b/mlir/lib/CAPI/IR/IR.cpp
@@ -403,6 +403,42 @@ MlirLocation mlirLocationUnknownGet(MlirContext context) {
   return wrap(Location(UnknownLoc::get(unwrap(context))));
 }
 
+MlirTypeID mlirLocationUnknownGetTypeID() {
+  return wrap(UnknownLoc::getTypeID());
+}
+
+bool mlirLocationIsAUnknown(MlirLocation location) {
+  return isa<UnknownLoc>(unwrap(location));
+}
+
+MlirLocation mlirLocationOpaqueGet(uintptr_t underlyingLocation,
+                                   MlirTypeID underlyingTypeID,
+                                   MlirLocation fallbackLocation) {
+  return wrap(Location(OpaqueLoc::get(
+      underlyingLocation, unwrap(underlyingTypeID), unwrap(fallbackLocation))));
+}
+
+uintptr_t mlirLocationOpaqueGetUnderlyingLocation(MlirLocation location) {
+  return llvm::cast<OpaqueLoc>(unwrap(location)).getUnderlyingLocation();
+}
+
+MlirTypeID mlirLocationOpaqueGetUnderlyingTypeID(MlirLocation location) {
+  return wrap(llvm::cast<OpaqueLoc>(unwrap(location)).getUnderlyingTypeID());
+}
+
+MlirLocation mlirLocationOpaqueGetFallbackLocation(MlirLocation location) {
+  return wrap(
+      Location(llvm::cast<OpaqueLoc>(unwrap(location)).getFallbackLocation()));
+}
+
+MlirTypeID mlirLocationOpaqueGetTypeID() {
+  return wrap(OpaqueLoc::getTypeID());
+}
+
+bool mlirLocationIsAOpaque(MlirLocation location) {
+  return isa<OpaqueLoc>(unwrap(location));
+}
+
 bool mlirLocationEqual(MlirLocation l1, MlirLocation l2) {
   return unwrap(l1) == unwrap(l2);
 }
diff --git a/mlir/test/CAPI/ir.c b/mlir/test/CAPI/ir.c
index e66c931383f89..359528b6a3a3c 100644
--- a/mlir/test/CAPI/ir.c
+++ b/mlir/test/CAPI/ir.c
@@ -2376,6 +2376,41 @@ void testExplicitThreadPools(void) {
   mlirLlvmThreadPoolDestroy(threadPool);
 }
 
+void testLocation(void) {
+  MlirContext ctx = mlirContextCreate();
+  fprintf(stderr, "@test_location\n");
+
+  MlirLocation unknownLoc = mlirLocationUnknownGet(ctx);
+  MlirLocation fileLoc = mlirLocationFileLineColGet(
+      ctx, mlirStringRefCreateFromCString("foo.c"), 1, 2);
+
+  // CHECK-LABEL: @test_location
+  // CHECK: unknown is_a_unknown: 1
+  fprintf(stderr, "unknown is_a_unknown: %d\n",
+          mlirLocationIsAUnknown(unknownLoc));
+  // CHECK: file is_a_unknown: 0
+  fprintf(stderr, "file is_a_unknown: %d\n", mlirLocationIsAUnknown(fileLoc));
+
+  MlirTypeID typeID = mlirLocationFileLineColRangeGetTypeID();
+  MlirLocation opaqueLoc =
+      mlirLocationOpaqueGet((uintptr_t)0xcafef00d, typeID, fileLoc);
+  // CHECK: opaque is_a_opaque: 1
+  fprintf(stderr, "opaque is_a_opaque: %d\n", mlirLocationIsAOpaque(opaqueLoc));
+  // CHECK: opaque underlying: cafef00d
+  fprintf(stderr, "opaque underlying: %" PRIxPTR "\n",
+          mlirLocationOpaqueGetUnderlyingLocation(opaqueLoc));
+  // CHECK: opaque typeid matches: 1
+  fprintf(stderr, "opaque typeid matches: %d\n",
+          mlirTypeIDEqual(mlirLocationOpaqueGetUnderlyingTypeID(opaqueLoc),
+                          typeID));
+  // CHECK: opaque fallback equal: 1
+  fprintf(stderr, "opaque fallback equal: %d\n",
+          mlirLocationEqual(mlirLocationOpaqueGetFallbackLocation(opaqueLoc),
+                            fileLoc));
+
+  mlirContextDestroy(ctx);
+}
+
 void testDiagnostics(void) {
   MlirContext ctx = mlirContextCreate();
   MlirDiagnosticHandlerID id = mlirContextAttachDiagnosticHandler(
@@ -2552,6 +2587,7 @@ int main(void) {
     return 16;
 
   testExplicitThreadPools();
+  testLocation();
   testDiagnostics();
 
   if (testBlockPredecessorsSuccessors(ctx))
diff --git a/mlir/test/python/ir/location.py b/mlir/test/python/ir/location.py
index 3e54dc922cd67..97491facc4a2f 100644
--- a/mlir/test/python/ir/location.py
+++ b/mlir/test/python/ir/location.py
@@ -14,7 +14,7 @@ def run(f):
 # CHECK-LABEL: TEST: testUnknown
 def testUnknown():
     with Context() as ctx:
-        loc = Location.unknown()
+        loc = UnknownLoc.get()
     assert loc.context is ctx
     ctx = None
     gc.collect()
@@ -23,6 +23,14 @@ def testUnknown():
     # CHECK: unknown repr: loc(unknown)
     print("unknown repr:", repr(loc))
 
+    assert isinstance(loc, UnknownLoc)
+    assert isinstance(loc, Location)
+    assert not isinstance(loc, FileLineColLoc)
+    assert not isinstance(loc, NameLoc)
+    assert not isinstance(loc, CallSiteLoc)
+    assert not isinstance(loc, FusedLoc)
+    assert not isinstance(loc, OpaqueLoc)
+
 
 run(testUnknown)
 
@@ -30,7 +38,7 @@ def testUnknown():
 # CHECK-LABEL: TEST: testLocationAttr
 def testLocationAttr():
     with Context() as ctxt:
-        loc = Location.unknown()
+        loc = UnknownLoc.get()
         attr = loc.attr
         clone = Location.from_attr(attr)
     gc.collect()
@@ -39,6 +47,7 @@ def testLocationAttr():
     # CHECK: clone: loc(unknown)
     print("clone:", str(clone))
     assert loc == clone
+    assert isinstance(clone, UnknownLoc)
 
 
 run(testLocationAttr)
@@ -47,8 +56,8 @@ def testLocationAttr():
 # CHECK-LABEL: TEST: testFileLineCol
 def testFileLineCol():
     with Context() as ctx:
-        loc = Location.file("foo1.txt", 123, 56)
-        range = Location.file("foo2.txt", 123, 56, 124, 100)
+        loc = FileLineColLoc.get("foo1.txt", 123, 56)
+        range = FileLineColLoc.get("foo2.txt", 123, 56, 124, 100)
 
     ctx = None
     gc.collect()
@@ -62,10 +71,10 @@ def testFileLineCol():
     # CHECK: file range repr: loc("foo2.txt":123:56 to 124:100)
     print("file range repr:", repr(range))
 
-    assert loc.is_a_file()
-    assert not loc.is_a_name()
-    assert not loc.is_a_callsite()
-    assert not loc.is_a_fused()
+    assert isinstance(loc, FileLineColLoc)
+    assert not isinstance(loc, NameLoc)
+    assert not isinstance(loc, CallSiteLoc)
+    assert not isinstance(loc, FusedLoc)
 
     # CHECK: file filename: foo1.txt
     print("file filename:", loc.filename)
@@ -78,7 +87,7 @@ def testFileLineCol():
     # CHECK: file end_col: 56
     print("file end_col:", loc.end_col)
 
-    assert range.is_a_file()
+    assert isinstance(range, FileLineColLoc)
     # CHECK: file filename: foo2.txt
     print("file filename:", range.filename)
     # CHECK: file start_line: 123
@@ -92,7 +101,7 @@ def testFileLineCol():
 
     with Context() as ctx:
         ctx.allow_unregistered_dialects = True
-        loc = Location.file("foo3.txt", 127, 61)
+        loc = FileLineColLoc.get("foo3.txt", 127, 61)
         with loc:
             i32 = IntegerType.get_signless(32)
             module = Module.create()
@@ -100,6 +109,7 @@ def testFileLineCol():
                 new_value = Operation.create("custom.op1", results=[i32]).result
                 # CHECK: new_value location: loc("foo3.txt":127:61)
                 print("new_value location: ", new_value.location)
+                assert isinstance(new_value.location, FileLineColLoc)
 
 
 run(testFileLineCol)
@@ -108,8 +118,8 @@ def testFileLineCol():
 # CHECK-LABEL: TEST: testName
 def testName():
     with Context() as ctx:
-        loc = Location.name("nombre")
-        loc_with_child_loc = Location.name("naam", loc)
+        loc = NameLoc.get("nombre")
+        loc_with_child_loc = NameLoc.get("naam", loc)
 
     ctx = None
     gc.collect()
@@ -123,17 +133,19 @@ def testName():
     # CHECK: name repr: loc("naam"("nombre"))
     print("name repr:", repr(loc_with_child_loc))
 
-    assert loc.is_a_name()
+    assert isinstance(loc, NameLoc)
     # CHECK: name name_str: nombre
     print("name name_str:", loc.name_str)
     # CHECK: name child_loc: loc(unknown)
     print("name child_loc:", loc.child_loc)
+    assert isinstance(loc.child_loc, UnknownLoc)
 
-    assert loc_with_child_loc.is_a_name()
+    assert isinstance(loc_with_child_loc, NameLoc)
     # CHECK: name name_str: naam
     print("name name_str:", loc_with_child_loc.name_str)
     # CHECK: name child_loc_with_child_loc: loc("nombre")
     print("name child_loc_with_child_loc:", loc_with_child_loc.child_loc)
+    assert isinstance(loc_with_child_loc.child_loc, NameLoc)
 
 
 run(testName)
@@ -142,9 +154,12 @@ def testName():
 # CHECK-LABEL: TEST: testCallSite
 def testCallSite():
     with Context() as ctx:
-        loc = Location.callsite(
-            Location.file("foo.text", 123, 45),
-            [Location.file("util.foo", 379, 21), Location.file("main.foo", 100, 63)],
+        loc = CallSiteLoc.get(
+            FileLineColLoc.get("foo.text", 123, 45),
+            [
+                FileLineColLoc.get("util.foo", 379, 21),
+                FileLineColLoc.get("main.foo", 100, 63),
+            ],
         )
     ctx = None
     # CHECK: callsite str: loc(callsite("foo.text":123:45 at callsite("util.foo":379:21 at "main.foo":100:63))
@@ -152,12 +167,13 @@ def testCallSite():
     # CHECK: callsite repr: loc(callsite("foo.text":123:45 at callsite("util.foo":379:21 at "main.foo":100:63))
     print("callsite repr:", repr(loc))
 
-    assert loc.is_a_callsite()
-
+    assert isinstance(loc, CallSiteLoc)
     # CHECK: callsite callee: loc("foo.text":123:45)
     print("callsite callee:", loc.callee)
+    assert isinstance(loc.callee, FileLineColLoc)
     # CHECK: callsite caller: loc(callsite("util.foo":379:21 at "main.foo":100:63))
     print("callsite caller:", loc.caller)
+    assert isinstance(loc.caller, CallSiteLoc)
 
 
 run(testCallSite)
@@ -166,35 +182,39 @@ def testCallSite():
 # CHECK-LABEL: TEST: testFused
 def testFused():
     with Context() as ctx:
-        loc_single = Location.fused([Location.name("apple")])
-        loc = Location.fused([Location.name("apple"), Location.name("banana")])
+        loc_single = FusedLoc.get([NameLoc.get("apple")])
+        loc = FusedLoc.get([NameLoc.get("apple"), NameLoc.get("banana")])
         attr = Attribute.parse('"sauteed"')
-        loc_attr = Location.fused(
-            [Location.name("carrot"), Location.name("potatoes")], attr
+        loc_attr = FusedLoc.get(
+            [NameLoc.get("carrot"), NameLoc.get("potatoes")], attr
         )
-        loc_empty = Location.fused([])
-        loc_empty_attr = Location.fused([], attr)
-        loc_single_attr = Location.fused([Location.name("apple")], attr)
+        loc_empty = FusedLoc.get([])
+        loc_empty_attr = FusedLoc.get([], attr)
+        loc_single_attr = FusedLoc.get([NameLoc.get("apple")], attr)
 
     ctx = None
 
-    assert not loc_single.is_a_fused()
+    # Fusing a single location without metadata returns that location unchanged.
+    assert not isinstance(loc_single, FusedLoc)
+    assert isinstance(loc_single, NameLoc)
     # CHECK: fused str: loc("apple")
     print("fused str:", str(loc_single))
     # CHECK: fused repr: loc("apple")
     print("fused repr:", repr(loc_single))
-    # # CHECK: fused locations: []
-    print("fused locations:", loc_single.locations)
 
-    assert loc.is_a_fused()
+    assert isinstance(loc, FusedLoc)
     # CHECK: fused str: loc(fused["apple", "banana"])
     print("fused str:", str(loc))
     # CHECK: fused repr: loc(fused["apple", "banana"])
     print("fused repr:", repr(loc))
     # CHECK: fused locations: [loc("apple"), loc("banana")]
     print("fused locations:", loc.locations)
+    # CHECK: fused metadata: None
+    print("fused metadata:", loc.metadata)
 
-    assert loc_attr.is_a_fused()
+    assert isinstance(loc_attr, FusedLoc)
+    # CHECK: fused metadata: "sauteed"
+    print("fused metadata:", loc_attr.metadata)
     # CHECK: fused str: loc(fused<"sauteed">["carrot", "potatoes"])
     print("fused str:", str(loc_attr))
     # CHECK: fused repr: loc(fused<"sauteed">["carrot", "potatoes"])
@@ -202,15 +222,15 @@ def testFused():
     # CHECK: fused locations: [loc("carrot"), loc("potatoes")]
     print("fused locations:", loc_attr.locations)
 
-    assert not loc_empty.is_a_fused()
+    # Empty fuse without metadata collapses to unknown.
+    assert not isinstance(loc_empty, FusedLoc)
+    assert isinstance(loc_empty, UnknownLoc)
     # CHECK: fused str: loc(unknown)
     print("fused str:", str(loc_empty))
     # CHECK: fused repr: loc(unknown)
     print("fused repr:", repr(loc_empty))
-    # CHECK: fused locations: []
-    print("fused locations:", loc_empty.locations)
 
-    assert loc_empty_attr.is_a_fused()
+    assert isinstance(loc_empty_attr, FusedLoc)
     # CHECK: fused str: loc(fused<"sauteed">[unknown])
     print("fused str:", str(loc_empty_attr))
     # CHECK: fused repr: loc(fused<"sauteed">[unknown])
@@ -218,7 +238,7 @@ def testFused():
     # CHECK: fused locations: [loc(unknown)]
     print("fused locations:", loc_empty_attr.locations)
 
-    assert loc_single_attr.is_a_fused()
+    assert isinstance(loc_single_attr, FusedLoc)
     # CHECK: fused str: loc(fused<"sauteed">["apple"])
     print("fused str:", str(loc_single_attr))
     # CHECK: fused repr: loc(fused<"sauteed">["apple"])
@@ -230,10 +250,61 @@ def testFused():
 run(testFused)
 
 
+# CHECK-LABEL: TEST: testOpaque
+def testOpaque():
+    with Context() as ctx:
+        fallback = FileLineColLoc.get("fallback.txt", 42, 7)
+        type_id = IntegerType.get_signless(32).typeid
+        loc = OpaqueLoc.get(0xDEADBEEF, type_id, fallback)
+
+    ctx = None
+    gc.collect()
+
+    # Printing falls back to the fallback location.
+    # CHECK: opaque str: loc("fallback.txt":42:7)
+    print("opaque str:", str(loc))
+
+    assert isinstance(loc, OpaqueLoc)
+    assert not isinstance(loc, UnknownLoc)
+    assert not isinstance(loc, FileLineColLoc)
+
+    # CHECK: opaque underlying_location: 0xdeadbeef
+    print("opaque underlying_location:", hex(loc.underlying_location))
+    assert loc.underlying_type_id == type_id
+    assert loc.fallback_location == fallback
+    assert isinstance(loc.fallback_location, FileLineColLoc)
+
+
+run(testOpaque)
+
+
+# CHECK-LABEL: TEST: testCast
+def testCast():
+    with Context() as ctx:
+        unknown = UnknownLoc.get()
+        # Explicit cast from base Location to subclass succeeds for a match.
+        as_unknown = UnknownLoc(unknown)
+        assert isinstance(as_unknown, UnknownLoc)
+
+        # Casting to the wrong subclass raises.
+        try:
+            FileLineColLoc(unknown)
+        except ValueError as e:
+            # CHECK: cast error: Cannot cast location to FileLineColLoc
+            print("cast error:", str(e))
+        else:
+            assert False, "expected ValueError"
+
+    ctx = None
+
+
+run(testCast)
+
+
 # CHECK-LABEL: TEST: testLocationCapsule
 def testLocationCapsule():
     with Context() as ctx:
-        loc1 = Location.file("foo.txt", 123, 56)
+        loc1 = FileLineColLoc.get("foo.txt", 123, 56)
     # CHECK: mlir.ir.Location._CAPIPtr
     loc_capsule = loc1._CAPIPtr
     print(loc_capsule)



More information about the Mlir-commits mailing list