
Updated the Python diagnostics handler to emit notes (in addition to errors) into the output stream so that users have more context as to where in the IR the error is occurring.
241 lines
5.5 KiB
Python
241 lines
5.5 KiB
Python
# RUN: %PYTHON %s | FileCheck %s
|
|
|
|
import gc
|
|
from mlir.ir import *
|
|
from mlir._mlir_libs._mlirPythonTestNanobind import (
|
|
test_diagnostics_with_errors_and_notes,
|
|
)
|
|
|
|
|
|
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")
|
|
|
|
|
|
# CHECK-LABEL: TEST: testBuiltInDiagnosticsHandler
|
|
@run
|
|
def testBuiltInDiagnosticsHandler():
|
|
ctx = Context()
|
|
|
|
try:
|
|
test_diagnostics_with_errors_and_notes(ctx)
|
|
except ValueError as e:
|
|
# CHECK: created error
|
|
# CHECK: attached note
|
|
print(e)
|