def infer_type(self, expr: E.Apply) -> T.Base: assert len(expr.arguments) == 2 lhs = expr.arguments[0] rhs = expr.arguments[1] if isinstance(lhs.type, T.Array): if isinstance(lhs, E.Array) and not lhs.items: # the user wrote: [][idx] raise Error.OutOfBounds(expr) try: rhs.typecheck(T.Int()) except Error.StaticTypeMismatch: raise Error.StaticTypeMismatch(rhs, T.Int(), rhs.type, "Array index") from None return lhs.type.item_type if isinstance(lhs.type, T.Map): if lhs.type.item_type is None: raise Error.OutOfBounds(expr) try: rhs.typecheck(lhs.type.item_type[0]) except Error.StaticTypeMismatch: raise Error.StaticTypeMismatch( rhs, lhs.type.item_type[0], rhs.type, "Map key" ) from None return lhs.type.item_type[1] raise Error.NotAnArray(lhs)
def _build_workflow_type_env( doc: TVDocument, check_quant: bool, self: Optional[Union[Workflow, Scatter, Conditional]] = None, outer_type_env: Env.Types = [], ) -> None: # Populate each Workflow, Scatter, and Conditional object with its # _type_env attribute containing the type environment available in the body # of the respective section. This is tricky because: # - forward-references to any declaration or call output in the workflow # are valid, except # - circular dependencies, direct or indirect # - (corollary) scatter and conditional expressions can't refer to # anything within the respective section # - a scatter variable is visible only inside the scatter # - declarations & call outputs of type T within a scatter have type # Array[T] outside of the scatter # - declarations & call outputs of type T within a conditional have type T? # outside of the conditional # # preconditions: # - _resolve_calls() # # postconditions: # - typechecks scatter and conditional expressions (recursively) # - sets _type_env attributes on each Workflow/Scatter/Conditional self = self or doc.workflow if not self: return assert isinstance(self, (Scatter, Conditional)) or self is doc.workflow assert self._type_env is None # When we've been called recursively on a scatter or conditional section, # the 'outer' type environment has everything available in the workflow # -except- the body of self. type_env = outer_type_env if isinstance(self, Scatter): # typecheck scatter array self.expr.infer_type(type_env, check_quant) if not isinstance(self.expr.type, T.Array): raise Err.NotAnArray(self.expr) if self.expr.type.item_type is None: raise Err.EmptyArray(self.expr) # bind the scatter variable to the array item type within the body try: Env.resolve(type_env, [], self.variable) raise Err.MultipleDefinitions( self, "Name collision for scatter variable " + self.variable) except KeyError: pass type_env = Env.bind(self.variable, self.expr.type.item_type, type_env, ctx=self) elif isinstance(self, Conditional): # typecheck the condition self.expr.infer_type(type_env, check_quant) if not self.expr.type.coerces(T.Boolean()): raise Err.StaticTypeMismatch(self.expr, T.Boolean(), self.expr.type) # descend into child scatter & conditional elements, if any. for child in self.elements: if isinstance(child, (Scatter, Conditional)): # prepare the 'outer' type environment for the child element, by # adding all its sibling declarations and call outputs child_outer_type_env = type_env for sibling in self.elements: if sibling is not child: child_outer_type_env = sibling.add_to_type_env( child_outer_type_env) _build_workflow_type_env(doc, check_quant, child, child_outer_type_env) # finally, populate self._type_env with all our children for child in self.elements: type_env = child.add_to_type_env(type_env) self._type_env = type_env