def _infer_type(self, type_env: Env.Types) -> T.Base: self.expr.infer_type(type_env, self._check_quant) if isinstance(self.expr.type, T.Array): if "sep" not in self.options: raise Error.StaticTypeMismatch( self, T.Array(None), self.expr.type, "array command placeholder must have 'sep'") # if sum(1 for t in [T.Int, T.Float, T.Boolean, T.String, T.File] if isinstance(self.expr.type.item_type, t)) == 0: # raise Error.StaticTypeMismatch(self, T.Array(None), self.expr.type, "cannot use array of complex types for command placeholder") elif "sep" in self.options: raise Error.StaticTypeMismatch( self, T.Array(None), self.expr.type, "command placeholder has 'sep' option for non-Array expression", ) if "true" in self.options or "false" in self.options: if not isinstance(self.expr.type, T.Boolean): raise Error.StaticTypeMismatch( self, T.Boolean(), self.expr.type, "command placeholder 'true' and 'false' options used with non-Boolean expression", ) if not ("true" in self.options and "false" in self.options): raise Error.StaticTypeMismatch( self, T.Boolean(), self.expr.type, "command placeholder with only one of 'true' and 'false' options", ) return T.String()
def _infer_type(self, type_env: Env.Types) -> T.Base: if not self.items: return T.Array(None) for item in self.items: item.infer_type(type_env, self._check_quant) # Start by assuming the type of the first item is the item type item_type: T.Base = self.items[0].type # Allow a mixture of Int and Float to construct Array[Float] if isinstance(item_type, T.Int): for item in self.items: if isinstance(item.type, T.Float): item_type = T.Float() # If any item is String, assume item type is String # If any item has optional quantifier, assume item type is optional # If all items have nonempty quantifier, assume item type is nonempty all_nonempty = len(self.items) > 0 for item in self.items: if isinstance(item.type, T.String): item_type = T.String(optional=item_type.optional) if item.type.optional: item_type = item_type.copy(optional=True) if isinstance(item.type, T.Array) and not item.type.nonempty: all_nonempty = False if isinstance(item_type, T.Array): item_type = item_type.copy(nonempty=all_nonempty) # Check all items are coercible to item_type for item in self.items: try: item.typecheck(item_type) except Error.StaticTypeMismatch: self._type = T.Array(item_type, optional=False, nonempty=True) raise Error.StaticTypeMismatch( self, item_type, item.type, "(inconsistent types within array)") from None return T.Array(item_type, optional=False, nonempty=True)
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, T.Array(None), expr.arguments[0].type) return T.Int()
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 _infer_type(self, type_env: Env.Types) -> T.Base: # check for Boolean condition if self.condition.infer_type(type_env, self._check_quant).type != T.Boolean(): raise Error.StaticTypeMismatch(self, T.Boolean(), self.condition.type, "in if condition") # Unify consequent & alternative types. Subtleties: # 1. If either is optional, unify to optional # 2. If one is Int and the other is Float, unify to Float # 3. If one is a nonempty array and the other is a possibly empty # array, unify to possibly empty array self_type = self.consequent.infer_type(type_env, self._check_quant).type assert isinstance(self_type, T.Base) self.alternative.infer_type(type_env, self._check_quant) if isinstance(self_type, T.Int) and isinstance(self.alternative.type, T.Float): self_type = T.Float(optional=self_type.optional) if self.alternative.type.optional: self_type = self_type.copy(optional=True) if (isinstance(self_type, T.Array) and isinstance(self.consequent.type, T.Array) and isinstance(self.alternative.type, T.Array)): self_type = self_type.copy(nonempty=( # pyre-ignore self.consequent.type.nonempty and self.alternative.type.nonempty # pyre-ignore )) try: self.consequent.typecheck(self_type) self.alternative.typecheck(self_type) except Error.StaticTypeMismatch: raise Error.StaticTypeMismatch( self, self.consequent.type, # pyre-ignore self.alternative.type, " (if consequent & alternative must have the same type)", ) from None return self_type
def infer_type(self, expr: E.Apply) -> T.Base: if len(expr.arguments) != 1: raise Error.WrongArity(expr, 1) expr.arguments[0].typecheck(T.Array(T.Any())) # TODO: won't handle implicit coercion from T to Array[T] assert isinstance(expr.arguments[0].type, T.Array) if expr.arguments[0].type.item_type is None: return T.Array(T.Any()) if not isinstance(expr.arguments[0].type.item_type, T.Array): raise Error.StaticTypeMismatch( expr.arguments[0], T.Array(T.Array(T.Any())), expr.arguments[0].type ) return expr.arguments[0].type
def infer_type(self, expr: E.Apply) -> T.Base: if not expr.arguments: raise Error.WrongArity(expr, 1) if not expr.arguments[0].type.coerces(T.File(optional=True)): if isinstance(expr.arguments[0].type, T.Array): if expr.arguments[0].type.optional or not expr.arguments[0].type.item_type.coerces( T.File(optional=True) ): raise Error.StaticTypeMismatch( expr.arguments[0], T.Array(T.File(optional=True)), expr.arguments[0].type ) else: raise Error.StaticTypeMismatch( expr.arguments[0], T.File(optional=True), expr.arguments[0].type ) if len(expr.arguments) == 2: if expr.arguments[1].type != T.String(): raise Error.StaticTypeMismatch( expr.arguments[1], T.String(), expr.arguments[1].type ) elif len(expr.arguments) > 2: raise Error.WrongArity(expr, 2) return T.Float()
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 typecheck(self, expected: T.Base) -> TVBase: """typecheck(self, expected : T.Base) -> WDL.Expr.Base Check that this expression's type is, or can be coerced to, ``expected``. :raise WDL.Error.StaticTypeMismatch: :return: `self` """ expected2 = expected if not self._check_quant: if isinstance(expected, T.Array): expected2 = expected.copy(nonempty=False, optional=True) else: expected2 = expected.copy(optional=True) if not self.type.coerces(expected2): raise Error.StaticTypeMismatch(self, expected2, self.type) return self
def infer_type(self, expr: E.Apply) -> T.Base: min_args = len(self.argument_types) for ty in reversed(self.argument_types): if ty.optional: min_args = min_args - 1 else: break if len(expr.arguments) > len(self.argument_types) or len(expr.arguments) < min_args: raise Error.WrongArity(expr, len(self.argument_types)) for i in range(len(expr.arguments)): try: expr.arguments[i].typecheck(self.argument_types[i]) except Error.StaticTypeMismatch: raise Error.StaticTypeMismatch( expr.arguments[i], self.argument_types[i], expr.arguments[i].type, "for {} argument #{}".format(self.name, i + 1), ) from None return self.return_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