Exemple #1
0
 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()
Exemple #2
0
 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()
Exemple #3
0
 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()
Exemple #4
0
 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
Exemple #5
0
 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
Exemple #6
0
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
Exemple #7
0
 def __init__(self, value: bool) -> None:
     super().__init__(T.Boolean(), value)
Exemple #8
0
 def boolean_type(self, items, meta):
     optional = False
     if items and items[0].value == "?":
         optional = True
     return T.Boolean(optional)
Exemple #9
0
 def _infer_type(self, type_env: Env.Types) -> T.Base:
     return T.Boolean()
Exemple #10
0
 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())
Exemple #11
0
    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()
Exemple #12
0
 def infer_type(self, expr: E.Apply) -> T.Base:
     if len(expr.arguments) != 1:
         raise Error.WrongArity(expr, 1)
     return T.Boolean()
Exemple #13
0
 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())
Exemple #14
0
    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),