def typecheck(self, expected: Optional[T.Base]) -> Base: "" if not self.items and isinstance(expected, T.Array): # the literal empty array satisfies any array type # (unless it has the nonempty quantifier) if expected.nonempty: raise Error.EmptyArray(self) return self return super().typecheck(expected) # pyre-ignore
def infer_type(self, expr: E.Apply) -> T.Base: if len(expr.arguments) != 2: raise Error.WrongArity(expr, 2) arg0ty: T.Base = expr.arguments[0].type if not isinstance(arg0ty, T.Array) or (expr._check_quant and arg0ty.optional): raise Error.StaticTypeMismatch(expr.arguments[0], T.Array(T.Any()), arg0ty) if isinstance(arg0ty.item_type, T.Any): # TODO: error for 'indeterminate type' raise Error.EmptyArray(expr.arguments[0]) arg1ty: T.Base = expr.arguments[1].type if not isinstance(arg1ty, T.Array) or (expr._check_quant and arg1ty.optional): raise Error.StaticTypeMismatch(expr.arguments[1], T.Array(T.Any()), arg1ty) if isinstance(arg1ty.item_type, T.Any): # TODO: error for 'indeterminate type' raise Error.EmptyArray(expr.arguments[1]) return T.Array( T.Pair(arg0ty.item_type, arg1ty.item_type), nonempty=(arg0ty.nonempty or arg1ty.nonempty), )
def infer_type(self, expr: E.Apply) -> T.Base: if len(expr.arguments) != 1: raise Error.WrongArity(expr, 1) if not isinstance(expr.arguments[0].type, T.Array): raise Error.StaticTypeMismatch(expr.arguments[0], T.Array(None), expr.arguments[0].type) if expr.arguments[0].type.item_type is None: # TODO: error for 'indeterminate type' raise Error.EmptyArray(expr.arguments[0]) ty = expr.arguments[0].type.item_type assert isinstance(ty, T.Base) return T.Array(ty.copy(optional=False))
def typecheck(self, type_env: Env.Types, check_quant: bool) -> None: # Infer the expression's type and ensure it checks against the declared # type. One time use! # # Subtlety: accept Array[T]+ = <expr> is accepted even if we can't # statically prove <expr> is nonempty. Its nonemptiness should be # checked at runtime. We do reject an empty array literal for <expr>. if self.expr: check_type = self.type if isinstance(check_type, T.Array): if check_type.nonempty and isinstance( self.expr, E.Array) and not self.expr.items: raise Err.EmptyArray(self.expr) check_type = check_type.copy(nonempty=False) self.expr.infer_type(type_env, check_quant).typecheck(check_type)
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