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, expr: E.Apply) -> T.Base: assert len(expr.arguments) == 2 if ( ( expr._check_quant and expr.arguments[0].type.optional != expr.arguments[1].type.optional ) or ( self.name not in ["==", "!="] and (expr.arguments[0].type.optional or expr.arguments[1].type.optional) ) or ( not ( expr.arguments[0].type.copy(optional=False) == expr.arguments[1].type.copy(optional=False) or ( isinstance(expr.arguments[0].type, T.Int) and isinstance(expr.arguments[1].type, T.Float) ) or ( isinstance(expr.arguments[0].type, T.Float) and isinstance(expr.arguments[1].type, T.Int) ) ) ) ): raise Error.IncompatibleOperand( expr, "Cannot compare {} and {}".format( str(expr.arguments[0].type), str(expr.arguments[1].type) ), ) return T.Boolean()
def infer_type(self, expr: E.Apply) -> T.Base: assert len(expr.arguments) == 2 for arg in expr.arguments: if not isinstance(arg.type, T.Boolean): raise Error.IncompatibleOperand(arg, "non-Boolean operand to ||") if expr._check_quant and arg.type.optional: raise Error.IncompatibleOperand(arg, "optional Boolean? operand to ||") return T.Boolean()
def eval(self, env: Env.Values) -> V.Base: "" try: if self.condition.eval(env).expect(T.Boolean()).value: ans = self.consequent.eval(env) else: ans = self.alternative.eval(env) return ans except ReferenceError: raise Error.NullValue(self) from None
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 _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
def __init__(self, value: bool) -> None: super().__init__(T.Boolean(), value)
def boolean_type(self, items, meta): optional = False if items and items[0].value == "?": optional = True return T.Boolean(optional)
def _infer_type(self, type_env: Env.Types) -> T.Base: return T.Boolean()
def __call__(self, expr: E.Apply, env: Env.Values, stdlib: Base) -> V.Base: lhs = expr.arguments[0].eval(env, stdlib=stdlib).expect(T.Boolean()).value if lhs: return V.Boolean(True) return expr.arguments[1].eval(env, stdlib=stdlib).expect(T.Boolean())
def __init__(self): # language built-ins self._at = _At() self._land = _And() self._lor = _Or() self._negate = StaticFunction( "_negate", [T.Boolean()], T.Boolean(), lambda x: V.Boolean(not x.value) ) self._add = _AddOperator() self._sub = _ArithmeticOperator("-", lambda l, r: l - r) self._mul = _ArithmeticOperator("*", lambda l, r: l * r) self._div = _ArithmeticOperator("/", lambda l, r: l // r) self._rem = StaticFunction( "_rem", [T.Int(), T.Int()], T.Int(), lambda l, r: V.Int(l.value % r.value) ) self._eqeq = _ComparisonOperator("==", lambda l, r: l == r) self._neq = _ComparisonOperator("!=", lambda l, r: l != r) self._lt = _ComparisonOperator("<", lambda l, r: l < r) self._lte = _ComparisonOperator("<=", lambda l, r: l <= r) self._gt = _ComparisonOperator(">", lambda l, r: l > r) self._gte = _ComparisonOperator(">=", lambda l, r: l >= r) # static stdlib functions for (name, argument_types, return_type, F) in [ ("floor", [T.Float()], T.Int(), lambda v: V.Int(math.floor(v.value))), ("ceil", [T.Float()], T.Int(), lambda v: V.Int(math.ceil(v.value))), ("round", [T.Float()], T.Int(), lambda v: V.Int(round(v.value))), ("length", [T.Array(T.Any())], T.Int(), lambda v: V.Int(len(v.value))), ("sub", [T.String(), T.String(), T.String()], T.String(), _sub), ("basename", [T.String(), T.String(optional=True)], T.String(), _basename), ( "defined", [T.Any(optional=True)], T.Boolean(), lambda v: V.Boolean(not isinstance(v, V.Null)), ), # context-dependent: ("write_lines", [T.Array(T.String())], T.File(), _notimpl), ("write_tsv", [T.Array(T.Array(T.String()))], T.File(), _notimpl), ("write_map", [T.Map((T.Any(), T.Any()))], T.File(), _notimpl), ("write_json", [T.Any()], T.File(), _notimpl), ("stdout", [], T.File(), _notimpl), ("stderr", [], T.File(), _notimpl), ("glob", [T.String()], T.Array(T.File()), _notimpl), ("read_int", [T.File()], T.Int(), _notimpl), ("read_boolean", [T.File()], T.Boolean(), _notimpl), ("read_string", [T.File()], T.String(), _notimpl), ("read_float", [T.File()], T.Float(), _notimpl), ("read_array", [T.File()], T.Array(T.Any()), _notimpl), ("read_map", [T.File()], T.Map((T.Any(), T.Any())), _notimpl), ("read_lines", [T.File()], T.Array(T.Any()), _notimpl), ("read_tsv", [T.File()], T.Array(T.Array(T.String())), _notimpl), ("read_json", [T.File()], T.Any(), _notimpl), ]: setattr(self, name, StaticFunction(name, argument_types, return_type, F)) # polymorphically typed stdlib functions which require specialized # infer_type logic self.range = _Range() self.prefix = _Prefix() self.size = _Size() self.select_first = _SelectFirst() self.select_all = _SelectAll() self.zip = _Zip() self.cross = _Zip() # FIXME self.flatten = _Flatten() self.transpose = _Transpose()
def infer_type(self, expr: E.Apply) -> T.Base: if len(expr.arguments) != 1: raise Error.WrongArity(expr, 1) return T.Boolean()
def __call__(self, expr: E.Apply, env: E.Env) -> V.Base: lhs = expr.arguments[0].eval(env).expect(T.Boolean()).value if lhs: return V.Boolean(True) return expr.arguments[1].eval(env).expect(T.Boolean())
def __call__(self, expr: E.Apply, env: E.Env) -> V.Base: assert len(expr.arguments) == len(self.argument_types) argument_values = [ arg.eval(env).coerce(ty) for arg, ty in zip(expr.arguments, self.argument_types) ] ans: V.Base = self.F(*argument_values) return ans.coerce(self.return_type) def _notimpl(one: Any = None, two: Any = None) -> None: exec("raise NotImplementedError()") _static_functions: List[Tuple[str, List[T.Base], T.Base, Any]] = [ ("_negate", [T.Boolean()], T.Boolean(), lambda x: V.Boolean(not x.value)), # pyre-fixme ("_rem", [T.Int(), T.Int()], T.Int(), lambda l, r: V.Int(l.value % r.value)), # pyre-fixme ("stdout", [], T.File(), _notimpl), ("basename", [T.String(), T.String(optional=True)], T.String(), _notimpl), # note: size() can take an empty value and probably returns 0 in that case. # e.g. https://github.com/DataBiosphere/topmed-workflows/blob/31ba8a714b36ada929044f2ba3d130936e6c740e/CRAM-no-header-md5sum/md5sum/CRAM_md5sum.wdl#L39 ("size", [T.File(optional=True), T.String(optional=True)], T.Float(), _notimpl), ("ceil", [T.Float()], T.Int(), _notimpl), ("round", [T.Float()], T.Int(), _notimpl), ("glob", [T.String()], T.Array(T.File()), _notimpl), ("read_int", [T.String()], T.Int(), _notimpl), ("read_boolean", [T.String()], T.Boolean(), _notimpl), ("read_string", [T.String()], T.String(), _notimpl),