Skip to content

feat[next]: dtype-generic type system foundations#2660

Open
havogt wants to merge 11 commits into
GridTools:mainfrom
havogt:dtype-generics-2-type-system
Open

feat[next]: dtype-generic type system foundations#2660
havogt wants to merge 11 commits into
GridTools:mainfrom
havogt:dtype-generics-2-type-system

Conversation

@havogt

@havogt havogt commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Preparation for dtype-generic field operators in gt4py.next (first step of the series): writing a field operator once and instantiating it at several scalar dtypes, spelled with a value-constrained typing.TypeVar such as TypeVar("T", float32, float64). The architecture is monomorphization at call time — the operator is type-checked once in terms of its type variables and then specialized to a concrete dtype for each call.

This PR adds only the (inert) type-system foundation; nothing produces a type variable yet, so there is no user-visible behavior change.

Newly introduced type:

  • ts.TypeVarType — a DataType (TypeSpec) carrying a name and a non-empty tuple of scalar constraints. Two occurrences of the same name within one signature denote the same type; it coexists with DeferredType rather than replacing it.
  • FieldType.dtype is widened to also allow a TypeVarType.

Supporting (dtype-scoped) utilities the later steps build on:

  • type_info.is_generic — recursive over composite types.
  • bind_type_vars / substitute_type_vars.
  • TypeVar-aware variants of the dtype predicates, promote, and extract_dtype / extract_dims.

Design and decisions are recorded in ADR 0023.

Add the type-system machinery for dtype-generic operators, inert until the
frontend produces it:

- ts.TypeVarType (value-constrained scalar type variable); widen FieldType.dtype
  to ScalarType | ListType | TypeVarType.
- type_info.is_generic (recursive), bind_type_vars / substitute_type_vars
  (dtype-scoped: name -> ScalarType), and TypeVar-aware dtype predicates / promote
  / extract_dtype / extract_dims.
- Route CompiledProgramsPool._is_generic through type_info.is_generic, resolving
  its TODO (behavior-preserving for scan operators).

Widening FieldType.dtype requires narrowing at the concrete-IR lowering sites
(dace, global_tmps), where the D4 invariant guarantees no TypeVarType, plus a
return-type annotation in FOAST type deduction.

Records the decision in ADR 0023. See the implementation roadmap for the PR
series.
@havogt havogt changed the title feat[next]: dtype-generic type system foundations (Stage 0) feat[next]: dtype-generic type system foundations Jun 16, 2026
Comment thread docs/development/ADRs/next/0023-Dtype-Generic-Operators.md Outdated
Comment thread docs/development/ADRs/next/0023-Dtype-Generic-Operators.md Outdated
Comment thread docs/development/ADRs/next/0024-Dtype-Generic-Operators.md
type_info.extract_dtype,
result_collection_constructor=as_tuple,
)(tmp_expr.type)
) = cast(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check if we can express this without a cast

self.dc_node, self.gt_type.dtype, field_origin, it_indices
)
dtype = self.gt_type.dtype
assert isinstance(dtype, (ts.ScalarType, ts.ListType))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can all these assert be avoided by better concept definition?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with widening the type is that now everything needs to either support the generic case or assert like here, even though after specialization none of these cases can occur. A general generic type would solve this automatically, before specialization things need to support the generic, e.g. GenericType[ScalarType], afterwards things can stay they way they are.

Comment thread src/gt4py/next/type_system/type_info.py Outdated
Comment thread src/gt4py/next/type_system/type_info.py Outdated
Comment thread src/gt4py/next/type_system/type_info.py Outdated
Comment thread src/gt4py/next/type_system/type_info.py Outdated
Comment thread tests/next_tests/unit_tests/type_system_tests/test_type_info.py Outdated
havogt added 4 commits June 17, 2026 11:42
- canonicalize TypeVarType.constraints (order carries no meaning for a
  value-constrained TypeVar) so the type's identity is order-insensitive
- unify the type-variable traversals: add _type_params / tree_map_type_params
  and reexpress is_generic and substitute_type_vars on them; reword the
  is_generic docstring (deep check, not the negation of is_concrete)
- document the non-scalar case in bind_var and the dtype-union narrowing
  assert in the dace lowering
- ADR 0023: tighten, rename the example, drop the external design-investigation
  reference, and add it to the ADR index under a new Type System section
Put the type-variable utility doctests under an 'Examples:' section (matching the
rest of type_info.py), make the _type_params summary a sentence, and drop docstring
and comment text describing rationale, callers, or future plans rather than behavior.
Replace the mutating closure-based walk with pure module-level helpers
(_bind_var / _merge_bindings / _bind) that return and merge per-leaf bindings;
keeps the tolerant structural recursion (mismatches deferred to the signature checks).
return tuple(sorted(constraints, key=lambda c: c.kind))


class TypeVarType(DataType):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class TypeVarType(DataType):
class DTypeVarType(DataType):

for now?

havogt added 2 commits June 19, 2026 11:51
Upstream main now occupies the 0023 slot with the Fingerprinting ADR, so
move this ADR to the next free number and update the index accordingly.
@havogt havogt requested a review from tehrengruber June 19, 2026 10:00
self.dc_node, self.gt_type.dtype, field_origin, it_indices
)
dtype = self.gt_type.dtype
assert isinstance(dtype, (ts.ScalarType, ts.ListType))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with widening the type is that now everything needs to either support the generic case or assert like here, even though after specialization none of these cases can occur. A general generic type would solve this automatically, before specialization things need to support the generic, e.g. GenericType[ScalarType], afterwards things can stay they way they are.

Comment thread src/gt4py/next/type_system/type_info.py Outdated
Comment thread src/gt4py/next/type_system/type_info.py Outdated
Comment thread src/gt4py/next/type_system/type_specifications.py
Comment thread src/gt4py/next/type_system/type_specifications.py
havogt added 4 commits June 23, 2026 20:23
Route substitution through the existing `tree_map_type` rather than a bespoke type-parameter map, handling the dtype / element-type / signature rewrite in the leaf function. Drop the upfront `is_generic` check: the traversal runs unconditionally and reconstructs the tree.

Addresses the review comment on the `tree_map_type_params` helper.
is_arithmetic hand-rolled its own TypeVarType handling under a comment
claiming it could not reuse _is_field_or_scalar_of_kind. That helper takes
a kind *set*, so arithmetic is just membership in the union of the floating
point and integral kinds; all(constraint in union) is correct even for a
type variable mixing float and integral constraints. Route it through the
shared helper via _ARITHMETIC_KINDS, removing the duplicated constraint fold.
The positional/keyword/return sub-type layout of FunctionType was spelled out
independently in _type_params, substitute_type_vars and is_compatible_type.
Extract it into _function_type_arg_groups (the grouped layout, kept grouped so
is_compatible_type still enforces per-group arity), _function_type_children
(the flat enumeration) and _map_function_type (structure-preserving rebuild),
and route the three call sites through them.
The dace lowering narrows FieldType.dtype past the widened TypeVarType with
plain 'assert isinstance(dtype, (ts.ScalarType, ts.ListType))'. Introduce a
type_info.is_concrete_dtype TypeIs guard and route those asserts through it,
so the narrowing is self-documenting (the dtype is concrete, i.e. not a type
variable) and the intent is stated once.
)


def _type_params(symbol_type: ts.TypeSpec) -> tuple[ts.TypeSpec, ...]:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be used as a generic type_tree_map?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants