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)
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)
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, )
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", {}), )
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])
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", {}), )
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
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
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
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)
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)
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)
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