Exemple #1
0
    def typecheck(self, check_quant: bool = True) -> None:
        """Typecheck each task in the document, then the workflow, if any.

        Documents returned by :func:`~WDL.load` have already been typechecked."""
        names = set()
        for _, namespace, _ in self.imports:
            if namespace in names:
                raise Err.MultipleDefinitions(
                    self, "Multiple imports with namespace " + namespace)
            names.add(namespace)
        names = set()
        # typecheck each task
        for task in self.tasks:
            if task.name in names:
                raise Err.MultipleDefinitions(
                    task, "Multiple tasks named " + task.name)
            names.add(task.name)
            task.typecheck(check_quant=check_quant)
        # typecheck the workflow
        if self.workflow:
            if self.workflow.name in names:
                raise Err.MultipleDefinitions(
                    self.workflow,
                    "Workflow name collides with a task also named " +
                    self.workflow.name,
                )
            self.workflow.typecheck(self, check_quant=check_quant)
Exemple #2
0
 def document(self, items, meta):
     imports = []
     structs = {}
     tasks = []
     workflow = None
     for item in items:
         if isinstance(item, D.Task):
             tasks.append(item)
         elif isinstance(item, D.Workflow):
             if workflow is not None:
                 raise Err.MultipleDefinitions(
                     sp(self.filename, meta),
                     "Document has multiple workflows")
             workflow = item
         elif isinstance(item, D.StructTypeDef):
             if item.name in structs:
                 raise Err.MultipleDefinitions(
                     sp(self.filename, meta),
                     "multiple structs named " + item.name)
             structs[item.name] = item
         elif isinstance(item, lark.Tree) and item.data == "version":
             pass
         elif isinstance(item, D.DocImport):
             imports.append(item)
         else:
             assert False
     return D.Document(sp(self.filename, meta), imports, structs, tasks,
                       workflow)
Exemple #3
0
 def workflow(self, items, meta):
     elements = []
     inputs = None
     outputs = None
     output_idents = None
     output_idents_pos = None
     parameter_meta = None
     meta_section = None
     for item in items[1:]:
         if isinstance(item, dict):
             if "inputs" in item:
                 assert inputs is None
                 inputs = item["inputs"]
             elif "outputs" in item:
                 if outputs is not None:
                     raise Err.MultipleDefinitions(
                         sp(self.filename, meta),
                         "redundant sections in workflow")
                 outputs = item["outputs"]
                 if "output_idents" in item:
                     assert output_idents is None
                     output_idents = item["output_idents"]
                     output_idents_pos = item["pos"]
             elif "meta" in item:
                 if meta_section is not None:
                     raise Err.MultipleDefinitions(
                         sp(self.filename, meta),
                         "redundant sections in workflow")
                 meta_section = item["meta"]
             elif "parameter_meta" in item:
                 if parameter_meta is not None:
                     raise Err.MultipleDefinitions(
                         sp(self.filename, meta),
                         "redundant sections in workflow")
                 parameter_meta = item["parameter_meta"]
             else:
                 assert False
         elif isinstance(item, (D.Call, D.Conditional, D.Decl, D.Scatter)):
             elements.append(item)
         else:
             assert False
     _check_keyword(sp(self.filename, meta), items[0].value)
     return D.Workflow(
         sp(self.filename, meta),
         items[0].value,
         inputs,
         elements,
         outputs,
         parameter_meta or dict(),
         meta_section or dict(),
         output_idents,
         output_idents_pos,
     )
Exemple #4
0
 def task(self, items, meta):
     d = {"noninput_decls": []}
     for item in items:
         if isinstance(item, dict):
             for k, v in item.items():
                 if k == "noninput_decl":
                     d["noninput_decls"].append(v)
                 elif k in d:
                     raise Err.MultipleDefinitions(
                         sp(self.filename, meta),
                         "redundant sections in task")
                 else:
                     d[k] = v
         else:
             assert isinstance(item, str)
             assert "name" not in d
             d["name"] = item.value
     _check_keyword(sp(self.filename, meta), d["name"])
     return D.Task(
         sp(self.filename, meta),
         d["name"],
         d.get("inputs", None),
         d["noninput_decls"],
         d["command"],
         d.get("outputs", []),
         d.get("parameter_meta", {}),
         d.get("runtime", {}),
         d.get("meta", {}),
     )
Exemple #5
0
 def placeholder(self, items, meta):
     options = dict(items[:-1])
     if len(options.items()) < len(items) - 1:
         raise Err.MultipleDefinitions(
             sp(self.filename, meta),
             "duplicate options in expression placeholder")
     return E.Placeholder(sp(self.filename, meta), options, items[-1])
Exemple #6
0
 def task(self, items, meta):
     d = {}
     for item in items:
         if isinstance(item, dict):
             for k, v in item.items():
                 if k in d:
                     raise Err.MultipleDefinitions(
                         sp(self.filename, meta), "redundant sections in task"
                     )
                 d[k] = v
         else:
             assert isinstance(item, str)
             assert "name" not in d
             d["name"] = item.value
     return D.Task(
         sp(self.filename, meta),
         d["name"],
         d.get("inputs", []),
         d.get("decls", []),
         d["command"],
         d.get("outputs", []),
         d.get("parameter_meta", {}),
         d.get("runtime", {}),
         d.get("meta", {}),
     )
Exemple #7
0
 def call_inputs(self, items, meta):
     d = dict()
     for k, v in items:
         if k in d:
             raise Err.MultipleDefinitions(sp(self.filename, meta),
                                           "duplicate keys in call inputs")
         d[k] = v
     return d
Exemple #8
0
 def meta_object(self, items, meta):
     d = dict()
     for k, v in items:
         if k in d:
             raise Err.MultipleDefinitions(sp(self.filename, meta),
                                           "duplicate keys in meta object")
         d[k] = v
     return d
Exemple #9
0
 def add_to_type_env(self, type_env: Env.Types) -> Env.Types:
     # Add an appropriate binding in the type env, after checking for name
     # collision.
     try:
         Env.resolve(type_env, [], self.name)
         raise Err.MultipleDefinitions(
             self, "Multiple declarations of " + self.name)
     except KeyError:
         pass
     ans: Env.Types = Env.bind(self.name, self.type, type_env, ctx=self)
     return ans
Exemple #10
0
 def struct(self, items, meta):
     assert len(items) >= 1
     name = items[0]
     _check_keyword(sp(self.filename, meta), name)
     members = {}
     for d in items[1:]:
         assert not d.expr
         if d.name in members:
             raise Err.MultipleDefinitions(sp(self.filename, meta),
                                           "duplicate members in struct")
         members[d.name] = d.type
     return D.StructTypeDef(sp(self.filename, meta), name, members)
Exemple #11
0
 def add_to_type_env(self, type_env: Env.Types) -> Env.Types:
     # Add the call's outputs to the type environment under the appropriate
     # namespace, after checking for namespace collisions.
     assert self.callee
     try:
         Env.resolve_namespace(type_env, [self.name])
         raise Err.MultipleDefinitions(
             self,
             "Workflow has multiple calls named {}; give calls distinct names using `call {} as NAME ...`"
             .format(self.name, self.callee.name),
         )
     except KeyError:
         pass
     outputs_env = []
     for outp in self.callee.outputs:
         outputs_env = Env.bind(outp.name, outp.type, outputs_env, ctx=self)
     return Env.namespace(self.name, outputs_env, type_env)
Exemple #12
0
 def document(self, items, meta):
     imports = []
     tasks = []
     workflow = None
     for item in items:
         if isinstance(item, D.Task):
             tasks.append(item)
         elif isinstance(item, D.Workflow):
             if workflow is not None:
                 raise Err.MultipleDefinitions(
                     sp(self.filename, meta), "Document has multiple workflows"
                 )
             workflow = item
         elif isinstance(item, lark.Tree) and item.data == "version":
             pass
         elif isinstance(item, dict) and "import" in item:
             imports.append(item["import"])
         else:
             assert False
     return D.Document(sp(self.filename, meta), imports, tasks, workflow, self.imported)
Exemple #13
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