feat[next]: dtype-generic type system foundations#2660
Conversation
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.
| type_info.extract_dtype, | ||
| result_collection_constructor=as_tuple, | ||
| )(tmp_expr.type) | ||
| ) = cast( |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
Can all these assert be avoided by better concept definition?
There was a problem hiding this comment.
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.
- 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): |
There was a problem hiding this comment.
| class TypeVarType(DataType): | |
| class DTypeVarType(DataType): |
for now?
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.
| self.dc_node, self.gt_type.dtype, field_origin, it_indices | ||
| ) | ||
| dtype = self.gt_type.dtype | ||
| assert isinstance(dtype, (ts.ScalarType, ts.ListType)) |
There was a problem hiding this comment.
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.
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, ...]: |
There was a problem hiding this comment.
Can this be used as a generic type_tree_map?
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-constrainedtyping.TypeVarsuch asTypeVar("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— aDataType(TypeSpec) carrying anameand a non-empty tuple of scalarconstraints. Two occurrences of the samenamewithin one signature denote the same type; it coexists withDeferredTyperather than replacing it.FieldType.dtypeis widened to also allow aTypeVarType.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, andextract_dtype/extract_dims.Design and decisions are recorded in ADR 0023.