This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch script/canonical-parser-df
in repository https://gitbox.apache.org/repos/asf/tvm.git

commit 8d3f02362a7f0bebcc52489bf8a85bbdd742a4fe
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Sep 23 18:35:16 2026 +0000

    [Script] Propagate original errors and consolidate source inspection
---
 docs/arch/tvmscript.rst                            |  15 +-
 python/tvm/relax/script/__init__.py                |   5 +-
 python/tvm/relax/script/builder/parser_protocol.py |   2 +-
 python/tvm/script/ir_builder/base.py               |  33 +--
 python/tvm/script/ir_builder/ir/parser_protocol.py |   2 +-
 python/tvm/script/parser/call_args_policy.py       |  12 +-
 python/tvm/script/parser/diagnostics.py            | 159 --------------
 python/tvm/script/parser/entry.py                  |  31 ++-
 python/tvm/script/parser/inspect_source.py         | 112 +++++++++-
 python/tvm/script/parser/source.py                 | 125 -----------
 python/tvm/script/parser/transpile.py              |  19 +-
 python/tvm/tirx/script/builder/parser_protocol.py  |   2 +-
 .../test_frontend_relax_construction_validation.py |   3 +-
 .../test_relax_parser_comparison_integration.py    |   4 +-
 tests/python/relax/test_op_view.py                 |  21 +-
 tests/python/relax/test_transform.py               |  12 +-
 tests/python/relax/test_tvmscript_parser.py        |  97 +++++----
 .../s_tir/test_s_tir_axis_binding_validation.py    |   3 +-
 .../python/script/test_capture_definition_scope.py |   9 +-
 tests/python/script/test_capture_frontend_scope.py |   2 +-
 tests/python/script/test_parser.py                 |  29 +--
 .../python/script/test_parser_capture_lifetime.py  |  33 ++-
 tests/python/script/test_parser_host_selection.py  |  22 +-
 tests/python/script/test_parser_latest_design.py   |  47 ++++-
 tests/python/script/test_parser_scope_helpers.py   |  11 +-
 tests/python/script/test_parser_scoped_symbols.py  |   7 +-
 .../python/tirx/codegen/test_codegen_blackwell.py  |  10 +-
 tests/python/tirx/codegen/test_codegen_cuda.py     |   2 +-
 tests/python/tirx/codegen/test_ptx_addr.py         |  24 +--
 tests/python/tirx/codegen/test_ptx_dialect.py      | 132 ++++++------
 .../test_frontend_construction_validation.py       |   5 +-
 .../script/test_native_span_and_loop_contracts.py  |  21 +-
 .../python/tirx/script/test_parser_buffer_views.py |   3 +-
 .../tirx/script/test_parser_jit_specialization.py  |   6 +-
 .../tirx/script/test_parser_tirx_emission.py       |   5 +-
 .../test_tirx_parser_comparison_integration.py     |   4 +-
 tests/python/tirx/test_jit.py                      |  21 +-
 tests/python/tirx/test_parser_printer.py           |  19 +-
 .../tvmscript/test_tvmscript_error_report.py       | 232 +++++++++------------
 tests/python/tvmscript/test_tvmscript_ops.py       |   2 +-
 .../tvmscript/test_tvmscript_parser_source.py      |   4 +-
 .../python/tvmscript/test_tvmscript_parser_tir.py  |   8 +-
 .../tvmscript/test_tvmscript_syntax_sugar.py       |   2 +-
 43 files changed, 585 insertions(+), 732 deletions(-)

diff --git a/docs/arch/tvmscript.rst b/docs/arch/tvmscript.rst
index a3aae7c1e9..df5d045485 100644
--- a/docs/arch/tvmscript.rst
+++ b/docs/arch/tvmscript.rst
@@ -156,8 +156,10 @@ mechanism.
 Parser Architecture
 -------------------
 
-The parser lives in ``python/tvm/script/parser/``. Documented functions in 
``entry.py``
-manage source acquisition, definition-context capture, callable composition 
and execution.
+The parser lives in ``python/tvm/script/parser/``. ``inspect_source.py`` owns 
source
+acquisition, the ``Source`` representation and its coordinate helpers, and 
definition-context
+capture. Documented functions in ``entry.py`` manage the parse sequence, 
callable composition
+and execution.
 The transpiler handles syntax; the builders interpret concrete values. TIRx 
JIT validation,
 defaults and specialization caching live in ``tvm.tirx.script.jit``; shared 
parser utilities
 remain independent of that policy.
@@ -170,8 +172,8 @@ then lowers to operations supplied by that namespace: for 
example, unmarked ``if
 TIR control-flow frames in a primitive function and Relax control-flow frames 
in a Relax
 function. The transpiler emits these operations without implementing dialect 
IR semantics.
 
-``protocol.py`` records callable syntax metadata. Argument policies such as 
``expr_str``
-translate symbolic strings written directly in source expressions, while 
``global_info``
+``ir_builder/ir/parser_protocol.py`` records callable syntax metadata. 
Argument policies such
+as ``expr_str`` translate symbolic strings written directly in source 
expressions, while ``global_info``
 preserves a module reference for builder-side lookup. Dtype and placement 
strings remain
 literal. Captured or computed symbolic shapes must already contain explicit IR 
variables;
 the parser does not interpret expression strings found inside captured values.
@@ -181,6 +183,11 @@ and loops and scopes become builder contexts. Concrete 
binding, type checking, c
 construction, and frame finalization belong to the builders. Ordinary host 
calls and
 operator overloads execute as part of the generated Python program.
 
+Parser syntax restrictions raise ``SyntaxError`` with the original filename 
and source
+range. Python helper and builder exceptions propagate unchanged, preserving 
their identity,
+type and source-mapped traceback. Temporary parser contexts are released on 
both successful
+and exceptional exits.
+
 Parse flow
 ~~~~~~~~~~
 
diff --git a/python/tvm/relax/script/__init__.py 
b/python/tvm/relax/script/__init__.py
index d173ace380..58e59d4c45 100644
--- a/python/tvm/relax/script/__init__.py
+++ b/python/tvm/relax/script/__init__.py
@@ -98,8 +98,9 @@ def py_module(
     TypeError
         If the decorated value is not a class, or an unsupported parser option
         is supplied.
-    DiagnosticError
-        If shared parsing, construction or enabled validation fails.
+    SyntaxError
+        If the source violates a parser syntax restriction. Construction and
+        enabled validation propagate their original exceptions unchanged.
     OSError
         If source inspection cannot recover a Python function's definition.
 
diff --git a/python/tvm/relax/script/builder/parser_protocol.py 
b/python/tvm/relax/script/builder/parser_protocol.py
index 7a8d0f08ab..357c3de0cb 100644
--- a/python/tvm/relax/script/builder/parser_protocol.py
+++ b/python/tvm/relax/script/builder/parser_protocol.py
@@ -1425,7 +1425,7 @@ def check_well_formed_(function: _relax.Function) -> None:
     whole-module checks, s_tir verification, then TIRx verification on each 
non-s_tir
     primitive function, preserving cross-function checks. Dialect X hooks 
validate a
     standalone completed function under that dialect policy. Invalid IR raises 
ValueError
-    retaining native details; the parser converts it to source diagnostics.
+    retaining native details; the parser propagates the exception unchanged.
     check_well_formed=False omits the generated call entirely.
 
     .. code:: python
diff --git a/python/tvm/script/ir_builder/base.py 
b/python/tvm/script/ir_builder/base.py
index 83f7e84e75..ebae9be7bd 100644
--- a/python/tvm/script/ir_builder/base.py
+++ b/python/tvm/script/ir_builder/base.py
@@ -68,21 +68,13 @@ class IRBuilderFrame(_Object):
     """
 
     def __enter__(self) -> "IRBuilderFrame":
-        try:
-            _ffi_api.IRBuilderFrameEnter(self)  # type: ignore[attr-defined] # 
pylint: disable=no-member
-        except Exception as error:
-            _attach_diagnostic_span(error, self.source_span)
-            raise
+        _ffi_api.IRBuilderFrameEnter(self)  # type: ignore[attr-defined] # 
pylint: disable=no-member
         return self
 
     def __exit__(self, exc_type, exc_value, trace) -> None:  # pylint: 
disable=unused-argument
         if exc_type is None and exc_value is None:
             # Do not execute `FrameExit` if the with scope exits because of 
exceptions
-            try:
-                _ffi_api.IRBuilderFrameExit(self)  # type: 
ignore[attr-defined] # pylint: disable=no-member
-            except Exception as error:
-                _attach_diagnostic_span(error, self.source_span)
-                raise
+            _ffi_api.IRBuilderFrameExit(self)  # type: ignore[attr-defined] # 
pylint: disable=no-member
 
     def add_callback(self, callback: Callable[[], None]) -> None:
         """Add a callback method invoked when exiting the with-scope.
@@ -261,19 +253,6 @@ def source_span(
     return ir.Span(source_name, line, end_line, column, end_column)
 
 
-def _attach_diagnostic_span(error: Exception, span: ir.Span | None) -> None:
-    """Retain an operation's explicit location without opening a source 
scope."""
-    if span is not None and not hasattr(error, "__tvm_script_location__"):
-        diagnostic_span = span.spans[-1] if isinstance(span, 
ir.SequentialSpan) else span
-        error.__tvm_script_location__ = (
-            str(diagnostic_span.source_name.name),
-            diagnostic_span.line,
-            diagnostic_span.end_line,
-            diagnostic_span.column,
-            diagnostic_span.end_column,
-        )
-
-
 _T = TypeVar("_T")
 
 
@@ -321,12 +300,8 @@ def with_at_group_(
         if span is not None and IRBuilder.is_in_scope()
         else nullcontext()
     )
-    try:
-        with context:
-            return at(location, thunk())
-    except Exception as error:
-        _attach_diagnostic_span(error, span)
-        raise
+    with context:
+        return at(location, thunk())
 
 
 at_ = at
diff --git a/python/tvm/script/ir_builder/ir/parser_protocol.py 
b/python/tvm/script/ir_builder/ir/parser_protocol.py
index 91982704e9..a680c9ca4d 100644
--- a/python/tvm/script/ir_builder/ir/parser_protocol.py
+++ b/python/tvm/script/ir_builder/ir/parser_protocol.py
@@ -510,7 +510,7 @@ def check_well_formed_(module: _ir.IRModule) -> None:
     whole-module checks, s_tir verification, then TIRx verification on each 
non-s_tir
     primitive function, preserving cross-function checks. Dialect X hooks 
validate a
     standalone completed function under that dialect policy. Invalid IR raises 
ValueError
-    retaining native details; the parser converts it to source diagnostics.
+    retaining native details; the parser propagates the exception unchanged.
     check_well_formed=False omits the generated call entirely.
 
     .. code:: python
diff --git a/python/tvm/script/parser/call_args_policy.py 
b/python/tvm/script/parser/call_args_policy.py
index fb1cdb5d4d..196b2d1aa6 100644
--- a/python/tvm/script/parser/call_args_policy.py
+++ b/python/tvm/script/parser/call_args_policy.py
@@ -43,7 +43,17 @@ class _LiteralParser:
         try:
             expression = ast.parse(node.value, mode="eval").body
         except SyntaxError as error:
-            raise SyntaxError(f"Invalid annotation expression: {error.msg}") 
from error
+            raise SyntaxError(
+                f"Invalid annotation expression: {error.msg}",
+                (
+                    self.filename,
+                    node.lineno,
+                    node.col_offset + 1,
+                    None,
+                    node.end_lineno,
+                    node.end_col_offset + 1,
+                ),
+            ) from error
         # Example: in R.Tensor(("n + 1",), "float32"), the generated resolve
         # call for n retains the byte range of n inside the quoted literal, not
         # the whole constructor. A triple-quoted physical newline advances 
lineno;
diff --git a/python/tvm/script/parser/diagnostics.py 
b/python/tvm/script/parser/diagnostics.py
deleted file mode 100644
index 5605011ee4..0000000000
--- a/python/tvm/script/parser/diagnostics.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""Source-located construction errors without replacing their original 
traceback."""
-
-from __future__ import annotations
-
-import ast
-import linecache
-import traceback
-
-from tvm.error import DiagnosticError
-
-
-def diagnostic_error(error: Exception, filename: str, tree: ast.Module) -> 
DiagnosticError:
-    """Return a DiagnosticError highlighting the original source operation.
-
-    error is the caught Python/builder exception. filename and tree supply the 
source
-    filename and original AST for fallback coordinates. Builder errors carry
-    plain location tuples; otherwise Python traceback/SyntaxError coordinates
-    identify the innermost original operation. Python 3.11+ supplies precise
-    traceback columns, while earlier versions fall back to the source line.
-    No frame or IR object is inspected; the caller attaches error as __cause__.
-    """
-    location = getattr(error, "__tvm_script_location__", None)
-    if location is not None:
-        filename, start, end, column, end_column = location
-        column, end_column = column - 1, end_column - 1
-    elif isinstance(error, SyntaxError) and error.lineno:
-        start = error.lineno
-        end = error.end_lineno or start
-        column = max((error.offset or 1) - 1, 0)
-        end_column = max((error.end_offset or column + 2) - 1, column + 1)
-    else:
-        frames = [
-            frame
-            for frame in traceback.extract_tb(error.__traceback__)
-            if frame.filename == filename
-        ]
-        if frames:
-            frame = frames[-1]
-            start, end = frame.lineno, getattr(frame, "end_lineno", None) or 
frame.lineno
-            column = getattr(frame, "colno", None) or 0
-            end_column = getattr(frame, "end_colno", None)
-            if getattr(frame, "end_lineno", None) is None:
-                # Python before 3.11 supplies only a line. Recover the original
-                # statement range from source AST, without an IR span context 
or
-                # executing/reparsing a generated program. Prefer the narrowest
-                # statement beginning on that line (a nested body over its 
def).
-                candidates = [
-                    node
-                    for node in ast.walk(tree)
-                    if isinstance(node, ast.stmt) and node.lineno == start
-                ]
-                if candidates:
-                    node = min(candidates, key=lambda item: item.end_lineno - 
item.lineno)
-                    end, column, end_column = node.end_lineno, 
node.col_offset, node.end_col_offset
-        else:
-            node = tree.body[-1]
-            start, end = node.lineno, node.lineno
-            column, end_column = node.col_offset, None
-    lines = [f"{filename}:{start}: {type(error).__name__}: {error}"]
-    for number in range(start, end + 1):
-        source = linecache.getline(filename, number).rstrip("\n")
-        first = column if number == start else len(source) - 
len(source.lstrip())
-        last = end_column if number == end and end_column is not None else 
len(source)
-        lines.extend(
-            (
-                f" {number} | {source}",
-                " " * (len(str(number)) + 4 + first) + "^" * max(last - first, 
1),
-            )
-        )
-    return DiagnosticError("\n".join(lines))
-
-
-def _format_source_snippet(
-    source_lines: list[str],
-    lineno: int,
-    col_offset: int,
-    end_lineno: int,
-    end_col_offset: int,
-) -> str:
-    """Format a source code snippet with a column/span marker.
-
-    Renders every source line spanned by the diagnostic (``lineno`` through
-    ``end_lineno``, inclusive) with a per-line gutter, followed by a caret
-    underline covering the offending span. For a single-line span
-    (``end_lineno == lineno``) only the columns ``col_offset`` (inclusive)
-    through ``end_col_offset`` (exclusive) are underlined; for a multi-line
-    span the underline covers the start column to end-of-line on the first
-    line, the full text of interior lines, and the start of the final line up
-    to ``end_col_offset``.
-
-    Parameters
-    ----------
-    source_lines : list of str
-        Lines of the source code.
-
-    lineno : int
-        1-based starting line number in the source.
-
-    col_offset : int
-        1-based starting column (inclusive) on ``lineno``.
-
-    end_lineno : int
-        1-based ending line number in the source (>= ``lineno``).
-
-    end_col_offset : int
-        1-based ending column (exclusive) on ``end_lineno``.
-
-    Returns
-    -------
-    snippet : str
-        Formatted source snippet with caret-marker line(s).
-    """
-    if end_lineno < lineno:
-        end_lineno = lineno
-
-    # Determine the gutter width so that all line numbers line up.
-    header_width = len(f" {end_lineno} ")
-    no_line_header = " " * header_width
-
-    parts = [f"{no_line_header}|  "]
-    for cur_lineno in range(lineno, end_lineno + 1):
-        idx = cur_lineno - 1
-        if not 0 <= idx < len(source_lines):
-            continue
-        line_text = source_lines[idx].rstrip("\n")
-        line_header = f" {cur_lineno} ".rjust(header_width)
-
-        # Compute the underline span [start_col, stop_col) for this line.
-        start_col = col_offset if cur_lineno == lineno else 1
-        stop_col = end_col_offset if cur_lineno == end_lineno else 
len(line_text) + 1
-
-        marker = ""
-        for i in range(1, len(line_text) + 1):
-            if start_col <= i < stop_col:
-                marker += "^"
-            else:
-                marker += " "
-        parts.append(f"{line_header}|  {line_text}")
-        parts.append(f"{no_line_header}|  {marker}")
-
-    if len(parts) == 1:
-        return ""
-    return "\n".join(parts)
diff --git a/python/tvm/script/parser/entry.py 
b/python/tvm/script/parser/entry.py
index 727b434725..7fef2e1973 100644
--- a/python/tvm/script/parser/entry.py
+++ b/python/tvm/script/parser/entry.py
@@ -30,14 +30,12 @@ from functools import wraps
 from types import FrameType, FunctionType
 from typing import TYPE_CHECKING, Any, TypeVar
 
-from tvm.error import DiagnosticError
 from tvm.ir import SourceName
 from tvm.script.ir_builder import base
 from tvm.script.ir_builder import ir as builder_ir
 from tvm.script.ir_builder.ir import parser_protocol as syntax_protocol
 
 from . import jit_support
-from .diagnostics import diagnostic_error
 from .inspect_source import (
     acquire_source,
     capture_annotation_bindings,
@@ -260,8 +258,8 @@ def make_decorator(
     ------
     ValueError
         When the returned decorator receives a non-function positional value.
-    DiagnosticError
-        When standalone construction fails during `parse` execution.
+    SyntaxError
+        When standalone source violates a parser restriction.
 
     Notes
     -----
@@ -269,7 +267,8 @@ def make_decorator(
     Standalone functions immediately transpile and execute a builder program.
     Annotations must be safe to re-evaluate: eager MissingType placeholders
     are not cached, and source annotations execute in declaration frames.
-    Registration persists for the lifetime of the returned decorator.
+    Construction errors propagate unchanged through `parse`. Registration
+    persists for the lifetime of the returned decorator.
     """
     mapping, default_options = dict(option_map or {}), dict(defaults or {})
 
@@ -707,22 +706,21 @@ def parse(
 
     Raises
     ------
-    DiagnosticError
-        If transpilation or host/builder execution fails. An existing
-        DiagnosticError is preserved; other execution errors gain original
-        source ranges.
     OSError
         If source inspection cannot recover the supplied object's text.
     TypeError
         If the source object cannot be inspected.
     SyntaxError
-        If initial source parsing fails before program execution.
+        If source parsing fails or transpilation detects a syntax restriction.
+        Parser restrictions retain their source filename and range.
 
     Notes
     -----
     Each call owns one AST copy and a fresh lexical environment. Declaration 
and
     definition frames are entered only during generated execution. Source
-    acquisition errors propagate directly, before diagnostic conversion.
+    acquisition, host and builder errors propagate with their original type,
+    identity and traceback. Temporary captures are released even when execution
+    fails.
     """
     # - Recover source and explicit lexical/definition inputs.
     # - Copy the AST once, then collect source syntax facts.
@@ -780,10 +778,6 @@ def parse(
         with jit_support.use_specialization(root_name, specialization):
             result = _builder()
         return result
-    except DiagnosticError:
-        raise
-    except Exception as error:
-        raise diagnostic_error(error, filename, tree) from error
     finally:
         # Release temporary captures on both successful and exceptional exits.
         if _builder is not None and definition_scope_name is not None:
@@ -811,14 +805,15 @@ def ir_module(module: type | None = None, **options: Any) 
-> IRModule | Callable
 
     Raises
     ------
-    DiagnosticError
-        If module transpilation or builder execution fails.
+    SyntaxError
+        If module source violates a parser restriction.
 
     Notes
     -----
     Class host bindings are captured before transpilation. Generated execution
     declares all registered signatures before defining their bodies. Source
-    acquisition errors and frame lifetime follow `parse`.
+    acquisition and builder errors propagate unchanged; frame lifetime follows
+    `parse`.
     """
 
     def apply(module: type) -> IRModule:
diff --git a/python/tvm/script/parser/inspect_source.py 
b/python/tvm/script/parser/inspect_source.py
index a235448764..443d982330 100644
--- a/python/tvm/script/parser/inspect_source.py
+++ b/python/tvm/script/parser/inspect_source.py
@@ -14,7 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-"""Recover source coordinates and capture the explicit definition context."""
+"""Inspect source text, AST coordinates and the explicit definition context.
+
+``Source`` exposes dedented text and relative AST coordinates with conversion 
to
+absolute IR spans. ``acquire_source`` returns an AST whose original coordinates
+are already restored for compilation. Both keep inspection in this module;
+source ownership and execution remain with the parser entry point.
+"""
 
 from __future__ import annotations
 import __future__
@@ -28,10 +34,112 @@ from collections.abc import Mapping
 from types import CodeType, FrameType, FunctionType
 from typing import Any
 
+from tvm.ir import SourceName, Span
+
 from .call_args_policy import parse_annotation
 from .prescan import collect_annotation_free_names
 
 
+class Source:
+    """Source code class for TVMScript.
+
+    It is constructed by source code str or doc AST tree.
+
+    Parameters
+    ----------
+    source_name : str
+        The filename of the file where the source code locates.
+
+    start_line : int
+        The first line number of the source code.
+
+    start_column : int
+        The first column number of the first line of the source code.
+
+    source : str
+        The source code str of source code.
+
+    full_source : str
+        The complete source code of the file where the source code locates.
+    """
+
+    source_name: str | None
+    start_line: int
+    start_column: int
+    source: str
+    full_source: str
+
+    def __init__(self, program: str | FunctionType | type) -> None:
+        if isinstance(program, str):
+            self.source_name = "<str>"
+            self.start_line = 1
+            self.start_column = 0
+            self.source = program
+            self.full_source = program
+            return
+
+        self.source_name = inspect.getsourcefile(program)  # type: ignore
+        lines, self.start_line = inspect.getsourcelines(program)  # type: 
ignore
+        if lines:
+            self.start_column = len(lines[0]) - len(lines[0].lstrip())
+        else:
+            self.start_column = 0
+        if self.start_column and lines:
+            self.source = "\n".join([line[self.start_column :].rstrip() for 
line in lines])
+        else:
+            self.source = "".join(lines)
+        try:
+            # It will cause a problem when running in Jupyter Notebook.
+            # `mod` will be <module '__main__'>, which is a built-in module
+            # and `getsource` will throw a TypeError
+            mod = inspect.getmodule(program)
+            if mod:
+                self.full_source = inspect.getsource(mod)
+            else:
+                self.full_source = self.source
+        except TypeError:
+            # It's a work around for Jupyter problem.
+            # Since `findsource` is an internal API of inspect, we just use it
+            # as a fallback method.
+            src, _ = inspect.findsource(program)  # type: ignore
+            self.full_source = "".join(src)
+
+    def as_ast(self) -> ast.Module:
+        """Parse the source code into AST.
+
+        Returns
+        -------
+        res : ast.AST
+            The AST of source code.
+        """
+        return ast.parse(self.source)
+
+    def location(self, node: ast.AST) -> tuple[int, int, int, int]:
+        """Return the absolute 1-based source range of an AST node."""
+        lineno = getattr(node, "lineno", 1) or 1
+        col_offset = getattr(node, "col_offset", self.start_column)
+        col_offset = self.start_column if col_offset is None else col_offset
+        end_lineno = getattr(node, "end_lineno", lineno) or lineno
+        end_col_offset = getattr(node, "end_col_offset", col_offset)
+        end_col_offset = col_offset if end_col_offset is None else 
end_col_offset
+        lineno += self.start_line - 1
+        end_lineno += self.start_line - 1
+        col_offset += self.start_column + 1
+        end_col_offset += self.start_column + 1
+        return lineno, col_offset, end_lineno, end_col_offset
+
+    def to_span(self, node: ast.AST) -> Span:
+        """Convert an AST node to the canonical IR source span."""
+        lineno, col_offset, end_lineno, end_col_offset = self.location(node)
+        return Span(
+            SourceName(self.source_name or "<unknown>"),
+            lineno,
+            end_lineno,
+            col_offset,
+            end_col_offset,
+        )
+
+
 def capture_lexical_bindings(function: FunctionType) -> dict[str, Any]:
     """Snapshot only actual body global/closure reads for a deferred 
callable."""
     bindings: dict[str, Any] = {}
@@ -130,7 +238,7 @@ def acquire_source(
     Text uses ``<str>`` unless a filename is supplied. Function/class source
     retains its original file, line and UTF-8 column offsets, including the
     decoration-site fallback used by gallery runners. Source inspection and
-    parsing errors propagate to the caller before diagnostic conversion.
+    parsing errors propagate unchanged to the caller.
     The returned AST is the source snapshot; entry copies it once for 
rewriting.
     """
     members = vars(source).values() if inspect.isclass(source) else (source,)
diff --git a/python/tvm/script/parser/source.py 
b/python/tvm/script/parser/source.py
deleted file mode 100644
index b035a09851..0000000000
--- a/python/tvm/script/parser/source.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""Source text and canonical one-based IR coordinates."""
-
-from __future__ import annotations
-
-import ast
-import inspect
-from types import FunctionType
-
-from tvm.ir import SourceName, Span
-
-
-class Source:
-    """Source code class for TVMScript.
-
-    It is constructed by source code str or doc AST tree.
-
-    Parameters
-    ----------
-    source_name : str
-        The filename of the file where the source code locates.
-
-    start_line : int
-        The first line number of the source code.
-
-    start_column : int
-        The first column number of the first line of the source code.
-
-    source : str
-        The source code str of source code.
-
-    full_source : str
-        The complete source code of the file where the source code locates.
-    """
-
-    source_name: str | None
-    start_line: int
-    start_column: int
-    source: str
-    full_source: str
-
-    def __init__(self, program: str | FunctionType | type) -> None:
-        if isinstance(program, str):
-            self.source_name = "<str>"
-            self.start_line = 1
-            self.start_column = 0
-            self.source = program
-            self.full_source = program
-            return
-
-        self.source_name = inspect.getsourcefile(program)  # type: ignore
-        lines, self.start_line = inspect.getsourcelines(program)  # type: 
ignore
-        if lines:
-            self.start_column = len(lines[0]) - len(lines[0].lstrip())
-        else:
-            self.start_column = 0
-        if self.start_column and lines:
-            self.source = "\n".join([line[self.start_column :].rstrip() for 
line in lines])
-        else:
-            self.source = "".join(lines)
-        try:
-            # It will cause a problem when running in Jupyter Notebook.
-            # `mod` will be <module '__main__'>, which is a built-in module
-            # and `getsource` will throw a TypeError
-            mod = inspect.getmodule(program)
-            if mod:
-                self.full_source = inspect.getsource(mod)
-            else:
-                self.full_source = self.source
-        except TypeError:
-            # It's a work around for Jupyter problem.
-            # Since `findsource` is an internal API of inspect, we just use it
-            # as a fallback method.
-            src, _ = inspect.findsource(program)  # type: ignore
-            self.full_source = "".join(src)
-
-    def as_ast(self) -> ast.Module:
-        """Parse the source code into AST.
-
-        Returns
-        -------
-        res : ast.AST
-            The AST of source code.
-        """
-        return ast.parse(self.source)
-
-    def location(self, node: ast.AST) -> tuple[int, int, int, int]:
-        """Return the absolute 1-based source range of an AST node."""
-        lineno = getattr(node, "lineno", 1) or 1
-        col_offset = getattr(node, "col_offset", self.start_column)
-        col_offset = self.start_column if col_offset is None else col_offset
-        end_lineno = getattr(node, "end_lineno", lineno) or lineno
-        end_col_offset = getattr(node, "end_col_offset", col_offset)
-        end_col_offset = col_offset if end_col_offset is None else 
end_col_offset
-        lineno += self.start_line - 1
-        end_lineno += self.start_line - 1
-        col_offset += self.start_column + 1
-        end_col_offset += self.start_column + 1
-        return lineno, col_offset, end_lineno, end_col_offset
-
-    def to_span(self, node: ast.AST) -> Span:
-        """Convert an AST node to the canonical IR source span."""
-        lineno, col_offset, end_lineno, end_col_offset = self.location(node)
-        return Span(
-            SourceName(self.source_name or "<unknown>"),
-            lineno,
-            end_lineno,
-            col_offset,
-            end_col_offset,
-        )
diff --git a/python/tvm/script/parser/transpile.py 
b/python/tvm/script/parser/transpile.py
index c7ff50688d..53975dc20d 100644
--- a/python/tvm/script/parser/transpile.py
+++ b/python/tvm/script/parser/transpile.py
@@ -161,7 +161,17 @@ class IRBuilderTranspiler(ast.NodeTransformer):
         return ast.Name(name, ast.Load())
 
     def _raise_error(self, node: ast.AST, message: str) -> NoReturn:
-        raise SyntaxError(message, (self.module.filename, node.lineno, 
node.col_offset + 1, None))
+        raise SyntaxError(
+            message,
+            (
+                self.module.filename,
+                node.lineno,
+                node.col_offset + 1,
+                None,
+                node.end_lineno,
+                node.end_col_offset + 1,
+            ),
+        )
 
     def _call(
         self, namespace: str, member: str, args: list[ast.expr], node: 
ast.AST, **keywords: ast.expr
@@ -1017,6 +1027,10 @@ class IRBuilderTranspiler(ast.NodeTransformer):
                 #     y = X.bind_(value, name="y")
                 # -------------------------------------------------
                 value = self._call_dialect("bind_", [value], statement, 
**keywords)
+            if frame_value:
+                # Binding an entered frame originates at the source as-target;
+                # retain the existing native span argument while locating this 
call.
+                ast.copy_location(value, target)
             return [ast.copy_location(ast.Assign([target], value), statement)]
         if isinstance(target, ast.Attribute):
             # -------------------- Pattern --------------------
@@ -1417,6 +1431,9 @@ class IRBuilderTranspiler(ast.NodeTransformer):
         else:
             self._raise_error(node.target, "Loop targets must be names or a 
flat tuple of names")
         context = self._call_dialect("for_", [iterable], node, names=names)
+        # The generated iteration check originates at the source iterable, not
+        # the final body line. Its native frame span still covers the whole 
loop.
+        ast.copy_location(context, node.iter)
         body = self.transform_statements(node.body)
         return ast.copy_location(
             ast.With([ast.withitem(context, node.target)], body or 
[ast.Pass()]), node
diff --git a/python/tvm/tirx/script/builder/parser_protocol.py 
b/python/tvm/tirx/script/builder/parser_protocol.py
index 418a86c39c..6d74d26eb3 100644
--- a/python/tvm/tirx/script/builder/parser_protocol.py
+++ b/python/tvm/tirx/script/builder/parser_protocol.py
@@ -1686,7 +1686,7 @@ def check_well_formed_(function: _tir.PrimFunc) -> None:
     whole-module checks, s_tir verification, then TIRx verification on each 
non-s_tir
     primitive function, preserving cross-function checks. Dialect X hooks 
validate a
     standalone completed function under that dialect policy. Invalid IR raises 
ValueError
-    retaining native details; the parser converts it to source diagnostics.
+    retaining native details; the parser propagates the exception unchanged.
     check_well_formed=False omits the generated call entirely.
 
     .. code:: python
diff --git 
a/tests/python/relax/script/test_frontend_relax_construction_validation.py 
b/tests/python/relax/script/test_frontend_relax_construction_validation.py
index d547113619..f45fe9d0e7 100644
--- a/tests/python/relax/script/test_frontend_relax_construction_validation.py
+++ b/tests/python/relax/script/test_frontend_relax_construction_validation.py
@@ -21,7 +21,6 @@ import pytest
 
 import tvm
 from tvm import relax
-from tvm.error import DiagnosticError
 from tvm.relax import BasePyModule
 from tvm.script import ir as I
 from tvm.script import relax as R
@@ -50,7 +49,7 @@ def _invalid_inplace_module(**options):
 
 
 def test_default_relax_validation_rejects_repeated_inplace_input():
-    with pytest.raises(DiagnosticError):
+    with pytest.raises(ValueError):
         _invalid_inplace_module()
 
 
diff --git 
a/tests/python/relax/script/test_relax_parser_comparison_integration.py 
b/tests/python/relax/script/test_relax_parser_comparison_integration.py
index 34c6532bd1..de0c351a5b 100644
--- a/tests/python/relax/script/test_relax_parser_comparison_integration.py
+++ b/tests/python/relax/script/test_relax_parser_comparison_integration.py
@@ -26,8 +26,6 @@ OPERATORS = [("<", "LT"), ("<=", "LE"), (">", "GT"), (">=", 
"GE"), ("==", "EQ"),
 
 @pytest.mark.parametrize("operator,kind", OPERATORS)
 def test_unmarked_host_comparison_never_calls_python_overload(operator, kind):
-    from tvm.error import DiagnosticError
-
     calls = []
 
     class Host:
@@ -51,7 +49,7 @@ def 
test_unmarked_host_comparison_never_calls_python_overload(operator, kind):
     )
     decorator = "R.function"
     statement = "return left " + operator + " right"
-    with pytest.raises(DiagnosticError, 
match="(PrimExpr|primitive|convert|type)"):
+    with pytest.raises(TypeError, match="(PrimExpr|primitive|convert|type)"):
         parser.parse(
             f"@{decorator}\ndef main():\n    {statement}\n",
             extra_vars={"left": Host(), "right": Host()},
diff --git a/tests/python/relax/test_op_view.py 
b/tests/python/relax/test_op_view.py
index dc7e658ab9..9679f92b25 100644
--- a/tests/python/relax/test_op_view.py
+++ b/tests/python/relax/test_op_view.py
@@ -54,7 +54,7 @@ def test_infer_shape_of_2d_static_view():
 
 
 def test_error_if_shape_argument_is_not_shape():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(TypeError):
 
         @R.function
         def func(A: R.Tensor([16])):
@@ -105,7 +105,7 @@ def 
test_infer_shape_of_2d_static_view_same_size_as_2d_source():
 
 
 def test_error_if_1d_static_view_larger_than_1d_source():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def func(A: R.Tensor([16])):
@@ -114,7 +114,7 @@ def test_error_if_1d_static_view_larger_than_1d_source():
 
 
 def test_error_if_static_2d_view_larger_than_source():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def func(A: R.Tensor([16])):
@@ -171,7 +171,7 @@ def test_infer_shape_of_2d_dynamic_view():
 
 
 def test_error_if_1d_dynamic_view_larger_than_1d_source():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def func(A: R.Tensor(["N"])):
@@ -180,9 +180,8 @@ def test_error_if_1d_dynamic_view_larger_than_1d_source():
             return B
 
 
[email protected](reason="See https://github.com/apache/tvm/pull/16877";)
 def test_error_if_1d_dynamic_view_provably_larger_than_1d_source():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def func(A: R.Tensor(["N"])):
@@ -192,7 +191,7 @@ def 
test_error_if_1d_dynamic_view_provably_larger_than_1d_source():
 
 
 def test_error_if_2d_dynamic_view_provably_larger_than_1d_source():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def func(A: R.Tensor(["N"])):
@@ -245,7 +244,7 @@ def test_infer_dtype_of_float32_view():
 
 
 def test_error_if_view_dtype_is_void():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(TypeError):
 
         @R.function
         def func(A: R.Tensor("float32")):
@@ -338,7 +337,7 @@ def test_view_dtype_may_be_smaller_than_input_dtype():
 
 def test_error_if_view_dtype_is_larger_than_input_dtype():
     """A view may not exceed the bounds of the viewed array"""
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def func(A: R.Tensor([16], "uint8")):
@@ -393,7 +392,7 @@ def 
test_error_if_number_of_bytes_of_view_is_larger_than_original():
     byte/element).
 
     """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def func(A: R.Tensor([8], "float16")):
@@ -410,7 +409,7 @@ def test_error_for_non_zero_relative_byte_offset():
 
     """
 
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def func(A: R.Tensor):
diff --git a/tests/python/relax/test_transform.py 
b/tests/python/relax/test_transform.py
index dd811855d8..a505cc5e26 100644
--- a/tests/python/relax/test_transform.py
+++ b/tests/python/relax/test_transform.py
@@ -589,7 +589,7 @@ def test_call_tir_inplace_some_new():
 
 
 def test_call_tir_inplace_repeated_input():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @tvm.script.ir_module
         class Input:
@@ -619,7 +619,7 @@ def test_call_tir_inplace_repeated_input():
 
 
 def test_call_tir_inplace_all_new():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @tvm.script.ir_module
         class Input:
@@ -648,7 +648,7 @@ def 
test_inplace_mutation_with_tuple_argument_raises_error():
     triggered a segfault rather than raising an exception.
 
     """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @I.ir_module(s_tir=True)
         class Module:
@@ -680,7 +680,7 @@ def 
test_inplace_mutation_with_non_tensor_argument_raises_error():
     triggered a segfault rather than raising an exception.
 
     """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @I.ir_module(s_tir=True)
         class Module:
@@ -709,7 +709,7 @@ def 
test_inplace_mutation_with_incompatible_tensor_shape_raises_error():
     different than the output's shape (`[32]` as opposed to `[16]`).
 
     """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @I.ir_module(s_tir=True)
         class Module:
@@ -738,7 +738,7 @@ def 
test_inplace_mutation_with_incompatible_tensor_dtype_raises_error():
     different than the output's dtype (`int32` as opposed to `float32`).
 
     """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @I.ir_module(s_tir=True)
         class Module:
diff --git a/tests/python/relax/test_tvmscript_parser.py 
b/tests/python/relax/test_tvmscript_parser.py
index 875d2619e6..da4246f5ee 100644
--- a/tests/python/relax/test_tvmscript_parser.py
+++ b/tests/python/relax/test_tvmscript_parser.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 # ruff: noqa: F401, F821, F841
+import inspect
 import sys
 from typing import Optional, Union
 
@@ -64,12 +65,12 @@ def test_simple_func():
     _check(foo, bb.get()["foo"])
 
 
-def test_error_report():
-    with pytest.raises(tvm.error.DiagnosticError):
+def test_call_tir_requires_global_var():
+    with pytest.raises(tvm.error.InternalError, match="first argument to be a 
GlobalVar"):
 
         @R.function
         def foo(x: R.Tensor((128, 128), "float32")) -> R.Tensor(None, 
"float32", ndim=2):
-            # error: a = b = c is not allowed.
+            # call_tir requires a GlobalVar rather than a packed-function name.
             gv0 = gv1 = R.call_tir("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
             return gv0
 
@@ -110,7 +111,7 @@ def test_unexpected_ndim_type():
 
 
 def test_unexpected_tir_cast_args():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(TypeError):
 
         @R.function
         def f(x: R.Tensor(("m",), "float32")):
@@ -120,7 +121,7 @@ def test_unexpected_tir_cast_args():
 
 
 def test_unexpected_tir_args():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(TypeError):
 
         @tvm.script.ir_module
         class TestWellCallTIR:
@@ -139,7 +140,7 @@ def test_unexpected_tir_args():
                 gv = R.call_tir(tir_addone, (x,), R.Tensor((T.max(16),), 
dtype="float32"))
                 return gv
 
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(TypeError):
 
         @R.function
         def f(x: R.Tensor(("m", "n"), "float32")):
@@ -149,7 +150,7 @@ def test_unexpected_tir_args():
 
 
 def test_func_type_annotation_fail():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(SyntaxError, match="Parameter 'x' requires an 
annotation") as error:
 
         @R.function
         def f(x, y):  # error: the parameter type annotation is missing
@@ -157,9 +158,16 @@ def test_func_type_annotation_fail():
             y = z
             return y
 
+    lines, start = inspect.getsourcelines(test_func_type_annotation_fail)
+    line_index = next(i for i, line in enumerate(lines) if "def f(x, y):" in 
line)
+    assert error.value.filename == __file__
+    assert error.value.lineno == error.value.end_lineno == start + line_index
+    assert error.value.offset == lines[line_index].index("x, y") + 1
+    assert error.value.end_offset == error.value.offset + 1
+
 
 def test_if_mismatch_var_fail():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(SyntaxError, match="same named output") as error:
 
         @R.function
         def f(cond: R.Tensor((), "bool"), x: R.Tensor((1,), "float32")):
@@ -171,9 +179,18 @@ def test_if_mismatch_var_fail():
                 z = R.add(w, w)  # error: The binding var is expected to `y`
             return z
 
+    lines, start = inspect.getsourcelines(test_if_mismatch_var_fail)
+    last = next(i for i, line in enumerate(lines) if "z = R.add(w, w)" in line)
+    assert error.value.filename == __file__
+    assert error.value.lineno == error.value.end_lineno == start + last
+    assert error.value.offset == lines[last].index("z = R.add(w, w)") + 1
+    assert (
+        error.value.end_offset == lines[last].index("z = R.add(w, w)") + 
len("z = R.add(w, w)") + 1
+    )
+
 
 def test_unassigned_call_fail():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def f(x: R.Tensor):
@@ -182,7 +199,7 @@ def test_unassigned_call_fail():
 
 
 def test_incorrect_tensor_shape():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(tvm.error.InternalError):
 
         @R.function
         def f(x: R.Tensor([16])):
@@ -426,7 +443,7 @@ def test_symbolic_shape():
         gv0 = R.call_dps_packed("extern_func", x, R.Tensor((m, n), 
dtype="float32"))
         return gv0
 
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(tvm.error.InternalError):
 
         @R.function
         def mismatch_dtype(x: R.Tensor(("m", "n"), "float32")) -> 
R.Tensor(None, "float32", ndim=2):
@@ -667,53 +684,57 @@ def test_dataflow_block_advanced():
 
 
 def test_dataflow_binding_after_output():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError, match="New binding is not allowed after 
dataflow block output"):
 
         @R.function
         def foo(x: R.Tensor((128, 128), "float32")) -> R.Tensor(None, 
"float32", ndim=2):
             with R.dataflow():
-                gv = R.call_tir("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
+                gv = R.call_dps_packed("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
                 R.output(gv)
-                lv = R.call_tir("extern_func", gv, R.Tensor((128, 128), 
dtype="float32"))
+                lv = R.call_dps_packed("extern_func", gv, R.Tensor((128, 128), 
dtype="float32"))
             return gv
 
 
 def test_dataflow_output_global_var():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(
+        ValueError, match="An output variable is not emitted by this dataflow 
block"
+    ):
 
         @R.function
         def foo(x: R.Tensor((128, 128), "float32")) -> R.Tensor(None, 
"float32", ndim=2):
-            gv0 = R.call_tir("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
+            gv0 = R.call_dps_packed("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
             with R.dataflow():
-                gv1 = R.call_tir("extern_func", gv0, R.Tensor((128, 128), 
dtype="float32"))
+                gv1 = R.call_dps_packed("extern_func", gv0, R.Tensor((128, 
128), dtype="float32"))
                 R.output(gv0, gv1)
             return gv1
 
 
 def test_dataflow_multiple_output():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(
+        ValueError, match="not allowed for a dataflow block to have multiple 
output"
+    ):
 
         @R.function
         def foo(x: R.Tensor((128, 128), "float32")) -> R.Tensor(None, 
"float32", ndim=2):
             with R.dataflow():
-                gv = R.call_tir("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
+                gv = R.call_dps_packed("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
                 R.output(gv)
                 R.output(gv)
             return gv
 
 
 def test_dataflow_output_outside_dataflow_block():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError, match="`R.output` should appear inside a 
dataflow block"):
 
         @R.function
         def foo(x: R.Tensor((128, 128), "float32")) -> R.Tensor(None, 
"float32", ndim=2):
-            gv = R.call_tir("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
+            gv = R.call_dps_packed("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
             R.output(gv)
             return gv
 
 
 def test_dataflow_scope_fail():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def f(x: R.Tensor(ndim=2)):
@@ -740,7 +761,7 @@ def test_return_without_binding():
 
 
 def test_multiple_return():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def foo(x: R.Tensor((128, 128), "float32")):
@@ -749,11 +770,11 @@ def test_multiple_return():
 
 
 def test_function_without_return():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError, match="A Relax function must have a return 
value"):
 
         @R.function
         def foo(x: R.Tensor((128, 128), "float32")):
-            gv0 = R.call_tir("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
+            gv0 = R.call_dps_packed("extern_func", x, R.Tensor((128, 128), 
dtype="float32"))
 
 
 def test_tensor_type_without_args():
@@ -931,7 +952,7 @@ def test_annotate_override():
     assert isinstance(y_bind.var.ty, relax.TensorType)
     assert isinstance(z_bind.var.ty, relax.AnyType)
 
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(tvm.error.InternalError):
 
         @R.function
         def test(x: R.Tensor):
@@ -1062,7 +1083,7 @@ def test_call_tir_inplace():
 
 
 def test_call_tir_inplace_with_tuple_var_raises_error():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(TypeError):
 
         @tvm.script.ir_module
         class Module:
@@ -1128,7 +1149,7 @@ def test_local_function():
 
 
 def test_inline_prim_func():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(TypeError, match="unexpected keyword argument.*local"):
 
         @I.ir_module(s_tir=True)
         class TestModule:
@@ -1270,7 +1291,7 @@ def test_if_branch_with_match_cast():
 
 
 def test_if_inside_dataflow():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def foo(cond: R.Tensor((), "bool"), x: R.Tensor((1,), "float32")):
@@ -1286,7 +1307,7 @@ def test_if_inside_dataflow():
 
 
 def test_var_if_scoping_fail():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(NameError):
 
         @R.function
         def f(cond: R.Tensor((), "bool"), x: R.Tensor((1,), "float32")):
@@ -1620,7 +1641,7 @@ class Module:
 
 
 def test_later_prim_param_not_adopted_by_usage_first_symbol():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
         tvm.script.from_source(
             """
 @R.function
@@ -1631,7 +1652,7 @@ def main(x: R.Tensor(["n"], "float32"), n: 
R.Prim("int64")):
 
 
 def test_non_int64_prim_param_rejected_in_shape_annotation():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(tvm.error.InternalError):
         tvm.script.from_source(
             """
 @R.function
@@ -1698,7 +1719,7 @@ def test_undefined_symbolic_var_raises_error():
     shape.  That is, this test case raises an error, and will not
     attempt to define `m` as either `x.shape[0]-1` or `x.shape[1]//2`.
     """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function
         def foo(x: R.Tensor(("m + 1", "m * 2"), "float32")):  # name 'm' is 
not defined
@@ -2079,7 +2100,7 @@ def 
test_function_with_void_return_type_may_be_used_as_statements():
 def test_function_with_non_void_return_type_must_be_assigned():
     """Non-void results must be assigned to a variable"""
 
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function(pure=False)
         def func(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
@@ -2166,7 +2187,7 @@ def test_private_function():
 
 
 def test_private_function_with_global_symbol_fail():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @I.ir_module(s_tir=True)
         class Addition:
@@ -2183,7 +2204,7 @@ def test_private_function_with_global_symbol_fail():
 
 
 def test_private_function_with_global_symbol_no_module_fail():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function(private=True)
         def func(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
@@ -2260,7 +2281,7 @@ def test_macro_non_hygienic():
 
 
 def test_macro_no_variable_leak():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(NameError):
 
         @R.macro(hygienic=True)
         def add_two(value):
@@ -2483,7 +2504,7 @@ def test_shared_meta_var_skips_relax_bindings():
     assert "meta_var" not in source
     _check(func)
 
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @R.function(private=True)
         def mismatched_declaration():
diff --git a/tests/python/s_tir/test_s_tir_axis_binding_validation.py 
b/tests/python/s_tir/test_s_tir_axis_binding_validation.py
index f82c77d040..4fda4b0e40 100644
--- a/tests/python/s_tir/test_s_tir_axis_binding_validation.py
+++ b/tests/python/s_tir/test_s_tir_axis_binding_validation.py
@@ -18,7 +18,6 @@
 
 import pytest
 
-import tvm
 from tvm.script import parser
 
 
@@ -39,5 +38,5 @@ def main():
             {axes}
             T.evaluate(vi)
 """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
         parser.parse(source)
diff --git a/tests/python/script/test_capture_definition_scope.py 
b/tests/python/script/test_capture_definition_scope.py
index 2fb1fb5949..21613eeccb 100644
--- a/tests/python/script/test_capture_definition_scope.py
+++ b/tests/python/script/test_capture_definition_scope.py
@@ -23,7 +23,6 @@ from types import SimpleNamespace
 
 import pytest
 
-from tvm.error import DiagnosticError
 from tvm.script import ir as I
 from tvm.script.ir_builder import IRBuilder
 from tvm.tirx.script.jit import make_jit
@@ -234,7 +233,7 @@ def 
test_unrelated_same_file_caller_does_not_supply_annotation_locals(language):
 
 def 
test_inactive_lexical_ancestor_is_not_replaced_by_unrelated_caller(language):
     # Before: annotation-only extent has left scope while an unrelated caller 
has extent.
-    # Expected builder: raise DiagnosticError naming extent; do not capture 
the caller.
+    # Expected builder: raise NameError naming extent; do not capture the 
caller.
     X = language.X
 
     def outer(extent):
@@ -249,7 +248,7 @@ def 
test_inactive_lexical_ancestor_is_not_replaced_by_unrelated_caller(language)
 
     middle = outer(8)
     extent = 99  # noqa: F841
-    with pytest.raises(DiagnosticError, match="extent"):
+    with pytest.raises(NameError, match="extent"):
         middle()
 
 
@@ -297,7 +296,7 @@ def 
test_jit_retains_active_lexical_ancestor_snapshot(factory, language):
 
 def test_unrelated_intervening_call_ends_annotation_capture(language):
     # Before: an unrelated invoke(callback) frame interrupts lexical ancestor 
capture.
-    # Expected builder: raise DiagnosticError naming unavailable extent.
+    # Expected builder: raise NameError naming unavailable extent.
     X = language.X
 
     def invoke(callback):
@@ -313,7 +312,7 @@ def 
test_unrelated_intervening_call_ends_annotation_capture(language):
 
         return invoke(middle)
 
-    with pytest.raises(DiagnosticError, match="extent"):
+    with pytest.raises(NameError, match="extent"):
         outer(8)
 
 
diff --git a/tests/python/script/test_capture_frontend_scope.py 
b/tests/python/script/test_capture_frontend_scope.py
index 2f0d784332..a530af61f2 100644
--- a/tests/python/script/test_capture_frontend_scope.py
+++ b/tests/python/script/test_capture_frontend_scope.py
@@ -23,7 +23,7 @@ from dummy_builder import Function
 from tvm import ir
 from tvm.script import ir as I
 from tvm.script.parser import entry
-from tvm.script.parser.source import Source
+from tvm.script.parser.inspect_source import Source
 from tvm.tirx.script.jit import make_jit
 
 VALUE = 1
diff --git a/tests/python/script/test_parser.py 
b/tests/python/script/test_parser.py
index 42e83d0ac5..9dd0212945 100644
--- a/tests/python/script/test_parser.py
+++ b/tests/python/script/test_parser.py
@@ -441,13 +441,13 @@ def 
test_quoted_symbols_share_identity_without_introducing_python_bindings(langu
     # Before: def main(x: X.tensor(("n", "n"))): X.record(n)
     # Expected builder program: X.tensor((X.resolve_type_var_("n"), 
X.resolve_type_var_("n")))
     # The unquoted body name n raises NameError until explicitly declared.
-    with pytest.raises(Exception) as error:
+    with pytest.raises(NameError) as error:
         language.parse("""
 @X.script
 def main(x: X.tensor(("n", "n"))):
     X.record(n)
 """)
-    assert isinstance(error.value.__cause__, NameError)
+    assert isinstance(error.value, NameError)
 
 
 def test_explicit_symbol_declaration_reuses_annotation_identity(language):
@@ -469,7 +469,7 @@ def main(x: X.tensor(("n",))):
 def test_script_namespace_cannot_be_rebound(language, binding):
     # Before: X = 1  (also loop/with targets)
     # Expected builder program: reject a binding that shadows the fixed script 
namespace X.
-    with pytest.raises(Exception, match="namespace|shadow|rebind"):
+    with pytest.raises(SyntaxError, match="namespace|shadow|rebind"):
         language.parse(f"@X.script\ndef main():\n    {binding}\n")
 
 
@@ -616,7 +616,7 @@ def 
test_conditional_branches_require_matching_output_names(language, other):
     # Before: if condition: y = left; else: z = right
     # Expected builder program: reject branches without the same named 
terminal output.
     language.X.__tvm_value_if__ = True
-    with pytest.raises(Exception, match="same named output"):
+    with pytest.raises(SyntaxError, match="same named output"):
         language.parse(
             "@X.script\ndef main():\n    if condition:\n        y = left\n"
             f"    else:\n        {other}\n"
@@ -641,7 +641,7 @@ def 
test_entry_keeps_reusable_input_tree_unchanged(language, monkeypatch):
 def test_bare_callable_alias_does_not_acquire_constexpr_syntax(language):
     # Before: marker = I.constexpr; if marker(True): ...
     # Expected builder program: an ordinary call to marker raises its runtime 
syntax-marker error.
-    with pytest.raises(Exception, match="syntax marker"):
+    with pytest.raises(TypeError, match="syntax marker"):
         language.parse(
             """
 @X.script
@@ -703,30 +703,33 @@ class Module:
 
 
 @pytest.mark.parametrize(
-    "body, line, column, message",
+    "body, line, column, end_column, message",
     [
-        ("    X = 1\n", 3, 5, "namespace"),
-        ("    for X in range(2):\n        pass\n", 3, 9, "namespace"),
+        ("    X = 1\n", 3, 5, 6, "namespace"),
+        ("    for X in range(2):\n        pass\n", 3, 9, 10, "namespace"),
         (
             "    if condition:\n        y = left\n    else:\n        z = 
right\n",
             6,
             9,
+            18,
             "same named output",
         ),
     ],
 )
-def test_syntax_diagnostics_point_to_the_offending_binding(language, body, 
line, column, message):
+def test_syntax_diagnostics_point_to_the_offending_binding(
+    language, body, line, column, end_column, message
+):
     # Before: X = 1; or if condition: y = left; else: z = right
     # Expected builder program: reject the offending namespace/output binding
     # at its original filename, line and column, before running the builder.
     language.X.__tvm_value_if__ = True
-    with pytest.raises(Exception, match=message) as error:
+    with pytest.raises(SyntaxError, match=message) as error:
         language.parse("@X.script\ndef main():\n" + body)
-    cause = error.value.__cause__
+    cause = error.value
     assert isinstance(cause, SyntaxError)
     assert (cause.filename, cause.lineno, cause.offset) == ("dummy.py", line, 
column)
-    assert f"dummy.py:{line}: SyntaxError:" in str(error.value)
-    assert f" {line} | {body.splitlines()[line - 3]}" in str(error.value)
+    assert cause.end_lineno == line
+    assert cause.end_offset == end_column
     assert not language.events
 
 
diff --git a/tests/python/script/test_parser_capture_lifetime.py 
b/tests/python/script/test_parser_capture_lifetime.py
index 23348bffa7..c62d421ebb 100644
--- a/tests/python/script/test_parser_capture_lifetime.py
+++ b/tests/python/script/test_parser_capture_lifetime.py
@@ -20,12 +20,12 @@ from __future__ import annotations
 
 import ast
 import gc
+import inspect
 import weakref
 from contextlib import contextmanager
 
 import pytest
 
-from tvm.error import DiagnosticError
 from tvm.script import ir as I
 from tvm.script.parser import entry
 
@@ -115,7 +115,7 @@ def 
test_owned_ast_context_and_builder_are_temporary(language, monkeypatch, fail
         if failure:
             try:
                 language.parse(source)
-            except DiagnosticError:
+            except NameError:
                 pass
             else:
                 pytest.fail("expected failed construction")
@@ -130,27 +130,40 @@ def 
test_owned_ast_context_and_builder_are_temporary(language, monkeypatch, fail
         )
 
 
-def test_expression_exception_keeps_original_source_call_traceback(language):
+class FixedFailure(ValueError):
+    def __setattr__(self, name, value):
+        if name == "__tvm_script_location__":
+            raise AttributeError("exception metadata is immutable")
+        super().__setattr__(name, value)
+
+
[email protected]("error_type", [ValueError, FixedFailure])
+def 
test_expression_exception_keeps_original_source_call_traceback(spanned_language,
 error_type):
     # Before: a normal Python helper fails within a generated function body.
     # Expected builder program: its real call frame retains the source 
expression range.
     import traceback
 
-    original = ValueError("original expression failure")
+    # Exercise the production shared span boundary as well as the generated 
call.
+    language = spanned_language
+    original = error_type("original expression failure")
 
     def explode():
         raise original
 
     source = "@X.script\ndef main():\n    X.record(explode())\n"
     filename = "expression_traceback.py"
-    with pytest.raises(DiagnosticError) as caught:
+    with pytest.raises(error_type) as caught:
         entry.parse(source, extra_vars={"X": language.X, "explode": explode}, 
filename=filename)
-    cause = caught.value.__cause__
-    assert cause is original
-    assert type(cause) is ValueError
-    frames = traceback.extract_tb(cause.__traceback__)
+    assert caught.value is original
+    assert type(caught.value) is error_type
+    frames = traceback.extract_tb(caught.value.__traceback__)
     calls = [frame for frame in frames if frame.filename == filename and 
frame.lineno == 3]
     assert calls
-    if calls[-1].colno is not None:
+    helper_frames = [frame for frame in frames if frame.name == "explode"]
+    assert len(helper_frames) == 1
+    assert helper_frames[0].filename == inspect.getsourcefile(explode)
+    assert helper_frames[0].lineno == inspect.getsourcelines(explode)[1] + 1
+    if getattr(calls[-1], "colno", None) is not None:
         assert (calls[-1].colno, calls[-1].end_lineno, calls[-1].end_colno) == 
(13, 3, 22)
 
 
diff --git a/tests/python/script/test_parser_host_selection.py 
b/tests/python/script/test_parser_host_selection.py
index d8ab5ce206..d133f7888e 100644
--- a/tests/python/script/test_parser_host_selection.py
+++ b/tests/python/script/test_parser_host_selection.py
@@ -113,7 +113,7 @@ def main():
 
 @pytest.mark.parametrize("track_span", [True, False])
 def test_constexpr_keeps_named_expression_unsupported(language, track_span):
-    with pytest.raises(Exception, match="Unsupported expression: NamedExpr"):
+    with pytest.raises(SyntaxError, match="Unsupported expression: NamedExpr"):
         parse(
             language,
             """
@@ -147,9 +147,7 @@ def main(condition: X.value):
     "expression", ["1 if I.constexpr(x) else 0", "I.constexpr(x) and 1", 
"I.constexpr(x) or 1"]
 )
 def test_missing_host_binding_cannot_be_truth_tested(language, expression):
-    from tvm.error import DiagnosticError
-
-    with pytest.raises(DiagnosticError) as caught:
+    with pytest.raises(NameError) as caught:
         parse(
             language,
             f"""
@@ -160,13 +158,11 @@ def main():
     X.record({expression})
 """,
         )
-    assert isinstance(caught.value.__cause__, NameError)
+    assert isinstance(caught.value, NameError)
 
 
 def test_missing_host_if_binding_raises_before_branch_assignments(language):
-    from tvm.error import DiagnosticError
-
-    with pytest.raises(DiagnosticError) as caught:
+    with pytest.raises(NameError) as caught:
         parse(
             language,
             """
@@ -180,7 +176,7 @@ def main():
         x = 3
 """,
         )
-    assert isinstance(caught.value.__cause__, NameError)
+    assert isinstance(caught.value, NameError)
 
 
 def test_host_lambda_local_does_not_capture_optional_binding(language):
@@ -246,9 +242,7 @@ def main(a: X.value(), b: X.value(), c: X.value()):
 
 
 def test_ir_branch_incoming_read_and_rebinding_obeys_python_scope(language):
-    from tvm.error import DiagnosticError
-
-    with pytest.raises(DiagnosticError) as error:
+    with pytest.raises(NameError) as error:
         language.parse("""
 @X.script
 def main(condition: X.value(), x: X.value()):
@@ -259,5 +253,5 @@ def main(condition: X.value(), x: X.value()):
         y = x
     return y
 """)
-    assert isinstance(error.value.__cause__, NameError)
-    assert "y" in str(error.value.__cause__)
+    assert isinstance(error.value, NameError)
+    assert "y" in str(error.value)
diff --git a/tests/python/script/test_parser_latest_design.py 
b/tests/python/script/test_parser_latest_design.py
index 59c5c4cf62..f8cea3847c 100644
--- a/tests/python/script/test_parser_latest_design.py
+++ b/tests/python/script/test_parser_latest_design.py
@@ -18,9 +18,10 @@
 
 from __future__ import annotations
 
+import ast
+
 import pytest
 
-from tvm.error import DiagnosticError
 from tvm.script import ir as I
 from tvm.script.ir_builder.ir import parser_protocol as protocol
 from tvm.script.parser import entry
@@ -98,13 +99,13 @@ def 
test_symbol_reassignment_reports_introduction_and_exact_write(
 ):
     # Before: annotation-free/body-declared n, followed by an ordinary write.
     # Expected diagnostic: n, its introduction line, and the actual offending 
source target.
-    with pytest.raises(DiagnosticError) as caught:
+    with pytest.raises(SyntaxError) as caught:
         language.parse(source, n=4)
     message = str(caught.value)
     assert "Symbolic variable 'n' cannot be reassigned" in message
     assert f"introduced at line {introduction}" in message
-    assert f"dummy.py:{offending}:" in message
-    cause = caught.value.__cause__
+    assert caught.value.filename == "dummy.py"
+    cause = caught.value
     assert isinstance(cause, SyntaxError)
     assert (cause.lineno, cause.end_lineno) == (offending, offending)
     assert cause.end_offset - cause.offset == 1
@@ -264,9 +265,45 @@ def 
test_scope_declaration_reuses_symbol_spelling_but_plain_write_is_rejected(
     source = "@X.script\ndef main():\n    n = X.symbol()\n    n = declared()\n"
     if ordinary_write:
         source += "    n = 3\n"
-        with pytest.raises(DiagnosticError, match="Symbolic variable 'n' 
cannot be reassigned"):
+        with pytest.raises(SyntaxError, match="Symbolic variable 'n' cannot be 
reassigned"):
             language.parse(source, declared=declared)
     else:
         result = language.parse(source + "    X.record(n)\n", 
declared=declared)
         assert result.body == [("emit", value)]
         assert not any(event[:2] == ("bind", "n") for event in language.events)
+
+
[email protected](
+    "source, node_type, message",
+    [
+        ("@X.script\ndef main(value):\n    pass\n", ast.arg, "requires an 
annotation"),
+        (
+            '@X.script\ndef main(value: "invalid +"):\n    pass\n',
+            ast.Constant,
+            "Invalid annotation expression",
+        ),
+        (
+            "@X.script\ndef main():\n    X.record((value :=\n        1))\n",
+            ast.NamedExpr,
+            "Unsupported expression: NamedExpr",
+        ),
+    ],
+)
+def test_transpilation_restrictions_keep_original_source_ranges(
+    language, source, node_type, message
+):
+    # Before: invalid signature, quoted annotation or multiline expression 
syntax.
+    # Expected: direct SyntaxError points at the original node before builder 
execution.
+    expected = next(node for node in ast.walk(ast.parse(source)) if 
isinstance(node, node_type))
+    with pytest.raises(SyntaxError, match=message) as caught:
+        entry.parse(source, extra_vars={"X": language.X}, 
filename="restriction.py")
+    error = caught.value
+    assert type(error) is SyntaxError
+    assert (error.filename, error.lineno, error.offset, error.end_lineno, 
error.end_offset) == (
+        "restriction.py",
+        expected.lineno,
+        expected.col_offset + 1,
+        expected.end_lineno,
+        expected.end_col_offset + 1,
+    )
+    assert not language.events
diff --git a/tests/python/script/test_parser_scope_helpers.py 
b/tests/python/script/test_parser_scope_helpers.py
index c22b32142e..1e951878d0 100644
--- a/tests/python/script/test_parser_scope_helpers.py
+++ b/tests/python/script/test_parser_scope_helpers.py
@@ -18,10 +18,10 @@
 
 import ast
 import copy
+import traceback
 
 import pytest
 
-from tvm.error import DiagnosticError
 from tvm.script.ir_builder import IRBuilder
 from tvm.script.parser import entry
 
@@ -141,13 +141,16 @@ def main(x: X.tensor((4,))):
     observe("outer_after", x, x, scope)
 """
     if fail:
-        with pytest.raises(DiagnosticError) as caught:
+        with pytest.raises(ValueError) as caught:
             language.parse(source, observe=observe)
-        assert caught.value.__cause__ is failure
+        assert caught.value is failure
         source_line = next(
             index for index, line in enumerate(source.splitlines(), 1) if 
'observe("inner"' in line
         )
-        assert f"dummy.py:{source_line}:" in str(caught.value)
+        assert any(
+            frame.filename == "dummy.py" and frame.lineno == source_line
+            for frame in traceback.extract_tb(caught.value.__traceback__)
+        )
     else:
         result = language.parse(source, observe=observe)
         assert result is language.functions["main"]
diff --git a/tests/python/script/test_parser_scoped_symbols.py 
b/tests/python/script/test_parser_scoped_symbols.py
index f7b7311e75..dfa219e889 100644
--- a/tests/python/script/test_parser_scoped_symbols.py
+++ b/tests/python/script/test_parser_scoped_symbols.py
@@ -18,7 +18,6 @@
 
 import pytest
 
-from tvm.error import DiagnosticError
 from tvm.script.parser import entry
 
 
@@ -80,10 +79,10 @@ def 
test_signature_read_before_introduction_remains_unbound(language, annotation
 def main(x: {annotation}):
     return x
 """
-    with pytest.raises(DiagnosticError) as error:
+    with pytest.raises(NameError) as error:
         parse(language, source)
-    assert isinstance(error.value.__cause__, NameError)
-    assert "n" in str(error.value.__cause__)
+    assert isinstance(error.value, NameError)
+    assert "n" in str(error.value)
 
 
 def test_signature_strings_do_not_replace_captured_python_names(language):
diff --git a/tests/python/tirx/codegen/test_codegen_blackwell.py 
b/tests/python/tirx/codegen/test_codegen_blackwell.py
index 26138ffd68..dadd71f435 100644
--- a/tests/python/tirx/codegen/test_codegen_blackwell.py
+++ b/tests/python/tirx/codegen/test_codegen_blackwell.py
@@ -245,7 +245,7 @@ def test_tma_mbarrier_remote_view_codegen():
 def test_mbarrier_remote_view_rejects_invalid_operations():
     from tvm.tirx.lang.pipeline import MBarrier, TMABar
 
-    with pytest.raises(tvm.error.DiagnosticError, match=r"remote_view\(\) 
cannot be initialized"):
+    with pytest.raises(ValueError, match=r"remote_view\(\) cannot be 
initialized"):
         # fmt: off
         @T.prim_func
         def invalid_init():
@@ -257,7 +257,7 @@ def test_mbarrier_remote_view_rejects_invalid_operations():
             bar.remote_view(0).init(1)
         # fmt: on
 
-    with pytest.raises(tvm.error.DiagnosticError, match=r"remote_view\(\) 
cannot be waited on"):
+    with pytest.raises(ValueError, match=r"remote_view\(\) cannot be waited 
on"):
         # fmt: off
         @T.prim_func
         def invalid_wait():
@@ -269,7 +269,7 @@ def test_mbarrier_remote_view_rejects_invalid_operations():
             bar.remote_view(0).wait(0, 0)
         # fmt: on
 
-    with pytest.raises(tvm.error.DiagnosticError, match="cannot also specify 
remote"):
+    with pytest.raises(ValueError, match="cannot also specify remote"):
         # fmt: off
         @T.prim_func
         def ambiguous_mbarrier_arrive():
@@ -281,7 +281,7 @@ def test_mbarrier_remote_view_rejects_invalid_operations():
             bar.remote_view(0).arrive(0, remote=1)
         # fmt: on
 
-    with pytest.raises(tvm.error.DiagnosticError, match="cannot also specify 
remote"):
+    with pytest.raises(ValueError, match="cannot also specify remote"):
         # fmt: off
         @T.prim_func
         def ambiguous_tma_arrive():
@@ -294,7 +294,7 @@ def test_mbarrier_remote_view_rejects_invalid_operations():
         # fmt: on
 
     with pytest.raises(
-        tvm.error.DiagnosticError,
+        ValueError,
         match=r"remote_view\(\) cannot be applied to a remote view",
     ):
         # fmt: off
diff --git a/tests/python/tirx/codegen/test_codegen_cuda.py 
b/tests/python/tirx/codegen/test_codegen_cuda.py
index 0500bee7c7..a352c19fe6 100644
--- a/tests/python/tirx/codegen/test_codegen_cuda.py
+++ b/tests/python/tirx/codegen/test_codegen_cuda.py
@@ -678,7 +678,7 @@ def test_cuda_ldg_vector_dtype_codegen(dtype, suffix, 
c_type, vec_base, vec):
 
 
 def test_cuda_ldg_vector_rejects_unsupported_dtype():
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="Unsupported vector CUDA"):
+    with pytest.raises(ValueError, match="Unsupported vector CUDA"):
         _get_source(_cuda_ldg_vector_kernel("float16", "v2"))
 
 
diff --git a/tests/python/tirx/codegen/test_ptx_addr.py 
b/tests/python/tirx/codegen/test_ptx_addr.py
index b26750370d..e706a0304c 100644
--- a/tests/python/tirx/codegen/test_ptx_addr.py
+++ b/tests/python/tirx/codegen/test_ptx_addr.py
@@ -242,9 +242,7 @@ def test_ptx_addr_offset_type_and_range_rejections():
 
 
 def test_ptx_addr_pointer_and_raw_address_validation():
-    with pytest.raises(
-        (ValueError, tvm.error.DiagnosticError), match="uint32 address 
requires shared"
-    ):
+    with pytest.raises(ValueError, match="uint32 address requires shared"):
 
         @T.prim_func
         def global_u32(raw: T.uint32):
@@ -252,9 +250,7 @@ def test_ptx_addr_pointer_and_raw_address_validation():
             value = T.local_scalar("uint32")
             T.ptx.ld.global_.b32(value, T.ptx.addr(raw, 4))
 
-    with pytest.raises(
-        (ValueError, tvm.error.DiagnosticError), match="does not support 
T.ptx.addr"
-    ):
+    with pytest.raises(ValueError, match="does not support T.ptx.addr"):
 
         @T.prim_func
         def ptr_operand(src: T.Buffer((8,), "uint32")):
@@ -264,9 +260,7 @@ def test_ptx_addr_pointer_and_raw_address_validation():
 
 
 def test_ptx_addr_tma_tmem_and_independent_immediate_rejections():
-    with pytest.raises(
-        (ValueError, tvm.error.DiagnosticError), match="does not support 
T.ptx.addr"
-    ):
+    with pytest.raises(ValueError, match="does not support T.ptx.addr"):
 
         @T.prim_func
         def tma(tmap: T.Buffer((8,), "uint64")):
@@ -277,9 +271,7 @@ def 
test_ptx_addr_tma_tmem_and_independent_immediate_rejections():
                 shared_buf.data, T.ptx.addr(tmap.data, 16), T.int32(0), 
barrier.data
             )
 
-    with pytest.raises(
-        (ValueError, tvm.error.DiagnosticError), match="does not support 
T.ptx.addr"
-    ):
+    with pytest.raises(ValueError, match="does not support T.ptx.addr"):
 
         @T.prim_func
         def tmem(raw: T.uint32):
@@ -314,7 +306,7 @@ def test_ptx_addr_printer_script_and_json_roundtrip():
 
 
 def test_ptx_addr_legacy_positional_offsets_rejected():
-    with pytest.raises((ValueError, tvm.error.DiagnosticError)):
+    with pytest.raises(ValueError):
 
         @T.prim_func
         def scalar_load(src: T.Buffer((8,), "uint32")):
@@ -322,7 +314,7 @@ def test_ptx_addr_legacy_positional_offsets_rejected():
             value = T.local_scalar("uint32")
             T.ptx.ld.global_.b32(value, src.data, 16)
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError)):
+    with pytest.raises(ValueError):
 
         @T.prim_func
         def vector_load(src: T.Buffer((8,), "uint32")):
@@ -330,14 +322,14 @@ def test_ptx_addr_legacy_positional_offsets_rejected():
             values = T.alloc_local((2,), "uint32")
             T.ptx.ld.global_.v2.b32(values[0], values[1], src.data, 16)
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError)):
+    with pytest.raises(ValueError):
 
         @T.prim_func
         def scalar_store(dst: T.Buffer((8,), "uint32")):
             T.device_entry()
             T.ptx.st.global_.b32(dst.data, 16, T.uint32(0))
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError)):
+    with pytest.raises(ValueError):
 
         @T.prim_func
         def vector_store(dst: T.Buffer((8,), "uint32")):
diff --git a/tests/python/tirx/codegen/test_ptx_dialect.py 
b/tests/python/tirx/codegen/test_ptx_dialect.py
index 9db4666189..81b0d7ec29 100644
--- a/tests/python/tirx/codegen/test_ptx_dialect.py
+++ b/tests/python/tirx/codegen/test_ptx_dialect.py
@@ -234,7 +234,7 @@ def test_ptx_red_vector_codegen_and_roundtrip():
     reparsed = tvm.script.from_source(kernel.script())
     tvm.ir.assert_structural_equal(kernel, reparsed)
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="already 
a 32-bit pair"):
+    with pytest.raises(ValueError, match="already a 32-bit pair"):
 
         @T.prim_func
         def packed_v8(a_ptr: T.handle):
@@ -243,7 +243,7 @@ def test_ptx_red_vector_codegen_and_roundtrip():
             v = T.local_scalar("uint32")
             T.ptx.red.global_.add.noftz.v8.f16x2(A.ptr_to([0]), v, v, v, v, v, 
v, v, v)
 
-    with pytest.raises((AttributeError, tvm.error.DiagnosticError), match="not 
a valid modifier"):
+    with pytest.raises(AttributeError, match="not a valid modifier"):
 
         @T.prim_func
         def f32_max(a_ptr: T.handle):
@@ -672,7 +672,7 @@ def test_ptx_92_cp_bulk_roundtrip():
 
 def test_ptx_trace_time_errors():
     # Global ld fed a raw uint32 address.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="shared 
state space"):
+    with pytest.raises(ValueError, match="shared state space"):
 
         @T.prim_func
         def bad_global_addr(out: T.Buffer((1,), "uint32")):
@@ -680,7 +680,7 @@ def test_ptx_trace_time_errors():
             T.ptx.ld.global_.b32(out[0], T.uint32(0))
 
     # Bogus modifier token.
-    with pytest.raises((AttributeError, tvm.error.DiagnosticError), match="not 
a valid modifier"):
+    with pytest.raises(AttributeError, match="not a valid modifier"):
 
         @T.prim_func
         def bad_modifier(out: T.Buffer((1,), "uint32")):
@@ -690,7 +690,7 @@ def test_ptx_trace_time_errors():
     # A floating register is not compatible with an integer instruction type,
     # even when it is wider (the relaxed ld/st rule admits a bit carrier, not a
     # differently typed floating register).
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="must 
have dtype"):
+    with pytest.raises(ValueError, match="must have dtype"):
 
         @T.prim_func
         def bad_value_dtype(a_ptr: T.handle):
@@ -699,7 +699,7 @@ def test_ptx_trace_time_errors():
             T.ptx.st.global_.u32(A.ptr_to([0]), T.float64(1.0))
 
     # Missing required modifier (no type token).
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="missing 
required modifier"):
+    with pytest.raises(ValueError, match="missing required modifier"):
 
         @T.prim_func
         def missing_type(a_ptr: T.handle):
@@ -709,7 +709,7 @@ def test_ptx_trace_time_errors():
 
     # Per-slot legal tokens whose combination is illegal PTX: acquire
     # requires a scope — rejected by the entry's check function.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="requires a scope"):
+    with pytest.raises(ValueError, match="requires a scope"):
 
         @T.prim_func
         def acquire_without_scope(out: T.Buffer((1,), "uint32"), a_ptr: 
T.handle):
@@ -718,7 +718,7 @@ def test_ptx_trace_time_errors():
             T.ptx.ld.global_.acquire.b32(out[0], A.ptr_to([0]))
 
     # A float is not an address in any state space.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="pointer 
or uint64 handle"):
+    with pytest.raises(ValueError, match="pointer or uint64 handle"):
 
         @T.prim_func
         def bad_addr_dtype(out: T.Buffer((1,), "uint32")):
@@ -730,7 +730,7 @@ def test_ptx_destination_errors():
     """A destination is a register the caller declared: it must be a writable 
lvalue."""
     # A floating destination is incompatible with an integer instruction type;
     # relaxed widening does not numerically convert between the two.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="must 
have dtype"):
+    with pytest.raises(ValueError, match="must have dtype"):
 
         @T.prim_func
         def wrong_dst_dtype(out: T.Buffer((1,), "float64"), a_ptr: T.handle):
@@ -740,7 +740,7 @@ def test_ptx_destination_errors():
 
     # A T.let binding is immutable, so it cannot be written into. This is the
     # gate that keeps the analyzer from re-expanding one call into N.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="writable scalar"):
+    with pytest.raises(ValueError, match="writable scalar"):
 
         @T.prim_func
         def let_destination(out: T.Buffer((1,), "uint32"), a_ptr: T.handle):
@@ -750,7 +750,7 @@ def test_ptx_destination_errors():
             T.ptx.ld.global_.b32(bound, A.ptr_to([0]))
 
     # An rvalue is not a destination either.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="writable scalar"):
+    with pytest.raises(ValueError, match="writable scalar"):
 
         @T.prim_func
         def rvalue_destination(a_ptr: T.handle):
@@ -792,7 +792,7 @@ def test_ptx_register_group_codegen():
 def test_ptx_register_group_errors():
     """A register group is one operand: its arity is fixed and its lanes 
agree."""
     # No `mov` shape takes two operands, so nothing in the family matches.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match=r"expects \d+ operand"):
+    with pytest.raises(ValueError, match=r"expects \d+ operand"):
 
         @T.prim_func
         def wrong_arity(a_ptr: T.handle):
@@ -804,7 +804,7 @@ def test_ptx_register_group_errors():
 
     # Each lane of a destination group is its own register the caller declared,
     # so each has to be an lvalue -- not just the first.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="writable scalar"):
+    with pytest.raises(ValueError, match="writable scalar"):
 
         @T.prim_func
         def non_lvalue_lane(a_ptr: T.handle):
@@ -818,7 +818,7 @@ def test_ptx_register_group_errors():
     # Lanes disagreeing on dtype: legal for each lane alone (both are 32-bit),
     # but the group is one operand with one C parameter type, so binding the
     # odd lane to it would be a numeric conversion.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="must 
have one dtype"):
+    with pytest.raises(ValueError, match="must have one dtype"):
 
         @T.prim_func
         def mixed_lane_dtypes(a_ptr: T.handle):
@@ -832,7 +832,7 @@ def test_ptx_register_group_errors():
 
     # A bare float literal names no dtype: on a .b32 lane it could be the
     # float's bits or the number, and it used to silently become T.uint32(1).
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="is 
ambiguous"):
+    with pytest.raises(ValueError, match="is ambiguous"):
 
         @T.prim_func
         def bare_float_literal(a_ptr: T.handle):
@@ -1052,7 +1052,7 @@ def test_ptx_relaxed_load_store_typing():
     assert '"l"(__value)' in src or '"l"(__value0)' in src
     assert "cvt." not in src
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="must 
have dtype"):
+    with pytest.raises(ValueError, match="must have dtype"):
 
         @T.prim_func
         def floating_source_for_integer_type(a_ptr: T.handle):
@@ -1245,7 +1245,7 @@ def test_ptx_integer_arithmetic_dispatch():
 
     # A derived dtype is enforced at trace time like any other: `.wide.s32`
     # writes 64 bits, so a 32-bit destination is rejected before codegen.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="must 
have dtype int64"):
+    with pytest.raises(ValueError, match="must have dtype int64"):
 
         @T.prim_func
         def narrow_wide_dst(out: T.Buffer((1,), "int32")):
@@ -1254,7 +1254,7 @@ def test_ptx_integer_arithmetic_dispatch():
 
     # `.sat` is a syntax line of its own, not a free qualifier: the check
     # rejects it where the ISA does not spell it.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="hi.s32"):
+    with pytest.raises(ValueError, match="hi.s32"):
 
         @T.prim_func
         def sat_on_lo(out: T.Buffer((1,), "int32")):
@@ -1265,21 +1265,21 @@ def test_ptx_integer_arithmetic_dispatch():
     # integer lines on which `.sat` has never been legal. Keep both reasons
     # visible so an invalid spelling is not misreported as an architecture
     # gate. In particular, sub.sat.u32 is not the counterpart of add.sat.u32.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="sm_120f-only"):
+    with pytest.raises(ValueError, match="sm_120f-only"):
 
         @T.prim_func
         def add_sat_sm120(out: T.Buffer((1,), "uint32")):
             T.device_entry()
             T.ptx.add.sat.u32(out[0], T.uint32(2), T.uint32(3))
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match=r"not on 
the add\.u64"):
+    with pytest.raises(ValueError, match=r"not on the add\.u64"):
 
         @T.prim_func
         def add_sat_no_syntax_line(out: T.Buffer((1,), "uint64")):
             T.device_entry()
             T.ptx.add.sat.u64(out[0], T.uint64(2), T.uint64(3))
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match=r"not on 
the sub\.u32"):
+    with pytest.raises(ValueError, match=r"not on the sub\.u32"):
 
         @T.prim_func
         def sub_sat_no_syntax_line(out: T.Buffer((1,), "uint32")):
@@ -1350,7 +1350,7 @@ def test_ptx_floating_point_dispatch():
     tvm.ir.assert_structural_equal(kernel, reparsed)
 
     # sqrt has no f64 approximation at any spelling, unlike rcp (9.7.3.14).
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="only on 
the .f32 line"):
+    with pytest.raises(ValueError, match="only on the .f32 line"):
 
         @T.prim_func
         def sqrt_approx_f64(out: T.Buffer((1,), "float64")):
@@ -1358,7 +1358,7 @@ def test_ptx_floating_point_dispatch():
             T.ptx.sqrt.approx.f64(out[0], T.float64(2.0))
 
     # ... and rcp's f64 approximation is unreachable without its mandatory 
.ftz.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="rcp.approx.ftz.f64"):
+    with pytest.raises(ValueError, match="rcp.approx.ftz.f64"):
 
         @T.prim_func
         def rcp_approx_f64(out: T.Buffer((1,), "float64")):
@@ -1366,7 +1366,7 @@ def test_ptx_floating_point_dispatch():
             T.ptx.rcp.approx.f64(out[0], T.float64(2.0))
 
     # One of .approx/.full/.rnd is required: the bare mnemonic names no line.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="missing 
required modifier"):
+    with pytest.raises(ValueError, match="missing required modifier"):
 
         @T.prim_func
         def div_without_mode(out: T.Buffer((1,), "float32")):
@@ -1426,7 +1426,7 @@ def test_ptx_half_precision_dispatch():
     tvm.ir.assert_structural_equal(kernel, reparsed)
 
     # The `.oob` line spells neither .ftz nor .sat beside it.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="oob 
line spells no"):
+    with pytest.raises(ValueError, match="oob line spells no"):
 
         @T.prim_func
         def oob_with_ftz(out: T.Buffer((1,), "uint16")):
@@ -1434,7 +1434,7 @@ def test_ptx_half_precision_dispatch():
             T.ptx.fma.rn.oob.ftz.f16(out[0], T.uint16(0), T.uint16(0), 
T.uint16(0))
 
     # .sat and .relu are two clampings on two different syntax lines.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="separate syntax lines"):
+    with pytest.raises(ValueError, match="separate syntax lines"):
 
         @T.prim_func
         def sat_and_relu(out: T.Buffer((1,), "uint16")):
@@ -1442,7 +1442,7 @@ def test_ptx_half_precision_dispatch():
             T.ptx.fma.rn.sat.relu.f16(out[0], T.uint16(0), T.uint16(0), 
T.uint16(0))
 
     # ex2's bf16 line without its mandatory .ftz names no syntax line.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="mandatorily"):
+    with pytest.raises(ValueError, match="mandatorily"):
 
         @T.prim_func
         def ex2_bf16_no_ftz(out: T.Buffer((1,), "uint16")):
@@ -1450,7 +1450,7 @@ def test_ptx_half_precision_dispatch():
             T.ptx.ex2.approx.bf16(out[0], T.uint16(0))
 
     # ... and the bf16 abs/neg lines spell no .ftz at all.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="takes 
no .ftz"):
+    with pytest.raises(ValueError, match="takes no .ftz"):
 
         @T.prim_func
         def abs_bf16_ftz(out: T.Buffer((1,), "uint16")):
@@ -1497,7 +1497,7 @@ def test_ptx_mixed_precision_dispatch():
     tvm.ir.assert_structural_equal(kernel, reparsed)
 
     # The mixed line spells no .ftz, unlike the .f32 line it shares an entry 
with.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="takes 
no .ftz"):
+    with pytest.raises(ValueError, match="takes no .ftz"):
 
         @T.prim_func
         def mixed_with_ftz(out: T.Buffer((1,), "float32")):
@@ -1505,7 +1505,7 @@ def test_ptx_mixed_precision_dispatch():
             T.ptx.add.rn.ftz.f32.f16(out[0], T.uint16(0), T.float32(0))
 
     # A converted source exists only on the .f32 line -- there is no f64 form.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="only 
exists on the .f32"):
+    with pytest.raises(ValueError, match="only exists on the .f32"):
 
         @T.prim_func
         def mixed_f64(out: T.Buffer((1,), "float64")):
@@ -1514,7 +1514,7 @@ def test_ptx_mixed_precision_dispatch():
 
     # And `mul` is the one mnemonic of the four with no mixed line at all, so
     # its entry declares no srctype slot and the token does not resolve.
-    with pytest.raises((AttributeError, tvm.error.DiagnosticError), match="not 
a valid modifier"):
+    with pytest.raises(AttributeError, match="not a valid modifier"):
 
         @T.prim_func
         def mul_mixed(out: T.Buffer((1,), "float32")):
@@ -1574,7 +1574,7 @@ def test_ptx_comparison_selection_dispatch():
     # Each source type takes its own operator set, and ptxas agrees with every
     # rejection below (probed at sm_90 before they were written into the 
check).
     # lo/ls/hi/hs are unsigned-only alternates for lt/le/gt/ge.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="signed"):
+    with pytest.raises(ValueError, match="signed"):
 
         @T.prim_func
         def unsigned_op_on_signed(out: T.Buffer((1,), "uint32")):
@@ -1582,7 +1582,7 @@ def test_ptx_comparison_selection_dispatch():
             T.ptx.setp.lo.s32(out[0], T.int32(1), T.int32(2))
 
     # The unordered comparisons and the NaN predicates are floating point only.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="floating-point comparison"):
+    with pytest.raises(ValueError, match="floating-point comparison"):
 
         @T.prim_func
         def float_op_on_integer(out: T.Buffer((1,), "uint32")):
@@ -1590,7 +1590,7 @@ def test_ptx_comparison_selection_dispatch():
             T.ptx.setp.nan.u32(out[0], T.uint32(1), T.uint32(2))
 
     # A bit-size type has no ordering, only equality.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="only 
with eq/ne"):
+    with pytest.raises(ValueError, match="only with eq/ne"):
 
         @T.prim_func
         def ordered_on_bitsize(out: T.Buffer((1,), "uint32")):
@@ -1598,7 +1598,7 @@ def test_ptx_comparison_selection_dispatch():
             T.ptx.setp.lt.b32(out[0], T.uint32(1), T.uint32(2))
 
     # .ftz applies only to .f32 comparisons ...
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="only to 
.f32 comparisons"):
+    with pytest.raises(ValueError, match="only to .f32 comparisons"):
 
         @T.prim_func
         def ftz_off_f32(out: T.Buffer((1,), "uint32")):
@@ -1606,7 +1606,7 @@ def test_ptx_comparison_selection_dispatch():
             T.ptx.setp.eq.ftz.s32(out[0], T.int32(1), T.int32(2))
 
     # ... and on slct, only to the .f32 selector line.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="f32 
selector line"):
+    with pytest.raises(ValueError, match="f32 selector line"):
 
         @T.prim_func
         def ftz_on_s32_selector(out: T.Buffer((1,), "uint32")):
@@ -1616,9 +1616,7 @@ def test_ptx_comparison_selection_dispatch():
     # Only d/a/b receive slct's relaxed bit-size typing. The selector must
     # still match the second instruction type exactly, even when another
     # same-constraint integer carrier would fit through inline asm.
-    with pytest.raises(
-        (ValueError, tvm.error.DiagnosticError), match=r"operand 
'c'.*int32.*uint32"
-    ):
+    with pytest.raises(ValueError, match=r"operand 'c'.*int32.*uint32"):
 
         @T.prim_func
         def relaxed_selector(out: T.Buffer((1,), "uint32")):
@@ -1716,7 +1714,7 @@ def test_ptx_half_comparison_dispatch():
 
     # Every rejection below was probed against ptxas before being written into
     # the check. The (dtype, stype) grid: a packed source needs a 32-bit 
answer.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="no 
half-precision syntax"):
+    with pytest.raises(ValueError, match="no half-precision syntax"):
 
         @T.prim_func
         def bad_pair(out: T.Buffer((1,), "uint16")):
@@ -1724,7 +1722,7 @@ def test_ptx_half_comparison_dispatch():
             T.ptx.set.eq.u16.f16x2(out[0], T.uint32(0), T.uint32(0))
 
     # `.ftz` follows the source precision: a bf16 source has none to flush.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="does 
not take it"):
+    with pytest.raises(ValueError, match="does not take it"):
 
         @T.prim_func
         def ftz_bf16_source(out: T.Buffer((1,), "uint32")):
@@ -1732,14 +1730,14 @@ def test_ptx_half_comparison_dispatch():
             T.ptx.set.eq.ftz.u32.bf16(out[0], T.uint16(0), T.uint16(0))
 
     # ... and no bf16 destination takes it either.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="destination takes no"):
+    with pytest.raises(ValueError, match="destination takes no"):
 
         @T.prim_func
         def ftz_bf16_dest(out: T.Buffer((1,), "uint16")):
             T.device_entry()
             T.ptx.set.eq.ftz.bf16.f32(out[0], T.float32(0), T.float32(0))
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="spells 
no .ftz"):
+    with pytest.raises(ValueError, match="spells no .ftz"):
 
         @T.prim_func
         def setp_ftz_bf16(out: T.Buffer((1,), "uint32")):
@@ -1747,7 +1745,7 @@ def test_ptx_half_comparison_dispatch():
             T.ptx.setp.eq.ftz.bf16(out[0], T.uint16(0), T.uint16(0))
 
     # The unordered comparisons need a floating-point source.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="integer 
source"):
+    with pytest.raises(ValueError, match="integer source"):
 
         @T.prim_func
         def unordered_on_integer(out: T.Buffer((1,), "uint16")):
@@ -1756,7 +1754,7 @@ def test_ptx_half_comparison_dispatch():
 
     # The classification fix is narrow: a real .b16 source remains a bit-size
     # value and therefore still permits equality comparisons only.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="bit-size source"):
+    with pytest.raises(ValueError, match="bit-size source"):
 
         @T.prim_func
         def ordered_on_bitsize(out: T.Buffer((1,), "uint16")):
@@ -1764,7 +1762,7 @@ def test_ptx_half_comparison_dispatch():
             T.ptx.set.lt.f16.b16(out[0], T.uint16(0), T.uint16(0))
 
     # 9.7.8 spells no unsigned alternates at all, so `lo` never resolves here.
-    with pytest.raises((AttributeError, tvm.error.DiagnosticError), match="not 
a valid modifier"):
+    with pytest.raises(AttributeError, match="not a valid modifier"):
 
         @T.prim_func
         def unsigned_alternate(out: T.Buffer((1,), "uint32")):
@@ -1836,7 +1834,7 @@ def test_ptx_logic_shift_dispatch():
 
     # cnot has no predicate line -- ptxas: "Unexpected instruction types
     # specified for 'cnot'" -- so the token is not in its slot at all.
-    with pytest.raises((AttributeError, tvm.error.DiagnosticError), match="not 
a valid modifier"):
+    with pytest.raises(AttributeError, match="not a valid modifier"):
 
         @T.prim_func
         def cnot_pred(out: T.Buffer((1,), "uint32")):
@@ -1844,7 +1842,7 @@ def test_ptx_logic_shift_dispatch():
             T.ptx.cnot.pred(out[0], T.uint32(0))
 
     # lop3's BoolOp line stops at .or/.and; ptxas rejects .xor outright.
-    with pytest.raises((AttributeError, tvm.error.DiagnosticError), match="not 
a valid modifier"):
+    with pytest.raises(AttributeError, match="not a valid modifier"):
 
         @T.prim_func
         def lop3_xor(out: T.Buffer((1,), "uint32")):
@@ -1853,7 +1851,7 @@ def test_ptx_logic_shift_dispatch():
 
     # shl is untyped -- a left shift zero-fills whatever the bits mean, so
     # there is no signed line (unlike shr, which needs to know how to fill).
-    with pytest.raises((AttributeError, tvm.error.DiagnosticError), match="not 
a valid modifier"):
+    with pytest.raises(AttributeError, match="not a valid modifier"):
 
         @T.prim_func
         def shl_signed(out: T.Buffer((1,), "int32")):
@@ -1903,9 +1901,7 @@ def test_ptx_logic_shift_dispatch():
     assert "lop3.b32 %0, %1, %2, %3, 0;" in boundary_src
     assert "lop3.b32 %0, %1, %2, %3, 255;" in boundary_src
 
-    with pytest.raises(
-        (ValueError, tvm.error.DiagnosticError), match=r"inclusive range 
0\.\.255, got -1"
-    ):
+    with pytest.raises(ValueError, match=r"inclusive range 0\.\.255, got -1"):
 
         @T.prim_func
         def lut_below_range(a_ptr: T.handle):
@@ -1914,9 +1910,7 @@ def test_ptx_logic_shift_dispatch():
             d = T.local_scalar("uint32")
             T.ptx.lop3.b32(d, A[0], A[0], A[0], -1)
 
-    with pytest.raises(
-        (ValueError, tvm.error.DiagnosticError), match=r"inclusive range 
0\.\.255, got 256"
-    ):
+    with pytest.raises(ValueError, match=r"inclusive range 0\.\.255, got 256"):
 
         @T.prim_func
         def lut_above_range(a_ptr: T.handle):
@@ -2018,7 +2012,7 @@ def test_ptx_data_movement_dispatch():
 
     # Every rejection below was probed against ptxas before it was written into
     # a check. multimem pairs an ordering semantic with a scope ...
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="go 
together"):
+    with pytest.raises(ValueError, match="go together"):
 
         @T.prim_func
         def sem_without_scope(a_ptr: T.handle):
@@ -2028,7 +2022,7 @@ def test_ptx_data_movement_dispatch():
             T.ptx.multimem_ld_reduce.relaxed.add.u32(v, A.ptr_to([0]))
 
     # ... and `.weak` is the line that has neither.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="takes 
no scope"):
+    with pytest.raises(ValueError, match="takes no scope"):
 
         @T.prim_func
         def weak_with_scope(a_ptr: T.handle):
@@ -2038,7 +2032,7 @@ def test_ptx_data_movement_dispatch():
             T.ptx.multimem_ld_reduce.weak.gpu.add.u32(v, A.ptr_to([0]))
 
     # The op x type table: `.add` is the row that takes .s32 but not .s64.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match=r"\.add 
takes"):
+    with pytest.raises(ValueError, match=r"\.add takes"):
 
         @T.prim_func
         def add_s64(a_ptr: T.handle):
@@ -2048,7 +2042,7 @@ def test_ptx_data_movement_dispatch():
             T.ptx.multimem_ld_reduce.add.s64(v, A.ptr_to([0]))
 
     # The scalar float line has no lone half: a width has to reach 32 bits.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="the 
scalar line takes"):
+    with pytest.raises(ValueError, match="the scalar line takes"):
 
         @T.prim_func
         def scalar_f16(a_ptr: T.handle):
@@ -2058,7 +2052,7 @@ def test_ptx_data_movement_dispatch():
             T.ptx.multimem_ld_reduce.add.f16(v, A.ptr_to([0]))
 
     # `.acc::f32` raises an accumulation, so it needs something to accumulate.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="applies 
to .add"):
+    with pytest.raises(ValueError, match="applies to .add"):
 
         @T.prim_func
         def acc_on_min(a_ptr: T.handle):
@@ -2068,7 +2062,7 @@ def test_ptx_data_movement_dispatch():
             T.ptx.multimem_ld_reduce.min.acc__f32.f16x2(v, A.ptr_to([0]))
 
     # st.async's mmio line is system-scoped only.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="requires .sys"):
+    with pytest.raises(ValueError, match="requires .sys"):
 
         @T.prim_func
         def mmio_gpu(a_ptr: T.handle):
@@ -2139,7 +2133,7 @@ def test_ptx_parallel_sync_dispatch():
     # Every rejection below was probed against ptxas first. redux pairs the
     # arithmetic ops with a signed type and the bitwise ones with the untyped
     # word -- they are two syntax lines, so the token does not even resolve.
-    with pytest.raises((AttributeError, tvm.error.DiagnosticError), match="not 
a valid modifier"):
+    with pytest.raises(AttributeError, match="not a valid modifier"):
 
         @T.prim_func
         def redux_add_b32(out: T.Buffer((1,), "uint32")):
@@ -2148,7 +2142,7 @@ def test_ptx_parallel_sync_dispatch():
 
     # atom's vector lines bound the width by the element: a packed pair stops
     # at .v4, and only a lone half reaches .v8.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="already 
a 32-bit pair"):
+    with pytest.raises(ValueError, match="already a 32-bit pair"):
 
         @T.prim_func
         def packed_v8(a_ptr: T.handle):
@@ -2160,7 +2154,7 @@ def test_ptx_parallel_sync_dispatch():
             )
 
     # red.async's op groups: `.add` reaches 64 bits, the bitwise ops do not.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match=r"\.and 
takes"):
+    with pytest.raises(ValueError, match=r"\.and takes"):
 
         @T.prim_func
         def red_async_and_u64(a_ptr: T.handle):
@@ -2291,7 +2285,7 @@ def test_ptx_wgmma_scale_d_runtime_predicate_roundtrip():
     # There is deliberately no compatibility overload for the old integer
     # immediate spelling. A bool already identifies a predicate; a runtime
     # integer must carry the explicit T.ptx.pred(...) register-class marker.
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match=r"T\.ptx\.pred"):
+    with pytest.raises(ValueError, match=r"T\.ptx\.pred"):
 
         @T.prim_func
         def bare_integer_scale_d():
@@ -2436,7 +2430,7 @@ def test_ptx_tcgen05_mma_block_size_form():
     assert "tcgen05.mma.cta_group::1.kind::mxf4.block_scale.block32" in src
     _assert_ptxas_ok(src, arch="sm_100a")
 
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match="mxf4.*block32"):
+    with pytest.raises(ValueError, match="mxf4.*block32"):
 
         @T.prim_func
         def invalid_mxf4_block16():
@@ -2603,7 +2597,7 @@ def test_ptx_pred_operand_rejects_untagged_integer():
     tells the two `cp.async` optional-operand syntax lines apart. A bool needs
     no tag -- it already says what it is.
     """
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), 
match=r"T\.ptx\.pred"):
+    with pytest.raises(ValueError, match=r"T\.ptx\.pred"):
 
         @T.prim_func
         def untagged_integer():
@@ -2670,7 +2664,7 @@ def 
test_ptx_sink_rejected_where_the_isa_has_no_underscore():
     Not from the family either: mov's *pack* shape has a scalar destination
     and a vector source, and the ISA gives neither of them a sink.
     """
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="not 
sinkable here"):
+    with pytest.raises(ValueError, match="not sinkable here"):
 
         @T.prim_func
         def sink_a_source():
@@ -2682,7 +2676,7 @@ def 
test_ptx_sink_rejected_where_the_isa_has_no_underscore():
             T.evaluate(hi)
 
     # ISA 9.7.10.4: "provided that at least one element is a scalar register".
-    with pytest.raises((ValueError, tvm.error.DiagnosticError), match="must be 
a real register"):
+    with pytest.raises(ValueError, match="must be a real register"):
 
         @T.prim_func
         def sink_every_lane():
diff --git a/tests/python/tirx/script/test_frontend_construction_validation.py 
b/tests/python/tirx/script/test_frontend_construction_validation.py
index a219763551..ba31e70eea 100644
--- a/tests/python/tirx/script/test_frontend_construction_validation.py
+++ b/tests/python/tirx/script/test_frontend_construction_validation.py
@@ -22,7 +22,6 @@ import pytest
 
 import tvm
 from tvm import tirx
-from tvm.error import DiagnosticError
 from tvm.script import ir as I
 from tvm.script import tirx as T
 
@@ -38,7 +37,7 @@ def _build_lexical_global():
 
 
 def test_default_tirx_validation_rejects_non_divisible_scope_extents():
-    with pytest.raises(DiagnosticError):
+    with pytest.raises(ValueError):
 
         @T.prim_func
         def invalid():
@@ -66,7 +65,7 @@ def test_tirx_validation_can_be_explicitly_disabled():
 
 
 def test_optional_annotation_is_restricted_to_jit():
-    with pytest.raises(DiagnosticError, match="only supported by @T.jit"):
+    with pytest.raises(TypeError, match="only supported by @T.jit"):
 
         @T.prim_func(private=True)
         def invalid(value: T.Optional(T.handle)):
diff --git a/tests/python/tirx/script/test_native_span_and_loop_contracts.py 
b/tests/python/tirx/script/test_native_span_and_loop_contracts.py
index 42285ff38e..fed75c5e2c 100644
--- a/tests/python/tirx/script/test_native_span_and_loop_contracts.py
+++ b/tests/python/tirx/script/test_native_span_and_loop_contracts.py
@@ -17,6 +17,8 @@
 
 """Deferred native results own spans and loop variables before frame entry."""
 
+import traceback
+
 import pytest
 import tvm_ffi
 
@@ -168,19 +170,34 @@ def 
test_source_call_preserves_cyclic_containers_and_native_member_spans(contain
 def 
test_native_diagnostics_keep_definition_location_and_restore_source_context(failure):
     with IRBuilder() as builder:
         if failure == "source_call":
+            original_error = ValueError("native helper failed")
 
             def fail():
-                raise ValueError("native helper failed")
+                raise original_error
 
             with pytest.raises(ValueError, match="native helper failed") as 
error:
                 _captured(fail)
+            assert error.value is original_error
+            assert type(error.value) is ValueError
+            frames = traceback.extract_tb(error.value.__traceback__)
+            assert frames[-1].filename == __file__
+            assert frames[-1].name == "fail"
+            assert frames[-1].lineno == fail.__code__.co_firstlineno + 1
+            assert any(
+                frame.filename == __file__ and frame.line.strip() == 
"_captured(fail)"
+                for frame in frames
+            )
         else:
             frame = _captured(lambda: native.If(tirx.IntImm("bool", True)))
+            definition_span = frame.source_span
+            assert positions(definition_span) == [("caller.py", 3), 
("frame.py", 7)]
             frame.__enter__()
             with builder.with_source_span(base.source_span(location(90, 
"unrelated.py"))):
                 with pytest.raises(InternalError, match="then") as error:
                     frame.__exit__(None, None, None)
-        assert error.value.__tvm_script_location__ == location(7)
+            assert type(error.value) is InternalError
+            assert frame.source_span.same_as(definition_span)
+            assert positions(frame.source_span) == [("caller.py", 3), 
("frame.py", 7)]
         following = native.serial(2)
         assert following.source_span is None
         with following:
diff --git a/tests/python/tirx/script/test_parser_buffer_views.py 
b/tests/python/tirx/script/test_parser_buffer_views.py
index d3675a6da7..6c59cfb613 100644
--- a/tests/python/tirx/script/test_parser_buffer_views.py
+++ b/tests/python/tirx/script/test_parser_buffer_views.py
@@ -22,7 +22,6 @@ from types import MethodType
 import pytest
 
 from tvm import ir, tirx
-from tvm.error import DiagnosticError
 from tvm.script import parser
 from tvm.script.ir_builder import IRBuilder
 from tvm.script.ir_builder import ir as I
@@ -168,7 +167,7 @@ def 
test_native_roundtrip_preserves_buffer_identity_and_shared_methods():
     ],
 )
 def test_view_validation(view, error):
-    with pytest.raises(DiagnosticError, match=error):
+    with pytest.raises(ValueError, match=error):
         parse(f"""
 @T.prim_func
 def main():
diff --git a/tests/python/tirx/script/test_parser_jit_specialization.py 
b/tests/python/tirx/script/test_parser_jit_specialization.py
index 7ead95acc7..3e13a0cd1c 100644
--- a/tests/python/tirx/script/test_parser_jit_specialization.py
+++ b/tests/python/tirx/script/test_parser_jit_specialization.py
@@ -123,7 +123,7 @@ def test_specialization_context_restored_after_failure():
             missing_call()  # noqa: F821
         T.evaluate(N)
 
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(NameError):
         broken.specialize(N=1)
     assert len(broken.specialize(N=2).params) == 0
     result = parser.parse("@T.prim_func\ndef normal(N: T.int32):\n    
T.evaluate(N)\n")
@@ -232,7 +232,7 @@ def 
test_capture_transport_does_not_specialize_ordinary_captured_names():
 
 
 def test_missing_constexpr_binding_has_explicit_diagnostic():
-    with pytest.raises(tvm.error.DiagnosticError, match="requires a 
specialization binding"):
+    with pytest.raises(TypeError, match="requires a specialization binding"):
         parser.parse("@T.jit\ndef kernel(N: T.constexpr):\n    
T.evaluate(N)\n")
 
 
@@ -250,7 +250,7 @@ def kernel(a: T.Optional(annotation())):
         missing_call()
     T.evaluate(1)
 """
-    with pytest.raises(tvm.error.DiagnosticError, match="missing_call"):
+    with pytest.raises(NameError, match="missing_call"):
         parser.parse(
             source, extra_vars={"annotation": annotation}, 
_specialization_bindings={"a": None}
         )
diff --git a/tests/python/tirx/script/test_parser_tirx_emission.py 
b/tests/python/tirx/script/test_parser_tirx_emission.py
index 8bbd845a31..ea32d5489c 100644
--- a/tests/python/tirx/script/test_parser_tirx_emission.py
+++ b/tests/python/tirx/script/test_parser_tirx_emission.py
@@ -18,7 +18,6 @@
 
 import pytest
 
-import tvm
 from tvm import tirx
 from tvm.relax.script import builder as R
 from tvm.script import parser
@@ -47,9 +46,9 @@ def 
test_source_store_uses_public_builder_and_propagates_errors(monkeypatch, err
 
     monkeypatch.setattr(tir_builder, "buffer_store", reject_store)
     source = f"@T.prim_func\ndef main():\n    {body}\n"
-    with pytest.raises(tvm.error.DiagnosticError, match="store rejected") as 
raised:
+    with pytest.raises(error_type, match="store rejected") as raised:
         parser.parse(source)
-    assert raised.value.__cause__ is error
+    assert raised.value is error
     assert len(calls) == 1
 
 
diff --git 
a/tests/python/tirx/script/test_tirx_parser_comparison_integration.py 
b/tests/python/tirx/script/test_tirx_parser_comparison_integration.py
index fcc739a664..ae5918bfaf 100644
--- a/tests/python/tirx/script/test_tirx_parser_comparison_integration.py
+++ b/tests/python/tirx/script/test_tirx_parser_comparison_integration.py
@@ -105,8 +105,6 @@ def 
test_iterator_comparison_keeps_written_operand_order(operator, kind):
 
 @pytest.mark.parametrize("operator,kind", OPERATORS)
 def test_unmarked_host_comparison_never_calls_python_overload(operator, kind):
-    from tvm.error import DiagnosticError
-
     calls = []
 
     class Host:
@@ -130,7 +128,7 @@ def 
test_unmarked_host_comparison_never_calls_python_overload(operator, kind):
     )
     decorator = "T.prim_func"
     statement = "T.evaluate(left " + operator + " right)"
-    with pytest.raises(DiagnosticError, 
match="(PrimExpr|primitive|convert|type)"):
+    with pytest.raises(TypeError, match="(PrimExpr|primitive|convert|type)"):
         parser.parse(
             f"@{decorator}\ndef main():\n    {statement}\n",
             extra_vars={"left": Host(), "right": Host()},
diff --git a/tests/python/tirx/test_jit.py b/tests/python/tirx/test_jit.py
index 00af56df61..bf7d8b38fb 100644
--- a/tests/python/tirx/test_jit.py
+++ b/tests/python/tirx/test_jit.py
@@ -19,6 +19,7 @@
 
 from __future__ import annotations
 
+import traceback
 import typing
 
 import pytest
@@ -364,7 +365,7 @@ def test_only_explicit_t_optional_is_specializable():
 
 
 def test_t_optional_is_restricted_to_jit():
-    with pytest.raises(tvm.error.DiagnosticError, match="only supported by 
@T.jit"):
+    with pytest.raises(TypeError, match="only supported by @T.jit"):
 
         @T.prim_func(private=True)
         def invalid(a: T.Optional(T.handle)):
@@ -411,19 +412,19 @@ def 
test_runtime_tir_if_cannot_guard_absent_optional_param():
         if flag != 0:
             T.match_buffer(a, (1,), "int32")
 
-    with pytest.raises(tvm.error.DiagnosticError, match="match_buffer"):
+    with pytest.raises(ValueError, match="match_buffer"):
         kernel.specialize(a=None)
 
 
 @pytest.mark.parametrize(
-    ("operation", "source_text"),
+    ("operation", "source_text", "error_type"),
     [
-        ("subscript", "a[10]"),
-        ("attribute", "a.ptr_to"),
-        ("match_buffer", "T.match_buffer"),
+        ("subscript", "a[10]", TypeError),
+        ("attribute", "a.ptr_to", AttributeError),
+        ("match_buffer", "T.match_buffer", tvm.error.InternalError),
     ],
 )
-def test_unguarded_absent_optional_param_reports_source(operation, 
source_text):
+def test_unguarded_absent_optional_param_reports_source(operation, 
source_text, error_type):
     @T.jit(private=True)
     def kernel(a: T.Optional(T.handle)):
         if T.constexpr(operation == "subscript"):
@@ -433,9 +434,11 @@ def 
test_unguarded_absent_optional_param_reports_source(operation, source_text):
         else:
             T.match_buffer(a, (1,), "int32")
 
-    with pytest.raises(tvm.error.DiagnosticError) as exc_info:
+    with pytest.raises(error_type) as exc_info:
         kernel.specialize(a=None)
-    assert source_text in str(exc_info.value)
+    assert type(exc_info.value) is error_type
+    frames = traceback.extract_tb(exc_info.value.__traceback__)
+    assert any(frame.filename == __file__ and source_text in frame.line for 
frame in frames)
 
 
 def test_present_optional_param_still_rejects_ffi_none():
diff --git a/tests/python/tirx/test_parser_printer.py 
b/tests/python/tirx/test_parser_printer.py
index d98cb2c36d..603d004857 100644
--- a/tests/python/tirx/test_parser_printer.py
+++ b/tests/python/tirx/test_parser_printer.py
@@ -757,7 +757,7 @@ def test_meta_class_constructor_rejects_unowned_resource():
         def __init__(self):
             tmp = T.alloc_buffer((1,), "int32", scope="local")
 
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @T.prim_func
         def test():
@@ -1088,11 +1088,10 @@ def func():
     v: T.int32
     v = v + T.int32(1)
 """
-    # The ValueError propagates through the parser framework which wraps it
-    # into a DiagnosticError.  Before the fix the broad ``except Exception``
-    # would silently swallow it and fall through to eval_assign.
+    # The ValueError propagates unchanged. A broad ``except Exception`` here
+    # previously swallowed it and fell through to eval_assign.
     with patch("tvm.tirx.script.builder.buffer_store", side_effect=bomb):
-        with pytest.raises(tvm.error.DiagnosticError):
+        with pytest.raises(ValueError, match="boom"):
             from_source(src)
 
 
@@ -1251,7 +1250,7 @@ from tvm.script import tirx as T
 def func():
     x: T.handle = T.int64(0)
 """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(tvm.error.InternalError):
         from_source(src_handle)
 
     # 3. Banned: non-PrimType annotation without T.let
@@ -1262,7 +1261,7 @@ from tvm.ir import PointerType, PrimType
 def func():
     x: T.Var(name="x", ty=PointerType(PrimType("float16"))) = T.int64(0)
 """
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(tvm.error.InternalError):
         from_source(src_ptr)
 
     # 4. An explicit mutable scalar declaration retains updates — round-trip
@@ -1503,7 +1502,7 @@ def test_buffer_local_compose_layout_printer_roundtrip():
 def test_buffer_local_inference_without_parent_layout_has_clear_diagnostic():
     """Shape inference requires a parent storage layout."""
 
-    with pytest.raises(tvm.error.DiagnosticError, match="parent buffer has 
layout=None"):
+    with pytest.raises(ValueError, match="parent buffer has layout=None"):
         # fmt: off
         @T.prim_func
         def func() -> None:
@@ -1632,7 +1631,7 @@ def 
test_buffer_local_printer_preserves_inherited_metadata():
 def test_buffer_local_rejects_shape_that_does_not_match_physical_span():
     """An explicit local shape product must preserve the physical span."""
 
-    with pytest.raises(tvm.error.DiagnosticError, match="physical storage span 
6 per thread"):
+    with pytest.raises(ValueError, match="physical storage span 6 per thread"):
         # fmt: off
         @T.prim_func
         def func() -> None:
@@ -2628,7 +2627,7 @@ def test_buffer_sub_tmem_rejects_partial_column_offset():
 
         return func
 
-    with pytest.raises(tvm.error.DiagnosticError, match="aligned to a physical 
32-bit column"):
+    with pytest.raises(ValueError, match="aligned to a physical 32-bit 
column"):
         build()
 
 
diff --git a/tests/python/tvmscript/test_tvmscript_error_report.py 
b/tests/python/tvmscript/test_tvmscript_error_report.py
index aec053fbe7..a59c6f83b6 100644
--- a/tests/python/tvmscript/test_tvmscript_error_report.py
+++ b/tests/python/tvmscript/test_tvmscript_error_report.py
@@ -15,8 +15,10 @@
 # specific language governing permissions and limitations
 # under the License.
 # ruff: noqa: E741, F821, F841, RUF005
+import ast
 import inspect
 import re
+import traceback
 
 import pytest
 
@@ -27,39 +29,42 @@ from tvm.script import from_source
 from tvm.script import tirx as T
 
 
-def check_error(func, rel_lineno):
-    """check if TIR script throws error"""
-    check_error_re = re.compile(r"^.*# check_error: (.+)$")
+def check_error(func, rel_lineno, error_type):
+    """Check the original exception class and its real source location."""
     source_code = inspect.getsource(func)
     indent = len(re.match(r"^\s*", source_code).group(0))
     source_code = "@T.prim_func(s_tir=True)\n" + "\n".join(
         line[indent:] for line in source_code.splitlines()
     )
-    # Parse errors now raise DiagnosticError with formatted source location.
-    with pytest.raises(tvm.error.DiagnosticError) as execinfo:
+    with pytest.raises(error_type) as caught:
         from_source(source_code)
-    err_str = str(execinfo.value)
-    if rel_lineno is None:
-        return
-    # The error message contains " --> <source>:<lineno>:<col>" formatted by 
Diagnostics.error().
-    # Accept either rel_lineno or rel_lineno+1 to match old tolerance.
-    assert f":{rel_lineno}:" in err_str or f":{rel_lineno + 1}:" in err_str, (
-        f"Expected error message to contain line {rel_lineno}, got:\n{err_str}"
-    )
-    error_line = source_code.split("\n")[rel_lineno]
-    m = check_error_re.match(error_line)
-    if m:
-        expected_error_text = m.group(1)
-        assert expected_error_text in err_str, (
-            f'check_error expects "{expected_error_text}" in error: {err_str}'
-        )
+    assert type(caught.value) is error_type
+    if isinstance(caught.value, SyntaxError):
+        assert caught.value.filename == "<str>"
+        assert caught.value.lineno == rel_lineno + 1
+        assert caught.value.offset is not None
+        assert caught.value.end_lineno >= caught.value.lineno
+        assert caught.value.end_offset is not None
+    else:
+        frames = [
+            frame
+            for frame in traceback.extract_tb(caught.value.__traceback__)
+            if frame.filename == "<str>"
+        ]
+        assert frames
+        if rel_lineno is not None:
+            assert frames[-1].lineno == rel_lineno + 1
+    if rel_lineno is not None:
+        match = re.match(r"^.*# check_error: (.+)$", 
source_code.splitlines()[rel_lineno])
+        if match:
+            assert match.group(1) in str(caught.value)
 
 
 def test_buffer_bind():
     def buffer_bind_missing_args(a: T.handle) -> None:
         A = T.match_buffer((16, 16), "float32")  # error
 
-    check_error(buffer_bind_missing_args, 2)
+    check_error(buffer_bind_missing_args, 2, tvm.error.InternalError)
 
 
 def test_undefined_buffer():
@@ -70,7 +75,7 @@ def test_undefined_buffer():
             for j in T.serial(0, 16):
                 C[i, j] = 0.0  # error
 
-    check_error(undefined_buffer, 6)
+    check_error(undefined_buffer, 6, NameError)
 
 
 def test_unsupported_function_call():
@@ -81,14 +86,14 @@ def test_unsupported_function_call():
             for j in T.serial(0, 16):
                 A[i, j] = 0.0
 
-    check_error(unsupported_function_call, 4)
+    check_error(unsupported_function_call, 4, AttributeError)
 
 
 def test_missing_type_annotation():
     def missing_type_annotation(a) -> None:  # error
         T.evaluate(0.0)
 
-    check_error(missing_type_annotation, 1)
+    check_error(missing_type_annotation, 1, SyntaxError)
 
 
 def test_invalid_for_function():
@@ -98,7 +103,7 @@ def test_invalid_for_function():
             for j in T.serial(0, 16):
                 A[i, j] = 0.0
 
-    check_error(invalid_for_function, 4)
+    check_error(invalid_for_function, 3, TypeError)
 
 
 def test_invalid_block_function():
@@ -108,14 +113,14 @@ def test_invalid_block_function():
         with T.evaluate(0.0):  # error
             T.evaluate(1.0)
 
-    check_error(invalid_block_function, 4)
+    check_error(invalid_block_function, 4, TypeError)
 
 
 def test_return_not_allowed():
     def return_not_allowed(a: T.handle) -> None:
         return T.evaluate(0)  # error
 
-    check_error(return_not_allowed, 2)
+    check_error(return_not_allowed, 2, NotImplementedError)
 
 
 def test_no_body():
@@ -123,7 +128,7 @@ def test_no_body():
         A = T.match_buffer(a, (16, 16), "float32")
         T.realize(A, "")  # error
 
-    check_error(no_body, 3)
+    check_error(no_body, 3, AttributeError)
 
 
 def test_inconsistent_binding():
@@ -137,8 +142,8 @@ def test_inconsistent_binding():
             vi, vj = T.axis.remap("S", [i, j])  # error
             T.evaluate(1.0)
 
-    check_error(inconsistent_binding_value, 3)
-    check_error(inconsistent_binding_type, 3)
+    check_error(inconsistent_binding_value, 3, tvm.error.InternalError)
+    check_error(inconsistent_binding_type, 3, tvm.error.InternalError)
 
 
 def test_error_remap_args():
@@ -154,8 +159,8 @@ def test_error_remap_args():
                 vi, vj = T.axis.remap("SS", [i + j, j])  # error
                 T.evaluate(1.0)
 
-    check_error(error_remap_type, 4)
-    check_error(error_remap_value, 4)
+    check_error(error_remap_type, 4, tvm.error.InternalError)
+    check_error(error_remap_value, 4, tvm.error.InternalError)
 
 
 def test_invalid_block_axes():
@@ -166,7 +171,7 @@ def test_invalid_block_axes():
                 vi = T.axis.S(i, A)  # error
                 T.evaluate(1.0)
 
-    check_error(invalid_block_axes, 5)
+    check_error(invalid_block_axes, 5, TypeError)
 
 
 def test_duplicate_block_axes():
@@ -183,8 +188,8 @@ def test_duplicate_block_axes():
                 vi, vi = T.axis.remap("SS", [i, j])  # error
                 T.evaluate(1.0)
 
-    check_error(duplicate_block_axes, 5)
-    check_error(duplicate_block_axes_remap, 4)
+    check_error(duplicate_block_axes, 5, ValueError)
+    check_error(duplicate_block_axes_remap, 4, ValueError)
 
 
 def test_miss_block_bind():
@@ -194,7 +199,7 @@ def test_miss_block_bind():
                 vi = T.axis.S(i)  # error
                 T.evaluate(1.0)
 
-    check_error(miss_block_bind_value, 4)
+    check_error(miss_block_bind_value, 4, TypeError)
 
 
 def test_invalid_loop_var():
@@ -202,15 +207,15 @@ def test_invalid_loop_var():
         for i, j in range(0, 16):  # error
             T.evaluate(1.0)
 
-    check_error(invalid_loop_var, 2)
+    check_error(invalid_loop_var, 2, ValueError)
 
 
 def test_inconsistent_grid():
     def inconsistent_grid(A: T.Buffer(16)) -> None:
-        for i in T.grid(16, 16):  # valid, i is a tuple (iter0, iter1)
-            T.evaluate(A[i])  # error
+        for i in T.grid(16, 16):  # error: one target cannot unpack two loop 
variables
+            T.evaluate(A[i])
 
-    check_error(inconsistent_grid, 3)
+    check_error(inconsistent_grid, 2, ValueError)
 
 
 def test_invalid_match_buffer_region():
@@ -221,7 +226,7 @@ def test_invalid_match_buffer_region():
                 A = T.match_buffer(vi)  # error
                 T.evaluate(1.0)
 
-    check_error(invalid_match_buffer_region, 5)
+    check_error(invalid_match_buffer_region, 5, ValueError)
 
 
 def test_buffer_rebinding_preserves_distinct_allocations():
@@ -292,12 +297,12 @@ def test_duplicate_block_signature():
                 T.sblock_attr({"key1": "block2"})  # error
                 T.evaluate(1.0)
 
-    check_error(duplicate_reads, 7)
-    check_error(duplicate_writes, 7)
-    check_error(duplicate_predicate, 6)
-    check_error(duplicate_init, 7)
-    check_error(duplicate_axes, 5)
-    check_error(duplicate_sblock_attrs_with_same_key_diff_value, 6)
+    check_error(duplicate_reads, 7, tvm.error.InternalError)
+    check_error(duplicate_writes, 7, tvm.error.InternalError)
+    check_error(duplicate_predicate, 6, tvm.error.InternalError)
+    check_error(duplicate_init, 7, ValueError)
+    check_error(duplicate_axes, 5, ValueError)
+    check_error(duplicate_sblock_attrs_with_same_key_diff_value, 6, 
tvm.error.InternalError)
 
 
 def test_opaque_access_during_complete():
@@ -307,7 +312,7 @@ def test_opaque_access_during_complete():
             with T.sblock():
                 T.evaluate(T.call_extern("dummy_extern_function", A.data, 
dtype="int32"))
 
-    check_error(opaque_access_during_complete, None)
+    check_error(opaque_access_during_complete, None, ValueError)
 
 
 def test_convert_slice_to_bufferload():
@@ -318,7 +323,7 @@ def test_convert_slice_to_bufferload():
                 vi, vj = T.axis.remap("SS", [i, j])
                 A[vi, vj] = A[vi : vi + 2, vj] + 1  # error
 
-    check_error(convert_slice_to_bufferload, 6)
+    check_error(convert_slice_to_bufferload, 6, TypeError)
 
 
 def test_tvm_exception_catch_from_special_stmt():
@@ -326,7 +331,7 @@ def test_tvm_exception_catch_from_special_stmt():
         A = T.sblock_alloc_buffer("(128, 128)", "float32")  # error
         T.evaluate(1.0)
 
-    check_error(special_stmt_except, 2)
+    check_error(special_stmt_except, 2, TypeError)
 
 
 def test_tvm_exception_catch_from_scope_handler():
@@ -334,7 +339,7 @@ def test_tvm_exception_catch_from_scope_handler():
         for i in T.serial("1", "1"):  # error
             T.evaluate(1)
 
-    check_error(scope_handler_except, 2)
+    check_error(scope_handler_except, 2, TypeError)
 
 
 def test_tvm_exception_catch_from_bare_intrin():
@@ -342,7 +347,7 @@ def test_tvm_exception_catch_from_bare_intrin():
         A = T.match_buffer(a, (16, 16), "float32")
         T.evaluate(A)  # error
 
-    check_error(intrin_except_unassign, 3)
+    check_error(intrin_except_unassign, 3, tvm.error.InternalError)
 
 
 def test_tvm_exception_catch_from_assigned_intrin():
@@ -350,7 +355,7 @@ def test_tvm_exception_catch_from_assigned_intrin():
         A = T.match_buffer(a, (16, 16), "float32")
         A[0, 0] = A[A]  # error
 
-    check_error(intrin_except_assign, 3)
+    check_error(intrin_except_assign, 3, tvm.error.InternalError)
 
 
 def test_match_buffer_shape_mismatch():
@@ -366,7 +371,7 @@ def test_match_buffer_shape_mismatch():
                 for jj in range(0, 4):
                     sub_A[i, j * 4 + jj] = 1
 
-    check_error(buffer_shape_mismatch, 7)
+    check_error(buffer_shape_mismatch, 7, tvm.error.InternalError)
 
 
 def test_high_dim_store():
@@ -376,7 +381,7 @@ def test_high_dim_store():
             for i, j in T.grid(16, 16):
                 B[i, j] = 1.0  # error: Store is only allowed with one index
 
-    check_error(high_dim_store, 5)
+    check_error(high_dim_store, 5, tvm.error.InternalError)
 
 
 def test_block_has_option_vars():
@@ -384,7 +389,7 @@ def test_block_has_option_vars():
         with T.sblock("root") as x:  # error: block does not support 
option_vars
             T.evaluate(0.0)
 
-    check_error(block_has_option_vars, 2)
+    check_error(block_has_option_vars, 2, TypeError)
 
 
 def test_implicit_root_has_attrs():
@@ -408,11 +413,11 @@ def test_implicit_root_has_attrs():
         v = T.axis.S(0, 0)  # error: implicit root does not support axis define
         T.evaluate(0.0)
 
-    check_error(implicit_root_has_read, 2)
-    check_error(implicit_root_has_write, 2)
-    check_error(implicit_root_has_attrs, 2)
-    check_error(implicit_root_has_predicate, 2)
-    check_error(implicit_root_has_axes, 2)
+    check_error(implicit_root_has_read, 2, ValueError)
+    check_error(implicit_root_has_write, 2, ValueError)
+    check_error(implicit_root_has_attrs, 2, tvm.error.InternalError)
+    check_error(implicit_root_has_predicate, 2, ValueError)
+    check_error(implicit_root_has_axes, 2, tvm.error.InternalError)
 
 
 @T.prim_func(s_tir=True)
@@ -502,7 +507,7 @@ def test_load_var():
         d = T.float32()
         d[2] = d[2, 1]  # error cannot provide two indices to load
 
-    check_error(load_var_multiple, 3)
+    check_error(load_var_multiple, 3, TypeError)
 
 
 def test_store_var():
@@ -510,7 +515,7 @@ def test_store_var():
         d = T.float32()
         d[2, 1] = d[1]  # error cannot provide two indices to store
 
-    check_error(store_var_multiple, 3)
+    check_error(store_var_multiple, 3, TypeError)
 
 
 def test_load_handle():
@@ -518,7 +523,7 @@ def test_load_handle():
         h_ = T.match_buffer(h, [1])
         h_[0] = h[0]  # error cannot load from handle
 
-    check_error(load_handle, 3)
+    check_error(load_handle, 3, TypeError)
 
 
 def test_store_handle():
@@ -526,7 +531,7 @@ def test_store_handle():
         h_ = T.match_buffer(h, [1])
         h[0] = h_[0]  # error cannot store to handle
 
-    check_error(store_handle, 3)
+    check_error(store_handle, 3, TypeError)
 
 
 def test_binop_bad_ast_type():
@@ -534,7 +539,7 @@ def test_binop_bad_ast_type():
         h_ = T.match_buffer(h, [1])
         h_[0] = h + [2]  # error rhs should be a primexpr
 
-    check_error(binop_bad_ast_type, 3)
+    check_error(binop_bad_ast_type, 3, TypeError)
 
 
 def test_binop_bad_type():
@@ -542,7 +547,7 @@ def test_binop_bad_type():
         h_ = T.match_buffer(h, [1])
         h_[0] = h + 2  # error lhs and rhs should be the same type
 
-    check_error(binop_bad_type, 3)
+    check_error(binop_bad_type, 3, TypeError)
 
 
 def test_non_integer_typed_block_iter():
@@ -550,7 +555,7 @@ def test_non_integer_typed_block_iter():
         with T.sblock():
             i = T.axis.S(0.1, 0.1)  # error IterVar requires an integer dtype
 
-    check_error(non_integer_typed_block_iter, 3)
+    check_error(non_integer_typed_block_iter, 3, tvm.error.InternalError)
 
 
 def test_illegal_buffer_slice():
@@ -573,9 +578,9 @@ def test_illegal_buffer_slice():
         for i in range(4):
             T.evaluate(A[0:i:1])  # error
 
-    check_error(strided_buffer_region, 3)
-    check_error(access_reversed_slice, 3)
-    check_error(access_non_const_slice_length, 3)
+    check_error(strided_buffer_region, 3, tvm.error.InternalError)
+    check_error(access_reversed_slice, 3, tvm.error.InternalError)
+    check_error(access_non_const_slice_length, 3, tvm.error.InternalError)
 
 
 def test_syntax_sugar_fail():
@@ -584,13 +589,11 @@ def test_syntax_sugar_fail():
         for i in T.thread_binding(128, 128):
             A[i] = A[i] * 2.0
 
-    check_error(loop_syntax_sugar_fail, 3)
+    check_error(loop_syntax_sugar_fail, 3, ValueError)
 
 
 def test_multi_line_error_report():
-    """A parse error whose offending AST node spans several physical source
-    lines must render ALL spanned lines (each with its own gutter line number
-    and an underline covering the span), not just the first line."""
+    """An original builder failure retains the full source-call traceback 
range."""
 
     # The offending call (`T.axis.remap(...)`) is deliberately split across
     # four physical lines so its AST node spans lineno..end_lineno > lineno.
@@ -607,69 +610,26 @@ def test_multi_line_error_report():
         ]
     )
 
-    with pytest.raises(tvm.error.DiagnosticError) as execinfo:
+    with pytest.raises(tvm.error.InternalError) as caught:
         from_source(source_code)
-    err_str = str(execinfo.value)
-
-    # All four spanned source lines must appear in the rendered snippet.
-    assert "T.axis.remap(" in err_str, err_str
-    assert '"S",' in err_str, err_str
-    assert "[i, j]," in err_str, err_str
-    # The trailing `)` closing line is also part of the span.
-    rendered_lines = err_str.splitlines()
-    assert any(" 7 " in line and ")" in line for line in rendered_lines), 
err_str
-    # The underline carets must be present on more than one line (multi-line).
-    marker_lines = [line for line in rendered_lines if "^" in line]
-    assert len(marker_lines) >= 2, err_str
-    # The gutter must show distinct line numbers for the spanned lines.
-    assert " 4 " in err_str and " 5 " in err_str and " 6 " in err_str, err_str
-
-
-def test_format_source_snippet_multi_line():
-    """Unit-level check that _format_source_snippet renders every line in a
-    multi-line span, with the underline covering start-col..EOL on the first
-    line, full interior lines, and col-1..end-col on the last line."""
-    from tvm.script.parser.diagnostics import _format_source_snippet
-
-    source_lines = [
-        "first ignored line\n",
-        "    foo(bar,\n",
-        "        baz,\n",
-        "        qux)\n",
-        "last ignored line\n",
+    frames = [
+        frame
+        for frame in traceback.extract_tb(caught.value.__traceback__)
+        if frame.filename == "<str>"
     ]
-    # Span lines 2..4 (1-based), starting at col 5 ('foo'), ending at col 13
-    # (exclusive) on line 4.
-    snippet = _format_source_snippet(
-        source_lines, lineno=2, col_offset=5, end_lineno=4, end_col_offset=13
-    )
-    lines = snippet.splitlines()
-    # All three spanned source lines must be present.
-    assert any("foo(bar," in line for line in lines), snippet
-    assert any("baz," in line for line in lines), snippet
-    assert any("qux)" in line for line in lines), snippet
-    # Underline carets present on the first line under 'foo(bar,'.
-    assert "^" in snippet, snippet
-    # The line numbers 2, 3, 4 appear in the gutter.
-    assert " 2 |" in snippet and " 3 |" in snippet and " 4 |" in snippet, 
snippet
-
-
-def test_format_source_snippet_single_line_unchanged():
-    """A single-line span (end_lineno == lineno) underlines only the
-    [col_offset, end_col_offset) columns on that one line."""
-    from tvm.script.parser.diagnostics import _format_source_snippet
-
-    source_lines = ["ignored\n", "    abc + def\n", "ignored\n"]
-    # Underline just 'abc' (cols 5..8 exclusive) on line 2.
-    snippet = _format_source_snippet(
-        source_lines, lineno=2, col_offset=5, end_lineno=2, end_col_offset=8
-    )
-    lines = snippet.splitlines()
-    # Exactly one source-text line and one marker line (plus the leading 
gutter).
-    text_lines = [line for line in lines if "abc + def" in line]
-    assert len(text_lines) == 1, snippet
-    marker_line = next(line for line in lines if "^" in line)
-    assert marker_line.count("^") == 3, snippet
+    assert frames[-1].lineno == 4
+    if getattr(frames[-1], "colno", None) is not None:
+        call = next(
+            node
+            for node in ast.walk(ast.parse(source_code))
+            if isinstance(node, ast.Call) and node.lineno == 4
+        )
+        assert (
+            frames[-1].lineno,
+            frames[-1].colno,
+            frames[-1].end_lineno,
+            frames[-1].end_colno,
+        ) == (call.lineno, call.col_offset, call.end_lineno, 
call.end_col_offset)
 
 
 if __name__ == "__main__":
diff --git a/tests/python/tvmscript/test_tvmscript_ops.py 
b/tests/python/tvmscript/test_tvmscript_ops.py
index 7793fb72c6..5184043a7e 100644
--- a/tests/python/tvmscript/test_tvmscript_ops.py
+++ b/tests/python/tvmscript/test_tvmscript_ops.py
@@ -247,7 +247,7 @@ try:
         T.bitwise_or(A[0:5] > T.Broadcast(T.float32(0), 5), B[0:5] > 
T.Broadcast(T.float32(0), 5))
         T.bitwise_and(A[0:5] < T.Broadcast(T.float32(0), 5), 
T.Broadcast(T.bool(1), 5))
         T.bitwise_or(A[0:5] > T.Broadcast(T.float32(0), 5), 
T.Broadcast(T.bool(1), 5))
-except tvm.error.DiagnosticError:
+except TypeError:
     slice_op_test = None
     slice_op_test_ref = None
 
diff --git a/tests/python/tvmscript/test_tvmscript_parser_source.py 
b/tests/python/tvmscript/test_tvmscript_parser_source.py
index 6ab4dc552b..3939a76cfb 100644
--- a/tests/python/tvmscript/test_tvmscript_parser_source.py
+++ b/tests/python/tvmscript/test_tvmscript_parser_source.py
@@ -17,6 +17,7 @@
 # ruff: noqa: F401
 """Source and span tests for the canonical parser"""
 
+import ast as doc
 import inspect
 
 import pytest
@@ -27,8 +28,7 @@ import tvm
 import tvm.testing
 from tvm.ir import Call, SequentialSpan, TensorLoad, assert_structural_equal
 from tvm.script import tirx as T
-import ast as doc
-from tvm.script.parser.source import Source
+from tvm.script.parser.inspect_source import Source
 from tvm.script.tirx import tile as Tx
 from tvm.tirx.stmt import TilePrimitiveCall
 
diff --git a/tests/python/tvmscript/test_tvmscript_parser_tir.py 
b/tests/python/tvmscript/test_tvmscript_parser_tir.py
index 0321bedc3e..1e3837025e 100644
--- a/tests/python/tvmscript/test_tvmscript_parser_tir.py
+++ b/tests/python/tvmscript/test_tvmscript_parser_tir.py
@@ -134,7 +134,7 @@ def main(A: T.Buffer(("n",), "float32")):
 
 
 def test_tir_direct_use_before_string_definition_is_undefined():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(NameError):
         tvm.script.from_source(
             """
 @T.prim_func
@@ -162,12 +162,12 @@ class Module:
     ],
 )
 def test_tir_direct_later_prim_param_is_undefined(source):
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(NameError):
         tvm.script.from_source(source)
 
 
 def test_tir_return_annotation_does_not_define_symbolic_var():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
         tvm.script.from_source(
             """
 @T.prim_func
@@ -228,7 +228,7 @@ def test_tir_func_private_attrs():
 
 
 def test_tir_func_private_manual_global_symbol_fail():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(tvm.error.InternalError):
 
         @T.prim_func(private=True, s_tir=True)
         def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
diff --git a/tests/python/tvmscript/test_tvmscript_syntax_sugar.py 
b/tests/python/tvmscript/test_tvmscript_syntax_sugar.py
index 006e415012..2184f9a09f 100644
--- a/tests/python/tvmscript/test_tvmscript_syntax_sugar.py
+++ b/tests/python/tvmscript/test_tvmscript_syntax_sugar.py
@@ -233,7 +233,7 @@ def test_match_buffer_region_has_implicit_shape_dtype():
 
 
 def test_match_buffer_input_requires_shape_arg():
-    with pytest.raises(tvm.error.DiagnosticError):
+    with pytest.raises(ValueError):
 
         @T.prim_func(s_tir=True)
         def func(a: T.handle):

Reply via email to