Files
apache--tvm/tests/python/tvmscript/test_tvmscript_printer_structural_equal.py
Tianqi Chen 4fd6cfab15 [TVMScript] Render invisible paths in structural diagnostics (#19916)
Structural diagnostics can identify a field below an object that
TVMScript renders without exposing that field. An underline alone then
points at the nearest visible parent and hides the full internal
location. This change keeps Script string-returning while making the
diagnostic context self-contained.

When the requested path is <root>.dtype, Script now returns this string
by default:

```text
Access path: <root>.dtype
Note: The underlined object is the nearest visible parent of this path.

T.int32
^^^^^^^
```

render_invisible_path_info defaults to true. Calls without target paths
are unchanged, and callers can set it to false to retain the legacy
underline-only string.

The implementation reuses the printer span-selection logic to capture
the deepest visible path and assembles the minimal
access-path/note/script block in C++. Pass-error enrichment uses the
same Script path. Production Python remains unchanged; focused Python
tests assert the complete strings for default-on, explicit-false,
hidden, exact-visible, unavailable-visible, pass-error, and
structural-equality cases.
2026-06-30 14:37:06 -04:00

175 lines
5.2 KiB
Python

# 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.
# ruff: noqa: F841
import pytest
from tvm_ffi.access_path import AccessPath
import tvm
from tvm.ir import assert_structural_equal
from tvm.script import ir as I
from tvm.script import tirx as T
def _error_message(exception):
return str(exception)
def _expected_result(func1, func2, objpath1, objpath2):
return f"""StructuralEqual check failed, caused by lhs at {objpath1}:
{func1.script(path_to_underline=[objpath1], syntax_sugar=False)}
and rhs at {objpath2}:
{func2.script(path_to_underline=[objpath2], syntax_sugar=False)}"""
def test_prim_type_hidden_path_exact_message():
with pytest.raises(ValueError) as exc_info:
assert_structural_equal(tvm.ir.PrimType("int32"), tvm.ir.PrimType("float32"))
assert str(exc_info.value) == (
"StructuralEqual check failed, caused by lhs at <root>.dtype:\n"
"Access path: <root>.dtype\n"
"Note: The underlined object is the nearest visible parent of this path.\n\n"
"T.int32\n"
"^^^^^^^\n"
"and rhs at <root>.dtype:\n"
"Access path: <root>.dtype\n"
"Note: The underlined object is the nearest visible parent of this path.\n\n"
"T.float32\n"
"^^^^^^^^^"
)
def test_prim_func_buffer_map():
@T.prim_func(s_tir=True)
def func1(a: T.handle, b: T.handle):
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 128))
@T.prim_func(s_tir=True)
def func2(a: T.handle, b: T.handle):
A = T.match_buffer(a, (128, 128))
B = T.match_buffer(b, (128, 256))
func1 = func1.with_attr("global_symbol", "main")
func2 = func2.with_attr("global_symbol", "main")
with pytest.raises(ValueError) as ve:
assert_structural_equal(func1, func2)
assert _error_message(ve.value) == _expected_result(
func1,
func2,
AccessPath.root()
.attr("buffer_map")
.map_item(func1.params[1])
.attr("shape")
.array_item(1)
.attr("value"),
AccessPath.root()
.attr("buffer_map")
.map_item(func2.params[1])
.attr("shape")
.array_item(1)
.attr("value"),
)
def test_evaluate():
@I.ir_module(s_tir=True)
class module1:
@T.prim_func(s_tir=True)
def func():
T.evaluate(0)
@I.ir_module(s_tir=True)
class module2:
@T.prim_func(s_tir=True)
def func():
T.evaluate(1)
with pytest.raises(ValueError) as ve:
assert_structural_equal(module1, module2)
assert _error_message(ve.value) == _expected_result(
module1,
module2,
AccessPath.root()
.attr("functions")
.map_item(module1.get_global_var("func"))
.attr("body")
.attr("value")
.attr("value"),
AccessPath.root()
.attr("functions")
.map_item(module2.get_global_var("func"))
.attr("body")
.attr("value")
.attr("value"),
)
def test_allocate():
@T.prim_func(s_tir=True)
def func1():
a = T.alloc_buffer((128, 128), dtype="float32")
@T.prim_func(s_tir=True)
def func2():
a = T.alloc_buffer((256, 128), dtype="float32")
func1 = func1.with_attr("global_symbol", "main")
func2 = func2.with_attr("global_symbol", "main")
with pytest.raises(ValueError) as ve:
assert_structural_equal(func1, func2)
assert _error_message(ve.value) == _expected_result(
func1,
func2,
AccessPath.root().attr("body").attr("buffer").attr("shape").array_item(0).attr("value"),
AccessPath.root().attr("body").attr("buffer").attr("shape").array_item(0).attr("value"),
)
def test_for():
@T.prim_func(s_tir=True)
def func1():
for i, j in T.grid(128, 128):
with T.sblock():
pass
@T.prim_func(s_tir=True)
def func2():
for i, j, k in T.grid(128, 128, 128):
with T.sblock():
pass
func1 = func1.with_attr("global_symbol", "main")
func2 = func2.with_attr("global_symbol", "main")
with pytest.raises(ValueError) as ve:
assert_structural_equal(func1, func2)
assert _error_message(ve.value) == _expected_result(
func1,
func2,
AccessPath.root().attr("body").attr("block").attr("body").attr("body").attr("body"),
AccessPath.root().attr("body").attr("block").attr("body").attr("body").attr("body"),
)
if __name__ == "__main__":
tvm.testing.main()