Addresses #53169. Mirrors the Python bindings pattern used for Attribute
subclasses so that Location discrimination uses `isinstance`, and fills
two small gaps at the same time.
### Approach
Previously `Location` was a single nanobind class with `is_a_file`,
`is_a_name`, etc. predicates, plus field accessors for every kind
defined on the base class. This PR introduces a `PyConcreteLocation<T>`
CRTP template (parallel to `PyConcreteAttribute<T>`) and registers one
subclass per `LocationAttr` kind: `UnknownLoc`, `FileLineColLoc`,
`NameLoc`, `CallSiteLoc`, `FusedLoc`.
TypeID-based downcasting is implemented in `PyLocation::maybeDownCast`
(using `mlirAttributeGetTypeID(mlirLocationGetAttribute(...))`) and
called at the boundaries that return Location objects: `op.location`,
`value.location`, `Location.from_attr`, and the subclass getters
themselves.
### Example
```python
from mlir.ir import Context, Module, FileLineColLoc, NameLoc, FusedLoc
with Context():
module = Module.parse("...")
for op in module.body.operations:
loc = op.location
if isinstance(loc, FileLineColLoc):
report(f"{loc.filename}:{loc.start_line}:{loc.start_col}")
elif isinstance(loc, NameLoc):
report(loc.name_str)
elif isinstance(loc, FusedLoc) and loc.metadata is not None:
report_fused(loc.locations, loc.metadata)
```
`FusedLoc.metadata` is also newly exposed (the underlying C API
`mlirLocationFusedGetMetadata` already existed but was not bound).
### Custom/downstream Location classes
`PyConcreteLocation<DerivedTy>` is marked `MLIR_PYTHON_API_EXPORTED` and
is used identically to `PyConcreteAttribute<DerivedTy>`. Downstream
projects that define their own `LocationAttr` in C++ expose a matching
Python class by declaring a subclass with the standard statics
(`isaFunction`, `pyClassName`, optional `getTypeIdFunction`) and calling
its `bind(m)` at module init; `isinstance` and automatic downcasting
then work with no further changes in the base bindings.
### Backward compatibility
Existing factories — `Location.unknown()`, `Location.file(...)`,
`Location.name(...)`, `Location.callsite(...)`, `Location.fused(...)` —
remain as aliases and now return the concrete subclass instance (so
`isinstance(Location.file(...), FileLineColLoc)` is true). No user
migration is required for construction.
The old `is_a_*` predicate methods are removed; downstream consumers
should switch to `isinstance(...)`. No in-tree users of the removed
predicates besides the existing Location tests, which have been
rewritten accordingly.
### Scope note — OpaqueLoc
OpaqueLoc is intentionally not included. It is keyed on a C++
`TypeID::get<T>()` tag that has no natural Python counterpart, and I
could not find a downstream consumer asking for Python exposure. An
earlier revision of this PR exposed it with an `id(obj)` + `ctypes.cast`
example, but that pattern is lifetime-unsafe (MLIR does not INCREF the
underlying pointer) and the C++ `getUnderlyingLocation<T>()` semantics
don't translate cleanly. Happy to add it in a follow-up once a real use
case surfaces.
### Changes
C API (`mlir/include/mlir-c/IR.h`, `mlir/lib/CAPI/IR/IR.cpp`):
- `mlirLocationIsAUnknown`, `mlirLocationUnknownGetTypeID` — other
Location kinds already had their `IsA*`/`*GetTypeID` pair.
Python (`mlir/include/mlir/Bindings/Python/IRCore.h`,
`mlir/lib/Bindings/Python/IRCore.cpp`):
- `PyConcreteLocation<T>` template and five concrete classes
- `PyLocation::maybeDownCast`
- Subclass `.get()` constructors, `.metadata` on `FusedLoc`
Tests:
- `mlir/test/python/ir/location.py` rewritten around `isinstance` and
subclass construction; adds explicit-cast-failure coverage.
- `mlir/test/CAPI/ir.c` adds `testLocation` covering the new
`mlirLocationIsAUnknown` predicate.
### Tool-use disclosure
This change was drafted with Claude (Anthropic) assistance per LLVM's
[AI Tool Use Policy](https://llvm.org/docs/AIToolPolicy.html). All code
was read, reviewed, and tested locally by me; the commit carries
`Assisted-by:` and `Co-Authored-By:` trailers accordingly.
---------
Co-authored-by: Claude (Anthropic) <noreply@anthropic.com>