Skip to content
Snippets Groups Projects

Nesting of Type Contexts, Type Hints, and Improved Array Typing

Closed Frederik Hennig requested to merge fhennig/nested-type-context into v2.0-dev
4 unresolved threads

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).

Edited by Frederik Hennig

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
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]
  • 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 within tc, and then in turn calls into tc via infer_dtype. Notably, calling apply_dtype or apply_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 call tc.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, must hook call tc.infer_dtype?

    • The call to infer_dtype merely instructs tc to apply its target type to the init-list expression. I agree that this is hard to read. Its especially weird since the tc type context only ever covers a single AST node: the current PsArrayInitList.

      There's no infinite recursion here, since the hook only calls apply_dtype and apply_hint on the nested context items_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...

    • Please register or sign in to reply
  • 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`
  • Please register or sign in to reply
    Loading