This is an automated email from the ASF dual-hosted git repository.
spectrometerHBH pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 622e96b7 [FEAT] Add named type schema groups for function signatures
(#820)
622e96b7 is described below
commit 622e96b766a3ae3024967e7ad07904e1b674824e
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Sep 27 13:36:50 2026 -0400
[FEAT] Add named type schema groups for function signatures (#820)
- Add recursive `named_args` lists to type schemas while retaining
positional `args` and `fallback`.
- Describe typed functions with named `return` and `params` groups,
including an explicit empty parameter list.
- Update C++ and Python producers, reflection consumers, binding
generation, and type-schema documentation.
---
.github/actions/build-wheel-for-publish/action.yml | 2 +-
.github/workflows/ci_mainline_only.yml | 4 +-
.github/workflows/ci_test.yml | 8 +-
.github/workflows/torch_c_dlpack.yml | 6 +-
docs/guides/cpp_lang_guide.md | 15 ++-
include/tvm/ffi/extra/stl.h | 7 +-
include/tvm/ffi/function.h | 4 +-
include/tvm/ffi/function_details.h | 9 +-
python/tvm_ffi/core.pyi | 2 +
python/tvm_ffi/cython/type_info.pxi | 100 +++++++++++---
python/tvm_ffi/dataclasses/py_class.py | 4 +-
python/tvm_ffi/stub/python_generator/utils.py | 19 ++-
python/tvm_ffi/stub/utils.py | 1 +
tests/cpp/test_metadata.cc | 148 +++++++++++++--------
tests/cpp/test_reflection.cc | 7 +-
tests/python/test_stubgen.py | 42 +++++-
tests/python/test_type_converter.py | 22 ++-
17 files changed, 289 insertions(+), 111 deletions(-)
diff --git a/.github/actions/build-wheel-for-publish/action.yml
b/.github/actions/build-wheel-for-publish/action.yml
index f51808c5..fded3153 100644
--- a/.github/actions/build-wheel-for-publish/action.yml
+++ b/.github/actions/build-wheel-for-publish/action.yml
@@ -46,7 +46,7 @@ runs:
using: "composite"
steps:
- name: Set up uv
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
- name: Check out source
uses: actions/checkout@v5
diff --git a/.github/workflows/ci_mainline_only.yml
b/.github/workflows/ci_mainline_only.yml
index bfb0ac68..c919e9da 100644
--- a/.github/workflows/ci_mainline_only.yml
+++ b/.github/workflows/ci_mainline_only.yml
@@ -50,7 +50,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Set up uv
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
- uses: ./.github/actions/detect-env-vars
id: env_vars
- name: Run clang-tidy
@@ -108,7 +108,7 @@ jobs:
fetch-tags: true
- name: Set up uv
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
with:
python-version: ${{ matrix.python_version }}
activate-environment: true
diff --git a/.github/workflows/ci_test.yml b/.github/workflows/ci_test.yml
index 2db75128..a4d9bf36 100644
--- a/.github/workflows/ci_test.yml
+++ b/.github/workflows/ci_test.yml
@@ -72,7 +72,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Set up uv
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
- name: Set up Python environment
run: uv sync --group dev --no-install-project
- name: Run pre-commit
@@ -89,7 +89,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Set up uv
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
with:
python-version: 3.13
- uses: ./.github/actions/detect-env-vars
@@ -112,7 +112,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Set up uv
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
with:
python-version: 3.13
- name: Generate docs
@@ -183,7 +183,7 @@ jobs:
# Run Python tests
- name: Setup Python ${{ matrix.python_version }}
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
with:
python-version: ${{ matrix.python_version }}
activate-environment: true
diff --git a/.github/workflows/torch_c_dlpack.yml
b/.github/workflows/torch_c_dlpack.yml
index 5aacba94..cd4afbd3 100644
--- a/.github/workflows/torch_c_dlpack.yml
+++ b/.github/workflows/torch_c_dlpack.yml
@@ -83,7 +83,7 @@ jobs:
path: tvm-ffi
- uses: Jimver/cuda-toolkit@3d45d157f327c09c04b50ee6ccdea2d9d017ec76 #
v0.2.35
id: cuda-toolkit
- - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
- name: build wheels
env:
CUDA_HOME: ${{steps.cuda-toolkit.outputs.CUDA_PATH}}
@@ -109,7 +109,7 @@ jobs:
fetch-depth: 0
submodules: recursive
path: tvm-ffi
- - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
- name: build torch libs and wheels
working-directory: ./tvm-ffi
run: |
@@ -128,7 +128,7 @@ jobs:
fetch-depth: 0
submodules: recursive
path: tvm-ffi
- - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 #
v8.2.0
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 #
v10.2.0
with:
python-version: "3.12"
activate-environment: true
diff --git a/docs/guides/cpp_lang_guide.md b/docs/guides/cpp_lang_guide.md
index d964421c..fe88ea5c 100644
--- a/docs/guides/cpp_lang_guide.md
+++ b/docs/guides/cpp_lang_guide.md
@@ -330,7 +330,20 @@ signature validation without calling the function.
The metadata contains:
-- **type_schema**: JSON string describing function signature (return type and
argument types)
+- **type_schema**: JSON string describing the function signature.
+
+A type schema has a nonempty `type` and may have positional `args`, named
+`named_args`, and `fallback`. `named_args` maps each group name to a list of
+complete type schemas; the list may be empty, and types in it may have their
+own `args`, `named_args`, or `fallback`. A function uses a one-element `return`
+list and an ordered `params` list:
+
+```json
+{"type":"ffi.Function","named_args":{"return":[{"type":"int"}],"params":[{"type":"int"}]}}
+```
+
+An empty `params` list represents a known zero-argument function. A bare
+`{"type":"ffi.Function"}` leaves the signature unspecified.
```cpp
ffi::Module mod = ffi::Module::LoadFromFile("path/to/export_lib.so");
diff --git a/include/tvm/ffi/extra/stl.h b/include/tvm/ffi/extra/stl.h
index 3f3e4213..e30c1d1b 100644
--- a/include/tvm/ffi/extra/stl.h
+++ b/include/tvm/ffi/extra/stl.h
@@ -453,6 +453,7 @@ struct TypeTraits<std::variant<Args...>> : public
TypeTraitsBase {
os << R"({"type":"std::variant","args":[)";
const char* sep = "";
((os << sep << details::TypeSchema<Args>::v(), sep = ", "), ...);
+ (void)sep;
os << "]}";
return std::move(os).str();
}
@@ -641,10 +642,12 @@ struct TypeTraits<std::function<Ret(Args...)>> :
TypeTraitsBase {
TVM_FFI_INLINE static std::string TypeSchema() {
std::ostringstream os;
- os << R"({"type":"std::function","args":[)" <<
details::TypeSchema<Ret>::v() << ",[";
+ os << R"({"type":"std::function","named_args":{"return":[)" <<
details::TypeSchema<Ret>::v()
+ << R"(],"params":[)";
const char* sep = "";
((os << sep << details::TypeSchema<Args>::v(), sep = ", "), ...);
- os << "]]}";
+ (void)sep;
+ os << "]}}";
return std::move(os).str();
}
};
diff --git a/include/tvm/ffi/function.h b/include/tvm/ffi/function.h
index 30aed159..a7a02959 100644
--- a/include/tvm/ffi/function.h
+++ b/include/tvm/ffi/function.h
@@ -891,8 +891,8 @@ class TypedFunction<R(Args...)> {
/*! \return Whether the packed function is not nullptr */
bool operator!=(std::nullptr_t null) const { return packed_ != nullptr; }
/*!
- * \brief Get the type schema of `TypedFunction<R(Args...)>` in json format.
- * \return The type schema of the function in json format.
+ * \brief Get the JSON type schema of `TypedFunction<R(Args...)>`.
+ * \return An `ffi.Function` schema with named `return` and `params` type
lists.
*/
static std::string TypeSchema() { return details::FuncFunctorImpl<R,
Args...>::TypeSchema(); }
diff --git a/include/tvm/ffi/function_details.h
b/include/tvm/ffi/function_details.h
index cf33a79c..b2f41ce0 100644
--- a/include/tvm/ffi/function_details.h
+++ b/include/tvm/ffi/function_details.h
@@ -99,10 +99,13 @@ struct FuncFunctorImpl {
}
TVM_FFI_INLINE static std::string TypeSchema() {
std::ostringstream oss;
- oss << R"({"type":")" << StaticTypeKey::kTVMFFIFunction << R"(","args":[)";
+ oss << R"({"type":")" << StaticTypeKey::kTVMFFIFunction <<
R"(","named_args":{"return":[)";
oss << details::TypeSchema<R>::v();
- ((oss << "," << details::TypeSchema<Args>::v()), ...);
- oss << "]}";
+ oss << R"(],"params":[)";
+ const char* sep = "";
+ ((oss << sep << details::TypeSchema<Args>::v(), sep = ","), ...);
+ (void)sep;
+ oss << "]}}";
return oss.str();
}
};
diff --git a/python/tvm_ffi/core.pyi b/python/tvm_ffi/core.pyi
index de5ddac4..f4629515 100644
--- a/python/tvm_ffi/core.pyi
+++ b/python/tvm_ffi/core.pyi
@@ -272,6 +272,7 @@ class TypeSchema:
args: tuple[TypeSchema, ...] = ()
origin_type_index: int
fallback: TypeSchema | None
+ named_args: dict[str, tuple[TypeSchema, ...]] | None
def __init__(
self,
@@ -279,6 +280,7 @@ class TypeSchema:
args: tuple[TypeSchema, ...] = (),
origin_type_index: int = ...,
fallback: TypeSchema | None = None,
+ named_args: dict[str, tuple[TypeSchema, ...]] | None = None,
) -> None: ...
@staticmethod
def from_json_obj(obj: dict[str, Any]) -> TypeSchema: ...
diff --git a/python/tvm_ffi/cython/type_info.pxi
b/python/tvm_ffi/cython/type_info.pxi
index cc6e0006..4e7fdc2f 100644
--- a/python/tvm_ffi/cython/type_info.pxi
+++ b/python/tvm_ffi/cython/type_info.pxi
@@ -161,14 +161,15 @@ _TYPE_INDEX_TO_ORIGIN[kTVMFFIBigInt] = "int"
class TypeSchema:
"""Type schema that describes a TVM FFI type.
- The schema is expressed using a compact JSON-compatible structure
- and can be rendered as a Python typing string with
- :py:meth:`repr`.
+ ``args`` holds positional type arguments. ``named_args`` maps each name
+ to an ordered list of complete type schemas. A typed function uses
+ ``return`` and ``params`` as named groups.
"""
origin: str
args: tuple["TypeSchema", ...] | None = None
origin_type_index: int =
dataclasses.field(default=_ORIGIN_TYPE_INDEX_UNKNOWN, repr=False)
fallback: "TypeSchema | None" = None
+ named_args: "dict[str, tuple[TypeSchema, ...]] | None" = None
def __post_init__(self):
origin = self.origin
@@ -176,9 +177,39 @@ class TypeSchema:
if args is not None and not isinstance(args, tuple):
args = tuple(args)
self.args = args
+ if not isinstance(origin, str) or not origin:
+ raise ValueError("schema type must be a nonempty string")
if origin != "tuple" and args is None:
args = ()
self.args = args
+ if args is not None and any(not isinstance(a, TypeSchema) for a in
args):
+ raise TypeError("schema arguments must be TypeSchema instances")
+ named_args = self.named_args
+ if named_args is not None:
+ if not isinstance(named_args, dict):
+ raise TypeError("named_args must be a map of names to type
lists")
+ normalized = {}
+ for name, values in named_args.items():
+ if not isinstance(name, str) or not name:
+ raise ValueError("named_args keys must be nonempty
strings")
+ if not isinstance(values, (list, tuple)):
+ raise TypeError("named_args values must be lists of type
schemas")
+ if any(not isinstance(value, TypeSchema) for value in values):
+ raise TypeError("named_args values must contain type
schemas")
+ normalized[name] = tuple(values)
+ self.named_args = named_args = normalized
+ if origin == "Callable":
+ if args and named_args is None:
+ # Legacy flat schemas retain return-only unknown parameters.
+ self.named_args = named_args = {"return": (args[0],)}
+ if len(args) > 1:
+ named_args["params"] = args[1:]
+ self.args = args = ()
+ if named_args is not None:
+ if args or set(named_args) - {"return", "params"}:
+ raise ValueError("Callable uses only named return and
params groups")
+ if "return" not in named_args or len(named_args["return"]) !=
1:
+ raise ValueError("Callable return group must contain
exactly one type")
if origin == "Union":
if len(args) < 2:
raise ValueError("Union must have at least two arguments")
@@ -270,18 +301,39 @@ class TypeSchema:
f"expected schema dict with 'type' key, got
{type(obj).__name__}"
)
origin = obj["type"]
+ if not isinstance(origin, str):
+ raise TypeError("schema type must be a string")
origin = _TYPE_SCHEMA_ORIGIN_CONVERTER.get(origin, origin)
fallback = TypeSchema.from_json_obj(obj["fallback"]) if "fallback" in
obj else None
+ raw_named = obj.get("named_args")
+ named_args = None
+ if "named_args" in obj:
+ if not isinstance(raw_named, dict):
+ raise TypeError("named_args must be a map of names to type
lists")
+ named_args = {}
+ for name, values in raw_named.items():
+ if not isinstance(name, str) or not name or not
isinstance(values, list):
+ raise TypeError("named_args must map names to type lists")
+ if any(not isinstance(value, dict) for value in values):
+ raise TypeError("named_args lists must contain type
schemas")
+ named_args[name] = tuple(TypeSchema.from_json_obj(value) for
value in values)
if "args" not in obj:
- return TypeSchema(origin, fallback=fallback)
+ return TypeSchema(origin, fallback=fallback, named_args=named_args)
raw_args = obj["args"]
+ if (obj["type"] == "std::function" and isinstance(raw_args, (list,
tuple))
+ and len(raw_args) == 2 and isinstance(raw_args[1], (list,
tuple))):
+ # Legacy std::function encoded parameters as a bare JSON array.
+ ret = TypeSchema.from_json_obj(raw_args[0])
+ params = tuple(TypeSchema.from_json_obj(value) for value in
raw_args[1])
+ return TypeSchema(origin, fallback=fallback,
+ named_args={"return": (ret,), "params": params})
if not isinstance(raw_args, (list, tuple)):
raw_args = ()
args = tuple(
TypeSchema.from_json_obj(a) for a in raw_args
if isinstance(a, dict)
)
- return TypeSchema(origin, args, fallback=fallback)
+ return TypeSchema(origin, args, fallback=fallback,
named_args=named_args)
@staticmethod
def from_json_str(s: str) -> "TypeSchema":
@@ -420,13 +472,15 @@ class TypeSchema:
params, ret = targs
ret_schema = TypeSchema.from_annotation(ret)
if isinstance(params, list):
- # Callable[[P1, P2], R] → (R, P1, P2)
+ # Callable[[P1, P2], R] → named return and params groups.
param_schemas = tuple(
TypeSchema.from_annotation(p) for p in params
)
- return TypeSchema("Callable", (ret_schema,) +
param_schemas)
+ return TypeSchema(
+ "Callable", named_args={"return": (ret_schema,),
"params": param_schemas}
+ )
# Callable[..., R]
- return TypeSchema("Callable", (ret_schema,))
+ return TypeSchema("Callable", named_args={"return":
(ret_schema,)})
return TypeSchema("Callable")
# --- Parameterised CObject subclasses (Array[int], Dict[str, V], …)
---
@@ -547,8 +601,9 @@ class TypeSchema:
s =
TypeSchema.from_json_str('{"type":"Optional","args":[{"type":"int"}]}')
assert s.repr() == "int | None"
- # Callable where the first arg is return type, remaining are
parameters
- s = TypeSchema("Callable", (TypeSchema("int"), TypeSchema("str")))
+ # Named return and parameter groups
+ s = TypeSchema("Callable", named_args={"return":
(TypeSchema("int"),),
+ "params":
(TypeSchema("str"),)})
assert s.repr() == "Callable[[str], int]"
# Container types from C++ FFI schemas
@@ -594,6 +649,19 @@ class TypeSchema:
origin = self.origin if ty_map is None else ty_map(self.origin)
schema_args = self.args
+ if origin == "Callable":
+ if not self.named_args:
+ return "Callable[..., Any]"
+ ret = self.named_args["return"][0]._repr_impl(
+ ty_map, input_mode, expanded_convert_types
+ )
+ if "params" not in self.named_args:
+ return f"Callable[..., {ret}]"
+ params = ", ".join(
+ arg._repr_impl(ty_map, input_mode, expanded_convert_types)
+ for arg in self.named_args["params"]
+ )
+ return f"Callable[[{params}], {ret}]"
args = [
i._repr_impl(ty_map, input_mode, expanded_convert_types)
for i in (() if schema_args is None else schema_args)
@@ -602,13 +670,6 @@ class TypeSchema:
return " | ".join(args)
elif origin == "Optional":
return args[0] + " | None"
- elif origin == "Callable":
- if not args:
- return "Callable[..., Any]"
- else:
- ret = args[0]
- args = ", ".join(args[1:])
- return f"Callable[[{args}], {ret}]"
elif origin == "tuple" and schema_args == ():
return "tuple[()]"
elif not args:
@@ -626,6 +687,11 @@ class TypeSchema:
}
else:
result = {"type": self.origin}
+ if self.named_args is not None:
+ result["named_args"] = {
+ name: [value.to_json() for value in values]
+ for name, values in self.named_args.items()
+ }
if self.fallback is not None:
result["fallback"] = self.fallback.to_json()
return result
diff --git a/python/tvm_ffi/dataclasses/py_class.py
b/python/tvm_ffi/dataclasses/py_class.py
index 1c581e75..4329ba42 100644
--- a/python/tvm_ffi/dataclasses/py_class.py
+++ b/python/tvm_ffi/dataclasses/py_class.py
@@ -289,7 +289,9 @@ def _method_type_schema_json(
ret_annotation = hints.get("return", Any)
ret_schema = TypeSchema.from_annotation(ret_annotation)
- schema = TypeSchema("Callable", (ret_schema, *arg_schemas))
+ schema = TypeSchema(
+ "Callable", named_args={"return": (ret_schema,), "params":
tuple(arg_schemas)}
+ )
return json.dumps({"type_schema": schema.to_json()})
diff --git a/python/tvm_ffi/stub/python_generator/utils.py
b/python/tvm_ffi/stub/python_generator/utils.py
index bf19b367..e4d6dfc9 100644
--- a/python/tvm_ffi/stub/python_generator/utils.py
+++ b/python/tvm_ffi/stub/python_generator/utils.py
@@ -113,12 +113,16 @@ def render_func_signature(
buf.write(f"def {func_name}(")
if func.schema.origin != "Callable":
raise ValueError(f"Expected Callable type schema, but got:
{func.schema}")
- if not func.schema.args:
+ if not func.schema.named_args:
ty_map("Any")
buf.write("*args: Any) -> Any: ...")
return buf.getvalue()
- arg_ret = func.schema.args[0]
- arg_args = func.schema.args[1:]
+ arg_ret = func.schema.named_args["return"][0]
+ if "params" not in func.schema.named_args:
+ ty_map("Any")
+ buf.write(f"*args: Any) -> {arg_ret.output_repr(ty_map)}: ...")
+ return buf.getvalue()
+ arg_args = func.schema.named_args["params"]
for i, arg in enumerate(arg_args):
if func.is_member and i == 0:
buf.write("self, ")
@@ -192,12 +196,15 @@ def _render_ffi_init_from_method(
input_ty_map = ty_map
indent_str = " " * indent
schema = method.schema
- if schema.origin != "Callable" or not schema.args:
+ if schema.origin != "Callable" or not schema.named_args:
+ ty_map("Any")
+ return f"{indent_str}def __ffi_init__(self, *args: Any) -> None: ..."
+ if "params" not in schema.named_args:
ty_map("Any")
return f"{indent_str}def __ffi_init__(self, *args: Any) -> None: ..."
- # schema.args[0] is return type, schema.args[1:] are param types.
+ # Function parameters are an ordered list in named_args["params"].
parts: list[str] = []
- for i, arg in enumerate(schema.args[1:]):
+ for i, arg in enumerate(schema.named_args["params"]):
parts.append(f"_{i}: {arg.input_repr(input_ty_map)}")
if parts:
params = ", ".join(parts)
diff --git a/python/tvm_ffi/stub/utils.py b/python/tvm_ffi/stub/utils.py
index f317f9c4..e7f7eb94 100644
--- a/python/tvm_ffi/stub/utils.py
+++ b/python/tvm_ffi/stub/utils.py
@@ -136,6 +136,7 @@ class NamedTypeSchema(TypeSchema):
args=schema.args,
origin_type_index=schema.origin_type_index,
fallback=schema.fallback,
+ named_args=schema.named_args,
)
self.name = name
self.size = size
diff --git a/tests/cpp/test_metadata.cc b/tests/cpp/test_metadata.cc
index ddb1abda..af701416 100644
--- a/tests/cpp/test_metadata.cc
+++ b/tests/cpp/test_metadata.cc
@@ -20,9 +20,11 @@
#include <tvm/ffi/c_api.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
+#include <tvm/ffi/container/tuple.h>
#include <tvm/ffi/container/variant.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/extra/json.h>
+#include <tvm/ffi/extra/stl.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/object.h>
#include <tvm/ffi/optional.h>
@@ -71,70 +73,85 @@ TEST(Schema, GlobalFuncTypeSchema) {
return ParseMetadataToSchema(metadata);
};
// Simple IDs
- EXPECT_EQ(fetch("testing.schema_id_int"),
-
R"({"type":"ffi.Function","args":[{"type":"int"},{"type":"int"}]})");
- EXPECT_EQ(fetch("testing.schema_id_float"),
-
R"({"type":"ffi.Function","args":[{"type":"float"},{"type":"float"}]})");
- EXPECT_EQ(fetch("testing.schema_id_bool"),
-
R"({"type":"ffi.Function","args":[{"type":"bool"},{"type":"bool"}]})");
- EXPECT_EQ(fetch("testing.schema_id_device"),
-
R"({"type":"ffi.Function","args":[{"type":"Device"},{"type":"Device"}]})");
- EXPECT_EQ(fetch("testing.schema_id_dtype"),
-
R"({"type":"ffi.Function","args":[{"type":"DataType"},{"type":"DataType"}]})");
- EXPECT_EQ(fetch("testing.schema_id_string"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.String"},{"type":"ffi.String"}]})");
- EXPECT_EQ(fetch("testing.schema_id_bytes"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Bytes"},{"type":"ffi.Bytes"}]})");
- EXPECT_EQ(fetch("testing.schema_id_func"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Function"},{"type":"ffi.Function"}]})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_int"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"int"}],"params":[{"type":"int"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_float"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"float"}],"params":[{"type":"float"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_bool"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"bool"}],"params":[{"type":"bool"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_device"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Device"}],"params":[{"type":"Device"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_dtype"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"DataType"}],"params":[{"type":"DataType"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_string"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.String"}],"params":[{"type":"ffi.String"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_bytes"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Bytes"}],"params":[{"type":"ffi.Bytes"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_func"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Function"}],"params":[{"type":"ffi.Function"}]}})");
EXPECT_EQ(
fetch("testing.schema_id_func_typed"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Function","args":[{"type":"None"},{"type":"int"},{"type":"float"},{"type":"ffi.Function"}]},{"type":"ffi.Function","args":[{"type":"None"},{"type":"int"},{"type":"float"},{"type":"ffi.Function"}]}]})");
-
- EXPECT_EQ(fetch("testing.schema_id_any"),
-
R"({"type":"ffi.Function","args":[{"type":"Any"},{"type":"Any"}]})");
- EXPECT_EQ(fetch("testing.schema_id_object"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Object"},{"type":"ffi.Object"}]})");
- EXPECT_EQ(fetch("testing.schema_id_dltensor"),
-
R"({"type":"ffi.Function","args":[{"type":"DLTensor*"},{"type":"DLTensor*"}]})");
- EXPECT_EQ(fetch("testing.schema_id_tensor"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Tensor"},{"type":"ffi.Tensor"}]})");
- EXPECT_EQ(fetch("testing.schema_tensor_view_input"),
-
R"({"type":"ffi.Function","args":[{"type":"None"},{"type":"DLTensor*"}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Function","named_args":{"return":[{"type":"None"}],"params":[{"type":"int"},{"type":"float"},{"type":"ffi.Function"}]}}],"params":[{"type":"ffi.Function","named_args":{"return":[{"type":"None"}],"params":[{"type":"int"},{"type":"float"},{"type":"ffi.Function"}]}}]}})");
+
+ EXPECT_EQ(
+ fetch("testing.schema_id_any"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Any"}],"params":[{"type":"Any"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_object"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Object"}],"params":[{"type":"ffi.Object"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_dltensor"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"DLTensor*"}],"params":[{"type":"DLTensor*"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_tensor"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Tensor"}],"params":[{"type":"ffi.Tensor"}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_tensor_view_input"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"None"}],"params":[{"type":"DLTensor*"}]}})");
EXPECT_EQ(
fetch("testing.schema_id_opt_int"),
-
R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"int"}]},{"type":"Optional","args":[{"type":"int"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Optional","args":[{"type":"int"}]}],"params":[{"type":"Optional","args":[{"type":"int"}]}]}})");
EXPECT_EQ(
fetch("testing.schema_id_opt_str"),
-
R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"ffi.String"}]},{"type":"Optional","args":[{"type":"ffi.String"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Optional","args":[{"type":"ffi.String"}]}],"params":[{"type":"Optional","args":[{"type":"ffi.String"}]}]}})");
EXPECT_EQ(
fetch("testing.schema_id_opt_obj"),
-
R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"ffi.Object"}]},{"type":"Optional","args":[{"type":"ffi.Object"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Optional","args":[{"type":"ffi.Object"}]}],"params":[{"type":"Optional","args":[{"type":"ffi.Object"}]}]}})");
EXPECT_EQ(
fetch("testing.schema_id_arr_int"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Array","args":[{"type":"int"}]},{"type":"ffi.Array","args":[{"type":"int"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Array","args":[{"type":"int"}]}],"params":[{"type":"ffi.Array","args":[{"type":"int"}]}]}})");
EXPECT_EQ(
fetch("testing.schema_id_arr_str"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Array","args":[{"type":"ffi.String"}]},{"type":"ffi.Array","args":[{"type":"ffi.String"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Array","args":[{"type":"ffi.String"}]}],"params":[{"type":"ffi.Array","args":[{"type":"ffi.String"}]}]}})");
EXPECT_EQ(
fetch("testing.schema_id_arr_obj"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Array","args":[{"type":"ffi.Object"}]},{"type":"ffi.Array","args":[{"type":"ffi.Object"}]}]})");
- EXPECT_EQ(fetch("testing.schema_id_arr"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Array"},{"type":"ffi.Array"}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Array","args":[{"type":"ffi.Object"}]}],"params":[{"type":"ffi.Array","args":[{"type":"ffi.Object"}]}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_arr"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Array"}],"params":[{"type":"ffi.Array"}]}})");
EXPECT_EQ(
fetch("testing.schema_id_map_str_int"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"int"}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"int"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"int"}]}],"params":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"int"}]}]}})");
EXPECT_EQ(
fetch("testing.schema_id_map_str_str"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.String"}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.String"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.String"}]}],"params":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.String"}]}]}})");
EXPECT_EQ(
fetch("testing.schema_id_map_str_obj"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Object"}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Object"}]}]})");
- EXPECT_EQ(fetch("testing.schema_id_map"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Map"},{"type":"ffi.Map"}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Object"}]}],"params":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Object"}]}]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_id_map"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Map"}],"params":[{"type":"ffi.Map"}]}})");
EXPECT_EQ(
fetch("testing.schema_id_variant_int_str"),
-
R"({"type":"ffi.Function","args":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"}]},{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"}]}],"params":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"}]}]}})");
// Packed function registered via def_packed: schema is plain ffi.Function
EXPECT_EQ(fetch("testing.schema_packed"), R"({"type":"ffi.Function"})");
@@ -142,18 +159,31 @@ TEST(Schema, GlobalFuncTypeSchema) {
// Mixed containers and optionals
EXPECT_EQ(
fetch("testing.schema_arr_map_opt"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"ffi.Array","args":[{"type":"Optional","args":[{"type":"int"}]}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"Optional","args":[{"type":"ffi.String"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}],"params":[{"type":"ffi.Array","args":[{"type":"Optional","args":[{"type":"int"}]}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"Optional","args":[{"type":"ffi.String"}]}]}})");
EXPECT_EQ(
fetch("testing.schema_variant_mix"),
-
R"({"type":"ffi.Function","args":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}],"params":[{"type":"Variant","args":[{"type":"int"},{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}]}})");
// No-arg and no-return combinations
- EXPECT_EQ(fetch("testing.schema_no_args"),
R"({"type":"ffi.Function","args":[{"type":"int"}]})");
- EXPECT_EQ(fetch("testing.schema_no_return"),
-
R"({"type":"ffi.Function","args":[{"type":"None"},{"type":"int"}]})");
+ EXPECT_EQ(fetch("testing.schema_no_args"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"int"}],"params":[]}})");
+ EXPECT_EQ(
+ fetch("testing.schema_no_return"),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"None"}],"params":[{"type":"int"}]}})");
EXPECT_EQ(fetch("testing.schema_no_args_no_return"),
- R"({"type":"ffi.Function","args":[{"type":"None"}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"None"}],"params":[]}})");
+}
+
+TEST(Schema, TypedFunctionGroups) {
+ EXPECT_EQ(
+ (TypedFunction<Tuple<int, String>()>::TypeSchema()),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Tuple","args":[{"type":"int"},{"type":"ffi.String"}]}],"params":[]}})");
+ EXPECT_EQ(
+ (TypeTraits<std::function<int(int)>>::TypeSchema()),
+
R"({"type":"std::function","named_args":{"return":[{"type":"int"}],"params":[{"type":"int"}]}})");
+ EXPECT_EQ((TypeTraits<std::function<int()>>::TypeSchema()),
+
R"({"type":"std::function","named_args":{"return":[{"type":"int"}],"params":[]}})");
}
TEST(Schema, FieldTypeSchemas) {
@@ -199,34 +229,36 @@ TEST(Schema, MethodTypeSchemas) {
// Instance methods
EXPECT_EQ(
method_schema("add_int"),
-
R"({"type":"ffi.Function","args":[{"type":"int"},{"type":"testing.SchemaAllTypes"},{"type":"int"}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"int"}],"params":[{"type":"testing.SchemaAllTypes"},{"type":"int"}]}})");
EXPECT_EQ(
method_schema("append_int"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Array","args":[{"type":"int"}]},{"type":"testing.SchemaAllTypes"},{"type":"ffi.Array","args":[{"type":"int"}]},{"type":"int"}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Array","args":[{"type":"int"}]}],"params":[{"type":"testing.SchemaAllTypes"},{"type":"ffi.Array","args":[{"type":"int"}]},{"type":"int"}]}})");
EXPECT_EQ(
method_schema("maybe_concat"),
-
R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"ffi.String"}]},{"type":"testing.SchemaAllTypes"},{"type":"Optional","args":[{"type":"ffi.String"}]},{"type":"Optional","args":[{"type":"ffi.String"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Optional","args":[{"type":"ffi.String"}]}],"params":[{"type":"testing.SchemaAllTypes"},{"type":"Optional","args":[{"type":"ffi.String"}]},{"type":"Optional","args":[{"type":"ffi.String"}]}]}})");
EXPECT_EQ(
method_schema("merge_map"),
-
R"({"type":"ffi.Function","args":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"testing.SchemaAllTypes"},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}],"params":[{"type":"testing.SchemaAllTypes"},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]},{"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"ffi.Array","args":[{"type":"int"}]}]}]}})");
// Static method make_with: return type is the object type itself.
// Build expected JSON as ffi.Function with return type = type_key and args
= (int, float, str)
EXPECT_EQ(
method_schema("make_with"),
-
R"({"type":"ffi.Function","args":[{"type":"testing.SchemaAllTypes"},{"type":"int"},{"type":"float"},{"type":"ffi.String"}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"testing.SchemaAllTypes"}],"params":[{"type":"int"},{"type":"float"},{"type":"ffi.String"}]}})");
}
TEST(Schema, DLLExportedFuncMetadata) {
// Minimal sanity check that DLL export metadata mechanism works.
-
EXPECT_EQ(ParseMetadataToSchema(CallMetadataFunc(__tvm_ffi__metadata_testing_dll_schema_id_int)),
-
R"({"type":"ffi.Function","args":[{"type":"int"},{"type":"int"}]})");
+ EXPECT_EQ(
+
ParseMetadataToSchema(CallMetadataFunc(__tvm_ffi__metadata_testing_dll_schema_id_int)),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"int"}],"params":[{"type":"int"}]}})");
}
TEST(Schema, DLLExportedFuncDocumentation) {
- EXPECT_EQ(ParseMetadataToSchema(
-
CallMetadataFunc(__tvm_ffi__metadata_testing_dll_test_add_with_docstring)),
-
R"({"type":"ffi.Function","args":[{"type":"int"},{"type":"int"},{"type":"int"}]})");
+ EXPECT_EQ(
+ ParseMetadataToSchema(
+
CallMetadataFunc(__tvm_ffi__metadata_testing_dll_test_add_with_docstring)),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"int"}],"params":[{"type":"int"},{"type":"int"}]}})");
String doc =
CallMetadataFunc(__tvm_ffi__doc_testing_dll_test_add_with_docstring);
std::string doc_str(doc);
EXPECT_EQ(doc_str,
diff --git a/tests/cpp/test_reflection.cc b/tests/cpp/test_reflection.cc
index f28a0574..84a80b6a 100644
--- a/tests/cpp/test_reflection.cc
+++ b/tests/cpp/test_reflection.cc
@@ -226,8 +226,9 @@ TEST(Reflection, ObjectPtrMethod) {
const TVMFFIMethodInfo* info =
reflection::GetMethodInfo("test.ObjectPtrHolder", "identity");
Map<String, Any> metadata =
json::Parse(String(info->metadata)).cast<Map<String, Any>>();
- EXPECT_EQ(metadata["type_schema"].cast<String>(),
-
R"({"type":"ffi.Function","args":[{"type":"test.Int"},{"type":"test.Int"}]})");
+ EXPECT_EQ(
+ metadata["type_schema"].cast<String>(),
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"test.Int"}],"params":[{"type":"test.Int"}]}})");
const TVMFFIMethodInfo* optional_info =
reflection::GetMethodInfo("test.ObjectPtrHolder", "optional_identity");
@@ -235,7 +236,7 @@ TEST(Reflection, ObjectPtrMethod) {
json::Parse(String(optional_info->metadata)).cast<Map<String, Any>>();
EXPECT_EQ(
optional_metadata["type_schema"].cast<String>(),
-
R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"test.Int"}]},{"type":"Optional","args":[{"type":"test.Int"}]}]})");
+
R"({"type":"ffi.Function","named_args":{"return":[{"type":"Optional","args":[{"type":"test.Int"}]}],"params":[{"type":"Optional","args":[{"type":"test.Int"}]}]}})");
}
TEST(Reflection, FieldInfo) {
diff --git a/tests/python/test_stubgen.py b/tests/python/test_stubgen.py
index 712835cf..0de76e91 100644
--- a/tests/python/test_stubgen.py
+++ b/tests/python/test_stubgen.py
@@ -196,6 +196,28 @@ def test_funcinfo_gen_variants() -> None:
assert render_func_signature(func, ty_map, indent=2) == " def
no_args(*args: Any) -> Any: ..."
assert called == ["Any"]
+ return_only = FuncInfo.from_schema("demo.unknown", TypeSchema("Callable",
(TypeSchema("int"),)))
+ assert (
+ render_func_signature(return_only, _identity_ty_map, indent=0)
+ == "def unknown(*args: Any) -> int: ..."
+ )
+
+ grouped = TypeSchema.from_json_obj(
+ {
+ "type": "ffi.Function",
+ "named_args": {
+ "return": [{"type": "Tuple", "args": [{"type": "int"},
{"type": "str"}]}],
+ "params": [],
+ },
+ }
+ )
+ copied = NamedTypeSchema("demo.grouped", grouped)
+ assert copied.named_args == grouped.named_args
+ assert (
+ render_func_signature(FuncInfo.from_schema("demo.grouped", copied),
_identity_ty_map, 0)
+ == "def grouped() -> tuple[int, str]: ..."
+ )
+
schema_member = NamedTypeSchema(
"pkg.Class.method",
TypeSchema(
@@ -236,7 +258,12 @@ def test_objectinfo_gen_fields_and_methods() -> None:
],
methods=[
FuncInfo(
- schema=NamedTypeSchema("demo.static", TypeSchema("Callable",
(TypeSchema("int"),))),
+ schema=NamedTypeSchema(
+ "demo.static",
+ TypeSchema(
+ "Callable", named_args={"return":
(TypeSchema("int"),), "params": ()}
+ ),
+ ),
is_member=False,
),
FuncInfo(
@@ -274,7 +301,7 @@ def test_objectinfo_gen_overloaded_static_methods() -> None:
),
FuncInfo.from_schema(
"demo.Factory.reset",
- TypeSchema("Callable", (TypeSchema("None"),)),
+ TypeSchema("Callable", named_args={"return":
(TypeSchema("None"),), "params": ()}),
is_member=False,
),
FuncInfo.from_schema(
@@ -462,8 +489,9 @@ def test_py_class_method_metadata_renders_stub_signature()
-> None:
describe_schema = methods["describe"].schema
assert describe_schema.origin == "Callable"
- assert [arg.origin for arg in describe_schema.args] == [
- "str",
+ assert describe_schema.named_args is not None
+ assert [arg.origin for arg in describe_schema.named_args["return"]] ==
["str"]
+ assert [arg.origin for arg in describe_schema.named_args["params"]] == [
MethodMetadata.__tvm_ffi_type_info__.type_key, # ty:
ignore[unresolved-attribute]
"List",
"str",
@@ -522,7 +550,9 @@ def
test_generate_global_funcs_imports_enum_from_dataclasses() -> None:
FuncInfo(
schema=NamedTypeSchema(
"demo.get_enum",
- TypeSchema("Callable", (TypeSchema("ffi.Enum"),)),
+ TypeSchema(
+ "Callable", named_args={"return":
(TypeSchema("ffi.Enum"),), "params": ()}
+ ),
),
is_member=False,
)
@@ -661,7 +691,7 @@ def test_generate_object_with_methods() -> None:
),
FuncInfo.from_schema(
"demo.IntPair.sum",
- TypeSchema("Callable", (TypeSchema("int"),)),
+ TypeSchema("Callable", named_args={"return":
(TypeSchema("int"),), "params": ()}),
is_member=True,
),
],
diff --git a/tests/python/test_type_converter.py
b/tests/python/test_type_converter.py
index 16c6c7ab..fd54313f 100644
--- a/tests/python/test_type_converter.py
+++ b/tests/python/test_type_converter.py
@@ -84,6 +84,21 @@ def _make_str_enum_type() -> typing.Any:
A = TypeSchema.from_annotation
+def test_named_args_round_trip() -> None:
+ payload = TypeSchema("custom.Payload", fallback=TypeSchema("int"))
+ function = TypeSchema(
+ "Callable",
+ named_args={"return": (A(tuple[int, str]),), "params": (payload,)},
+ )
+ schema = TypeSchema("custom.Wrapper", named_args={"members": (function,),
"empty": ()})
+ assert TypeSchema.from_json_obj(schema.to_json()) == schema
+ assert function.input_repr() == "Callable[[int], tuple[int, str]]"
+ assert A(Callable[[], int]).to_json()["named_args"]["params"] == []
+ assert "params" not in A(Callable[..., int]).to_json()["named_args"]
+ with pytest.raises(TypeError):
+ TypeSchema.from_json_obj({"type": "int", "named_args": {"member":
{"type": "str"}}})
+
+
# ---------------------------------------------------------------------------
# Category 1: POD type exact match (check_value)
# ---------------------------------------------------------------------------
@@ -3488,9 +3503,10 @@ class TestSTLOriginParsing:
def test_std_function(self) -> None:
"""std::function maps to Callable."""
s = TypeSchema.from_json_str(
- '{"type":"std::function","args":[{"type":"int"},{"type":"str"}]}'
+ '{"type":"std::function","args":[{"type":"int"},[{"type":"str"}]]}'
)
assert s.origin == "Callable"
+ assert s.to_json()["named_args"]["params"] == [{"type": "str"}]
def test_object_rvalue_ref_origin(self) -> None:
"""ObjectRValueRef maps to Object."""
@@ -4213,7 +4229,9 @@ class TestFromAnnotationCallable:
def test_no_params(self) -> None:
"""Callable[[], int]."""
- assert A(Callable[[], int]) == S("Callable", S("int"))
+ assert A(Callable[[], int]) == TypeSchema(
+ "Callable", named_args={"return": (S("int"),), "params": ()}
+ )
class TestFromAnnotationList: