Nesting of Type Contexts, Type Hints, and Improved Array Typing
This MR introduces a few extensions to the typifier, allowing it to infer types for a wider range of more complex expressions. In particular, the typing of array literals and array declaration is improved significantly.
- Allow nested type contexts to be deferred by linking them to their parent contexts via inference hooks
- Introduce a system of type hints and their propagation through inference hooks in order to
- resolve type contexts from incomplete type information
- propagate fallback-to-default behaviour to nested contexts
- (possible future applications)
- Use the above to refactor and extend the type inference of array literals and subscripts:
- Array literals now fall back to the default type if no array type is known
- Inline array literals can now inherit their type from the enclosing expression's type context
- Apply the default numeric data type to arguments of
PsCast
and relationals if the argument type could not be inferred
Closes #99 (closed).
Merge request reports
Activity
changed milestone to %Release 2.0
added feature label
assigned to @da15siwa
added 1 commit
- f907b330 - fix minor type conflicts with predefined array types
requested review from @he66coqe
mentioned in task #99 (closed)
removed review request for @he66coqe
added 5 commits
-
f907b330...eb02fcab - 4 commits from branch
v2.0-dev
- 01d92cd1 - Merge branch 'v2.0-dev' into fhennig/nested-type-context
-
f907b330...eb02fcab - 4 commits from branch
requested review from @he66coqe
214 215 If the hint is not sufficient to resolve the context, a `TypificationError` is raised. 216 """ 217 assert self._target_type is None 218 219 # Type hints that can be resolved right there 220 match hint: 221 case ToDefault(default_dtype) if not self._inference_hooks: 222 self.apply_dtype(default_dtype) 223 case _: 224 for i, hook in enumerate(self._inference_hooks): 225 target_type = hook(hint) 226 if target_type is not None: 227 self._target_type = self._fix_constness(target_type) 228 # That hook was successful; remove it so it is not called a second time 229 del self._inference_hooks[i] Deleting elements from a list during iteration is unsafe. See e.g. https://stackoverflow.com/questions/1207406/how-to-remove-items-from-a-list-while-iterating for possible solutions.
212 def apply_hint(self, hint: TypeHint): 213 """Attempt to resolve this type context from the given type hint. 214 215 If the hint is not sufficient to resolve the context, a `TypificationError` is raised. 216 """ 217 assert self._target_type is None 218 219 # Type hints that can be resolved right there 220 match hint: 221 case ToDefault(default_dtype) if not self._inference_hooks: 222 self.apply_dtype(default_dtype) 223 case _: 224 for i, hook in enumerate(self._inference_hooks): 225 target_type = hook(hint) 226 if target_type is not None: 227 self._target_type = self._fix_constness(target_type) 729 inherit_arr_type = True 730 propagate_elem_type(tc.target_type.base_type, tc.target_type.length) 731 else: 732 inherit_arr_type = False 733 571 734 for item in items: 572 735 self.visit_expr(item, items_tc) 573 736 574 737 if items_tc.target_type is None: 575 if tc.target_type is None: 576 raise TypificationError(f"Unable to infer type of array {expr}") 577 elif not isinstance(tc.target_type, PsArrayType): 578 raise TypificationError( 579 f"Cannot apply type {tc.target_type} to an array initializer." 738 # Infer type of items from enclosing context 739 def hook(type_or_hint: PsType | TypeHint) -> PsType | None: Frankly, I find this code quite hard to reason about. This is mainly because
hook
gets called from withintc
, and then in turn calls intotc
viainfer_dtype
. Notably, callingapply_dtype
orapply_hint
presumably leads to infinite recursion. I suggest at least mentioning this fact in the documentation.But is it even necessary to have this "complicated" control flow? If the hook is called because the type of
tc
becomes known, then there is no need to calltc.infer_dtype
, right? If the hook receives a type hint instead, then it must return the type of the enclosing context (here:tc
) to the caller.tc
can then handle that information accordingly. Again, musthook
calltc.infer_dtype
?The call to
infer_dtype
merely instructstc
to apply its target type to the init-list expression. I agree that this is hard to read. Its especially weird since thetc
type context only ever covers a single AST node: the currentPsArrayInitList
.There's no infinite recursion here, since the hook only calls
apply_dtype
andapply_hint
on the nested contextitems_tc
, but I agree that the system has the potential to introduce infinite recursions in principle.I agree that this piece of control flow is terribly complicated, stemming from the fact that the type of the array init list can come from so many different sources, and that these can currently conflict in subtle ways. I've layed out a revision of array modelling in my notes (see also #102 (closed)), which may greatly simplify their typification. It might make sense to withhold this current set of changes, until the underlying IR modelling is improved...
311 decl = freeze(Assignment(arr, (5, 78, 1, TypedSymbol("x", Fp(16))))) 312 decl = typify(decl) 313 assert decl.rhs.dtype == constify(Arr(Fp(16, const=True))) 314 315 316 def test_inline_arrays_1d(): 317 ctx = KernelCreationContext(default_dtype=Fp(16)) 318 freeze = FreezeExpressions(ctx) 319 typify = Typifier(ctx) 320 321 x, y = sp.symbols("x, y") 322 idx = TypedSymbol("idx", Int(32)) 323 324 arr: PsArrayInitList = cast(PsArrayInitList, freeze(sp.Tuple(1, 2, 3, 4))) 325 decl = PsDeclaration(freeze(x), freeze(y) + PsSubscript(arr, freeze(idx))) 326 # The array elements should learn their type from the context, which gets it from `y`