diff --git a/WDL/Tree.py b/WDL/Tree.py index a8362c11..5a14add7 100644 --- a/WDL/Tree.py +++ b/WDL/Tree.py @@ -59,16 +59,30 @@ class StructTypeDef(SourceNode): there. The referenced definition might itself be imported from yet another document. """ + parameter_meta: Dict[str, Any] + """:type: Dict[str,Any] + + ``parameter_meta{}`` section as a JSON-like dict""" + + meta: Dict[str, Any] + """:type: Dict[str,Any] + + ``meta{}`` section as a JSON-like dict""" + def __init__( self, pos: SourcePosition, name: str, members: Dict[str, Type.Base], + parameter_meta: Dict[str, Any], + meta: Dict[str, Any], imported: "Optional[Tuple[Document,StructTypeDef]]" = None, ) -> None: super().__init__(pos) self.name = name self.members = members + self.parameter_meta = parameter_meta + self.meta = meta self.imported = imported @property @@ -1963,7 +1977,9 @@ def _import_structs(doc: Document): except KeyError: pass if not existing: - st2 = StructTypeDef(imp.pos, name, st.members, imported=(imp.doc, st)) + st2 = StructTypeDef( + imp.pos, name, st.members, st.parameter_meta, st.meta, imported=(imp.doc, st) + ) doc.struct_typedefs = doc.struct_typedefs.bind(name, st2) diff --git a/WDL/_grammar.py b/WDL/_grammar.py index a5e3bdb5..0e2199e7 100644 --- a/WDL/_grammar.py +++ b/WDL/_grammar.py @@ -98,7 +98,7 @@ bound_decl: type CNAME "=" expr -> decl ?any_decl: unbound_decl | bound_decl -struct: "struct" CNAME "{" unbound_decl* "}" +struct: "struct" CNAME "{" (unbound_decl | meta_section)* "}" /////////////////////////////////////////////////////////////////////////////////////////////////// // type diff --git a/WDL/_parser.py b/WDL/_parser.py index 9ea6d462..d2e19a11 100644 --- a/WDL/_parser.py +++ b/WDL/_parser.py @@ -586,14 +586,32 @@ def struct(self, meta, items): name = items[0] self._check_keyword(self._sp(meta), name) members = {} - for d in items[1:]: - assert not d.expr - if d.name in members: - raise Error.MultipleDefinitions( - self._sp(meta), f"duplicate struct member '{d.name}'" - ) - members[d.name] = d.type - return Tree.StructTypeDef(self._sp(meta), name, members) + parameter_meta = None + meta_section = None + for item in items[1:]: + if isinstance(item, dict): + if "meta" in item: + if meta_section is not None: + raise Error.MultipleDefinitions( + self._sp(meta), "redundant struct meta sections" + ) + meta_section = item["meta"] + elif "parameter_meta" in item: + if parameter_meta is not None: + raise Error.MultipleDefinitions( + self._sp(meta), "redundant struct parameter_meta sections" + ) + parameter_meta = item["parameter_meta"] + else: + assert False + elif isinstance(item, Tree.Decl): + assert not item.expr + if item.name in members: + raise Error.MultipleDefinitions( + self._sp(meta), f"duplicate struct member '{item.name}'" + ) + members[item.name] = item.type + return Tree.StructTypeDef(self._sp(meta), name, members, parameter_meta, meta_section) def import_alias(self, meta, items): assert len(items) == 2 diff --git a/tests/spec_tests/config.yaml b/tests/spec_tests/config.yaml index 923345a9..70179d0e 100644 --- a/tests/spec_tests/config.yaml +++ b/tests/spec_tests/config.yaml @@ -38,11 +38,9 @@ wdl-1.2: - chunk_array.wdl - file_sizes_task.wdl - multi_nested_inputs.wdl - - import_structs.wdl - input_hint_task.wdl - join_paths_task.wdl - one_mount_point_task.wdl - - person_struct_task.wdl - string_to_file.wdl - test_allow_nested_inputs.wdl - test_contains.wdl @@ -58,6 +56,7 @@ wdl-1.2: - map_to_struct.wdl # issue #712 - multiline_string_placeholders.wdl # wrong namespace in expected output - nested_scatter.wdl # expected output is wrong + - person_struct_task.wdl # erroneous use of bash -gt to compare Float and Int - placeholders.wdl # wrong namespace in expected output - placeholder_none.wdl # questionable claim that interpolations should swallow errors - primitive_literals.wdl # expected output is wrong diff --git a/tests/test_1doc.py b/tests/test_1doc.py index 6fa95ce7..185d1f0a 100644 --- a/tests/test_1doc.py +++ b/tests/test_1doc.py @@ -1868,6 +1868,69 @@ def test_parser(self): with self.assertRaises(WDL.Error.MultipleDefinitions): doc = WDL.parse_document(doc) + def test_meta(self): + doc = r""" + version 1.2 + + struct Person { + meta { + description: "Something" + more_stuff: "Also here" + coolness: 7 + } + String name + parameter_meta { + name: "A name" + age: "An age" + } + Int age + } + """ + doc = WDL.parse_document(doc) + doc.typecheck() + struct = doc.struct_typedefs.resolve("Person") + self.assertEqual(struct.meta["description"], "Something") + self.assertEqual(struct.meta["more_stuff"], "Also here") + self.assertEqual(struct.meta["coolness"].value, 7) + self.assertEqual(struct.parameter_meta["name"], "A name") + self.assertEqual(struct.parameter_meta["age"], "An age") + + doc = r""" + version 1.2 + + struct Person { + meta { + description: "Something" + } + String name + meta { + description2: "Something else" + } + Int age + } + """ + with self.assertRaises(WDL.Error.MultipleDefinitions): + doc = WDL.parse_document(doc) + + doc = r""" + version 1.2 + + struct Person { + parameter_meta { + name: "A name" + age: "An age" + } + String name + Int age + parameter_meta { + name: "A name" + age: "An age" + } + } + """ + with self.assertRaises(WDL.Error.MultipleDefinitions): + doc = WDL.parse_document(doc) + def test_decl(self): doc = r""" version 1.0