Files
clang-p2996/mlir/test/python/ir/diagnostic_handler.py
Rahul Kayaith 3ea4c5014d [mlir][python] Capture error diagnostics in exceptions
This updates most (all?) error-diagnostic-emitting python APIs to
capture error diagnostics and include them in the raised exception's
message:
```
>>> Operation.parse('"arith.addi"() : () -> ()'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
mlir._mlir_libs.MLIRError: Unable to parse operation assembly:
error: "-":1:1: 'arith.addi' op requires one result
 note: "-":1:1: see current operation: "arith.addi"() : () -> ()
```

The diagnostic information is available on the exception for users who
may want to customize the error message:
```
>>> try:
...   Operation.parse('"arith.addi"() : () -> ()')
... except MLIRError as e:
...   print(e.message)
...   print(e.error_diagnostics)
...   print(e.error_diagnostics[0].message)
...
Unable to parse operation assembly
[<mlir._mlir_libs._mlir.ir.DiagnosticInfo object at 0x7fed32bd6b70>]
'arith.addi' op requires one result
```

Error diagnostics captured in exceptions aren't propagated to diagnostic
handlers, to avoid double-reporting of errors. The context-level
`emit_error_diagnostics` option can be used to revert to the old
behaviour, causing error diagnostics to be reported to handlers instead
of as part of exceptions.

API changes:
- `Operation.verify` now raises an exception on verification failure,
  instead of returning `false`
- The exception raised by the following methods has been changed to
  `MLIRError`:
  - `PassManager.run`
  - `{Module,Operation,Type,Attribute}.parse`
  - `{RankedTensorType,UnrankedTensorType}.get`
  - `{MemRefType,UnrankedMemRefType}.get`
  - `VectorType.get`
  - `FloatAttr.get`

closes #60595

depends on D144804, D143830

Reviewed By: stellaraccident

Differential Revision: https://reviews.llvm.org/D143869
2023-03-07 14:59:22 -05:00

194 lines
4.7 KiB
Python

# RUN: %PYTHON %s | FileCheck %s
import gc
from mlir.ir import *
def run(f):
print("\nTEST:", f.__name__)
f()
gc.collect()
assert Context._get_live_count() == 0
return f
@run
def testLifecycleContextDestroy():
ctx = Context()
def callback(foo): ...
handler = ctx.attach_diagnostic_handler(callback)
assert handler.attached
# If context is destroyed before the handler, it should auto-detach.
ctx = None
gc.collect()
assert not handler.attached
# And finally collecting the handler should be fine.
handler = None
gc.collect()
@run
def testLifecycleExplicitDetach():
ctx = Context()
def callback(foo): ...
handler = ctx.attach_diagnostic_handler(callback)
assert handler.attached
handler.detach()
assert not handler.attached
@run
def testLifecycleWith():
ctx = Context()
def callback(foo): ...
with ctx.attach_diagnostic_handler(callback) as handler:
assert handler.attached
assert not handler.attached
@run
def testLifecycleWithAndExplicitDetach():
ctx = Context()
def callback(foo): ...
with ctx.attach_diagnostic_handler(callback) as handler:
assert handler.attached
handler.detach()
assert not handler.attached
# CHECK-LABEL: TEST: testDiagnosticCallback
@run
def testDiagnosticCallback():
ctx = Context()
def callback(d):
# CHECK: DIAGNOSTIC: message='foobar', severity=DiagnosticSeverity.ERROR, loc=loc(unknown)
print(f"DIAGNOSTIC: message='{d.message}', severity={d.severity}, loc={d.location}")
return True
handler = ctx.attach_diagnostic_handler(callback)
loc = Location.unknown(ctx)
loc.emit_error("foobar")
assert not handler.had_error
# CHECK-LABEL: TEST: testDiagnosticEmptyNotes
# TODO: Come up with a way to inject a diagnostic with notes from this API.
@run
def testDiagnosticEmptyNotes():
ctx = Context()
def callback(d):
# CHECK: DIAGNOSTIC: notes=()
print(f"DIAGNOSTIC: notes={d.notes}")
return True
handler = ctx.attach_diagnostic_handler(callback)
loc = Location.unknown(ctx)
loc.emit_error("foobar")
assert not handler.had_error
# CHECK-LABEL: TEST: testDiagnosticNonEmptyNotes
@run
def testDiagnosticNonEmptyNotes():
ctx = Context()
ctx.emit_error_diagnostics = True
def callback(d):
# CHECK: DIAGNOSTIC:
# CHECK: message='arith.addi' op requires one result
# CHECK: notes=['see current operation: "arith.addi"() : () -> ()']
print(f"DIAGNOSTIC:")
print(f" message={d.message}")
print(f" notes={list(map(str, d.notes))}")
return True
handler = ctx.attach_diagnostic_handler(callback)
loc = Location.unknown(ctx)
try:
Operation.create('arith.addi', loc=loc).verify()
except MLIRError:
pass
assert not handler.had_error
# CHECK-LABEL: TEST: testDiagnosticCallbackException
@run
def testDiagnosticCallbackException():
ctx = Context()
def callback(d):
raise ValueError("Error in handler")
handler = ctx.attach_diagnostic_handler(callback)
loc = Location.unknown(ctx)
loc.emit_error("foobar")
assert handler.had_error
# CHECK-LABEL: TEST: testEscapingDiagnostic
@run
def testEscapingDiagnostic():
ctx = Context()
diags = []
def callback(d):
diags.append(d)
return True
handler = ctx.attach_diagnostic_handler(callback)
loc = Location.unknown(ctx)
loc.emit_error("foobar")
assert not handler.had_error
# CHECK: DIAGNOSTIC: <Invalid Diagnostic>
print(f"DIAGNOSTIC: {str(diags[0])}")
try:
diags[0].severity
raise RuntimeError("expected exception")
except ValueError:
pass
try:
diags[0].location
raise RuntimeError("expected exception")
except ValueError:
pass
try:
diags[0].message
raise RuntimeError("expected exception")
except ValueError:
pass
try:
diags[0].notes
raise RuntimeError("expected exception")
except ValueError:
pass
# CHECK-LABEL: TEST: testDiagnosticReturnTrueHandles
@run
def testDiagnosticReturnTrueHandles():
ctx = Context()
def callback1(d):
print(f"CALLBACK1: {d}")
return True
def callback2(d):
print(f"CALLBACK2: {d}")
return True
ctx.attach_diagnostic_handler(callback1)
ctx.attach_diagnostic_handler(callback2)
loc = Location.unknown(ctx)
# CHECK-NOT: CALLBACK1
# CHECK: CALLBACK2: foobar
# CHECK-NOT: CALLBACK1
loc.emit_error("foobar")
# CHECK-LABEL: TEST: testDiagnosticReturnFalseDoesNotHandle
@run
def testDiagnosticReturnFalseDoesNotHandle():
ctx = Context()
def callback1(d):
print(f"CALLBACK1: {d}")
return True
def callback2(d):
print(f"CALLBACK2: {d}")
return False
ctx.attach_diagnostic_handler(callback1)
ctx.attach_diagnostic_handler(callback2)
loc = Location.unknown(ctx)
# CHECK: CALLBACK2: foobar
# CHECK: CALLBACK1: foobar
loc.emit_error("foobar")