summaryrefslogtreecommitdiff
path: root/buildscripts/idl
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@mongodb.com>2020-11-10 12:43:17 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-11-11 01:31:47 +0000
commitc4f0c53602d42366c9c16b0f92482a35440c1828 (patch)
tree093888f11c067a309921290b6a318f2bc646bcae /buildscripts/idl
parent523247d096a796c15c911370e622a3614411a25b (diff)
downloadmongo-c4f0c53602d42366c9c16b0f92482a35440c1828.tar.gz
SERVER-51848 Specific generic args in IDL
Diffstat (limited to 'buildscripts/idl')
-rw-r--r--buildscripts/idl/idl/ast.py69
-rw-r--r--buildscripts/idl/idl/binder.py55
-rw-r--r--buildscripts/idl/idl/generator.py107
-rw-r--r--buildscripts/idl/idl/generic_field_list_types.py64
-rw-r--r--buildscripts/idl/idl/parser.py105
-rw-r--r--buildscripts/idl/idl/syntax.py51
-rw-r--r--buildscripts/idl/tests/test_parser.py124
7 files changed, 546 insertions, 29 deletions
diff --git a/buildscripts/idl/idl/ast.py b/buildscripts/idl/idl/ast.py
index 033b0df002c..e5d43e77067 100644
--- a/buildscripts/idl/idl/ast.py
+++ b/buildscripts/idl/idl/ast.py
@@ -33,11 +33,10 @@ Represents the derived IDL specification after type resolution in the binding pa
This is a lossy translation from the IDL Syntax tree as the IDL AST only contains information about
the enums and structs that need code generated for them, and just enough information to do that.
"""
+from abc import ABCMeta, abstractmethod
+from typing import List, Optional
-from typing import List, Union, Any, Optional, Tuple
-
-from . import common
-from . import errors
+from . import common, errors
class IDLBoundSpec(object):
@@ -55,6 +54,8 @@ class IDLBoundSpec(object):
class IDLAST(object):
"""The in-memory representation of an IDL file."""
+ # pylint: disable=too-many-instance-attributes
+
def __init__(self):
# type: () -> None
"""Construct an IDLAST."""
@@ -63,7 +64,8 @@ class IDLAST(object):
self.commands = [] # type: List[Command]
self.enums = [] # type: List[Enum]
self.structs = [] # type: List[Struct]
-
+ self.generic_argument_lists = [] # type: List[GenericArgumentList]
+ self.generic_reply_field_lists = [] # type: List[GenericReplyFieldList]
self.server_parameters = [] # type: List[ServerParameter]
self.configs = [] # type: List[ConfigOption]
@@ -217,10 +219,65 @@ class Command(Struct):
self.api_version = "" # type: str
self.is_deprecated = False # type: bool
self.unstable = False # type: bool
+ super(Command, self).__init__(file_name, line, column)
+
+
+class FieldListBase(common.SourceLocation, metaclass=ABCMeta):
+ """Base class for generic argument or reply field lists."""
+
+ def __init__(self, file_name, line, column):
+ # type: (str, int, int) -> None
+ """Construct a field list."""
+ self.name = None # type: str
+ self.cpp_name = None # type: str
+ self.description = None # type: str
+ self.fields = [] # type: List[FieldListEntry]
+ super(FieldListBase, self).__init__(file_name, line, column)
+
+ @abstractmethod
+ def get_should_forward_name(self):
+ # type: () -> str
+ """Get the name of the shard-forwarding rule for this generic argument or reply field."""
+ pass
+
+
+class GenericArgumentList(FieldListBase):
+ """IDL generic argument list."""
+
+ def get_should_forward_name(self):
+ # type: () -> str
+ """Get the name of the shard-forwarding rule for a generic argument."""
+ return "shouldForwardToShards"
+
+
+class GenericReplyFieldList(FieldListBase):
+ """IDL generic reply field list."""
+
+ def get_should_forward_name(self):
+ # type: () -> str
+ """Get the name of the shard-forwarding rule for a generic reply field."""
+ return "shouldForwardFromShards"
+
+
+class FieldListEntry(common.SourceLocation):
+ """Options for a field in a field list."""
+
+ def __init__(self, file_name, line, column):
+ # type: (str, int, int) -> None
+ """Construct a FieldListEntry."""
+ self.name = None # type: str
self.forward_to_shards = False # type: bool
self.forward_from_shards = False # type: bool
+
self.command_name = None # type: str
- super(Command, self).__init__(file_name, line, column)
+ super(FieldListEntry, self).__init__(file_name, line, column)
+
+ def get_should_forward(self):
+ """Get the shard-forwarding rule for a generic argument or reply field."""
+ assert not (self.forward_to_shards and self.forward_from_shards), \
+ "Only FieldListEntry.forward_to_shards or forward_from_shards should be set"
+
+ return self.forward_to_shards or self.forward_from_shards
class EnumValue(common.SourceLocation):
diff --git a/buildscripts/idl/idl/binder.py b/buildscripts/idl/idl/binder.py
index 397c39533cb..e65b7f64dfb 100644
--- a/buildscripts/idl/idl/binder.py
+++ b/buildscripts/idl/idl/binder.py
@@ -29,12 +29,11 @@
"""Transform idl.syntax trees from the parser into well-defined idl.ast trees."""
import re
-from typing import cast, List, Set, Union
+from typing import Type, TypeVar, cast, List, Set, Union
from . import ast
from . import bson
from . import common
-from . import cpp_types
from . import enum_types
from . import errors
from . import syntax
@@ -334,6 +333,52 @@ def _bind_struct(ctxt, parsed_spec, struct):
return ast_struct
+def _bind_field_list_entry(field_list_entry):
+ # type: (syntax.FieldListEntry) -> ast.FieldListEntry
+ """Bind a generic argument or reply field list entry."""
+ ast_entry = ast.FieldListEntry(field_list_entry.file_name, field_list_entry.line,
+ field_list_entry.column)
+ ast_entry.name = field_list_entry.name
+ ast_entry.forward_to_shards = field_list_entry.forward_to_shards
+ ast_entry.forward_from_shards = field_list_entry.forward_from_shards
+ return ast_entry
+
+
+ASTFieldListBaseClass = TypeVar("ASTFieldListBaseClass", bound=ast.FieldListBase, covariant=True)
+
+
+def _bind_field_list(field_list, ast_class):
+ # type: (syntax.FieldListBase, Type[ASTFieldListBaseClass]) -> ASTFieldListBaseClass
+ """Bind a generic argument or reply field list (helper method).
+
+ The ast_class param must be a subclass of ast.FieldListBase. The returned value is an
+ instance of ast_class.
+ """
+ ast_field_list = ast_class(field_list.file_name, field_list.line, field_list.column)
+ ast_field_list.description = field_list.description
+ ast_field_list.cpp_name = field_list.name
+ if field_list.cpp_name:
+ ast_field_list.cpp_name = field_list.cpp_name
+
+ return ast_field_list
+
+
+def _bind_generic_argument_list(field_list):
+ # type: (syntax.GenericArgumentList) -> ast.GenericArgumentList
+ """Bind a generic argument list."""
+ ast_field_list = _bind_field_list(field_list, ast.GenericArgumentList)
+ ast_field_list.fields = [_bind_field_list_entry(f) for f in field_list.fields]
+ return ast_field_list
+
+
+def _bind_generic_reply_field_list(field_list):
+ # type: (syntax.GenericReplyFieldList) -> ast.GenericReplyFieldList
+ """Bind a generic reply field list."""
+ ast_field_list = _bind_field_list(field_list, ast.GenericReplyFieldList)
+ ast_field_list.fields = [_bind_field_list_entry(f) for f in field_list.fields]
+ return ast_field_list
+
+
def _inject_hidden_command_fields(command):
# type: (syntax.Command) -> None
"""Inject hidden fields to aid deserialization/serialization for OpMsg parsing of commands."""
@@ -1223,6 +1268,12 @@ def bind(parsed_spec):
if not struct.imported:
bound_spec.structs.append(_bind_struct(ctxt, parsed_spec, struct))
+ for arg_list in parsed_spec.symbols.generic_argument_lists:
+ bound_spec.generic_argument_lists.append(_bind_generic_argument_list(arg_list))
+
+ for field_list in parsed_spec.symbols.generic_reply_field_lists:
+ bound_spec.generic_reply_field_lists.append(_bind_generic_reply_field_list(field_list))
+
for feature_flag in parsed_spec.feature_flags:
bound_spec.server_parameters.append(_bind_feature_flags(ctxt, feature_flag))
diff --git a/buildscripts/idl/idl/generator.py b/buildscripts/idl/idl/generator.py
index 70eb1e00499..295fabb2f44 100644
--- a/buildscripts/idl/idl/generator.py
+++ b/buildscripts/idl/idl/generator.py
@@ -28,24 +28,17 @@
# pylint: disable=too-many-lines
"""IDL C++ Code Generator."""
-from abc import ABCMeta, abstractmethod
-import copy
+import hashlib
import io
import os
import re
-import string
import sys
import textwrap
-import hashlib
-from typing import cast, Dict, List, Mapping, Tuple, Union
+from abc import ABCMeta, abstractmethod
+from typing import Dict, Iterable, List, Mapping, Tuple, Union
-from . import ast
-from . import bson
-from . import common
-from . import cpp_types
-from . import enum_types
-from . import struct_types
-from . import writer
+from . import (ast, bson, common, cpp_types, enum_types, generic_field_list_types, struct_types,
+ writer)
def _get_field_member_name(field):
@@ -539,6 +532,13 @@ class _CppHeaderFileWriter(_CppFileWriterBase):
if len(required_constructor.args) != len(constructor.args):
self._writer.write_line(required_constructor.get_declaration())
+ def gen_field_list_entry_lookup_methods(self, field_list):
+ # type: (ast.FieldListBase) -> None
+ """Generate the declarations for generic argument or reply field lookup methods."""
+ field_list_info = generic_field_list_types.get_field_list_info(field_list)
+ self._writer.write_line(field_list_info.get_has_field_method().get_declaration())
+ self._writer.write_line(field_list_info.get_should_forward_method().get_declaration())
+
def gen_serializer_methods(self, struct):
# type: (ast.Struct) -> None
"""Generate a serializer method declarations."""
@@ -761,6 +761,17 @@ class _CppHeaderFileWriter(_CppFileWriterBase):
self._writer.write_empty_line()
+ def gen_field_list_entries_declaration(self, field_list):
+ # type: (ast.FieldListBase) -> None
+ """Generate the field list entries map for a generic argument or reply field list."""
+ field_list_info = generic_field_list_types.get_field_list_info(field_list)
+ self._writer.write_line(
+ common.template_args('// Map: fieldName -> ${should_forward_name}',
+ should_forward_name=field_list_info.get_should_forward_name()))
+ self._writer.write_line(
+ "static const stdx::unordered_map<std::string, bool> _genericFields;")
+ self.write_empty_line()
+
def gen_known_fields_declaration(self):
# type: () -> None
"""Generate all the known fields vectors for a command."""
@@ -934,6 +945,7 @@ class _CppHeaderFileWriter(_CppFileWriterBase):
'mongo/bson/simple_bsonobj_comparator.h',
'mongo/idl/idl_parser.h',
'mongo/rpc/op_msg.h',
+ 'mongo/stdx/unordered_map.h',
] + spec.globals.cpp_includes
if spec.configs:
@@ -1036,6 +1048,24 @@ class _CppHeaderFileWriter(_CppFileWriterBase):
self.write_empty_line()
+ field_lists_list: Iterable[Iterable[ast.FieldListBase]]
+ field_lists_list = [spec.generic_argument_lists, spec.generic_reply_field_lists]
+ for field_lists in field_lists_list:
+ for field_list in field_lists:
+ self.gen_description_comment(field_list.description)
+ with self.gen_class_declaration_block(field_list.cpp_name):
+ self.write_unindented_line('public:')
+
+ # Field lookup methods
+ self.gen_field_list_entry_lookup_methods(field_list)
+ self.write_empty_line()
+
+ # Member variables
+ self.write_unindented_line('private:')
+ self.gen_field_list_entries_declaration(field_list)
+
+ self.write_empty_line()
+
for scp in spec.server_parameters:
if scp.cpp_class is None:
self._gen_exported_constexpr(scp.name, 'Default', scp.default, scp.condition)
@@ -1344,6 +1374,24 @@ class _CppSourceFileWriter(_CppFileWriterBase):
#print(struct.name + ": "+ str(required_constructor.args))
self._gen_constructor(struct, required_constructor, False)
+ def gen_field_list_entry_lookup_methods(self, field_list):
+ # type: (ast.FieldListBase) -> None
+ """Generate the definitions for generic argument or reply field lookup methods."""
+ field_list_info = generic_field_list_types.get_field_list_info(field_list)
+ defn = field_list_info.get_has_field_method().get_definition()
+ with self._block('%s {' % (defn, ), '}'):
+ self._writer.write_line(
+ 'return _genericFields.find(fieldName.toString()) != _genericFields.end();')
+
+ self._writer.write_empty_line()
+
+ defn = field_list_info.get_should_forward_method().get_definition()
+ with self._block('%s {' % (defn, ), '}'):
+ self._writer.write_line('auto it = _genericFields.find(fieldName.toString());')
+ self._writer.write_line('return (it == _genericFields.end() || it->second);')
+
+ self._writer.write_empty_line()
+
def _gen_command_deserializer(self, struct, bson_object):
# type: (ast.Struct, str) -> None
"""Generate the command field deserializer."""
@@ -1937,6 +1985,24 @@ class _CppSourceFileWriter(_CppFileWriterBase):
self._gen_known_fields_declaration(struct, "knownBSON", False)
self._gen_known_fields_declaration(struct, "knownOP_MSG", True)
+ def gen_field_list_entries_declaration(self, field_list):
+ # type: (ast.FieldListBase) -> None
+ """Generate the field list entries map for a generic argument or reply field list."""
+ klass = common.title_case(field_list.cpp_name)
+ field_list_info = generic_field_list_types.get_field_list_info(field_list)
+ self._writer.write_line(
+ common.template_args('// Map: fieldName -> ${should_forward_name}',
+ should_forward_name=field_list_info.get_should_forward_name()))
+ block_name = common.template_args(
+ 'const stdx::unordered_map<std::string, bool> ${klass}::_genericFields {', klass=klass)
+ with self._block(block_name, "};"):
+ sorted_entries = sorted(field_list.fields, key=lambda f: f.name)
+ for entry in sorted_entries:
+ self._writer.write_line(
+ common.template_args(
+ '{"${name}", ${should_forward}},', klass=klass, name=entry.name,
+ should_forward='true' if entry.get_should_forward() else 'false'))
+
def _gen_server_parameter_specialized(self, param):
# type: (ast.ServerParameter) -> None
"""Generate a specialized ServerParameter."""
@@ -2250,6 +2316,9 @@ class _CppSourceFileWriter(_CppFileWriterBase):
def generate(self, spec, header_file_name):
# type: (ast.IDLAST, str) -> None
"""Generate the C++ header to a stream."""
+
+ # pylint: disable=too-many-statements
+
self.gen_file_header()
# Include platform/basic.h
@@ -2274,8 +2343,8 @@ class _CppSourceFileWriter(_CppFileWriterBase):
# Generate mongo includes third
header_list = [
'mongo/bson/bsonobjbuilder.h',
- 'mongo/db/command_generic_argument.h',
'mongo/db/commands.h',
+ 'mongo/idl/command_generic_argument.h',
]
if spec.server_parameters:
@@ -2337,6 +2406,18 @@ class _CppSourceFileWriter(_CppFileWriterBase):
self.gen_to_bson_serializer_method(struct)
self.write_empty_line()
+ field_lists_list: Iterable[Iterable[ast.FieldListBase]]
+ field_lists_list = [spec.generic_argument_lists, spec.generic_reply_field_lists]
+ for field_lists in field_lists_list:
+ for field_list in field_lists:
+ # Member variables
+ self.gen_field_list_entries_declaration(field_list)
+ self.write_empty_line()
+
+ # Write field lookup methods
+ self.gen_field_list_entry_lookup_methods(field_list)
+ self.write_empty_line()
+
if spec.server_parameters:
self.gen_server_parameters(spec.server_parameters, header_file_name)
if spec.configs:
diff --git a/buildscripts/idl/idl/generic_field_list_types.py b/buildscripts/idl/idl/generic_field_list_types.py
new file mode 100644
index 00000000000..62cffc77a81
--- /dev/null
+++ b/buildscripts/idl/idl/generic_field_list_types.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2020-present MongoDB, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the Server Side Public License, version 1,
+# as published by MongoDB, Inc.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Server Side Public License for more details.
+#
+# You should have received a copy of the Server Side Public License
+# along with this program. If not, see
+# <http://www.mongodb.com/licensing/server-side-public-license>.
+#
+# As a special exception, the copyright holders give permission to link the
+# code of portions of this program with the OpenSSL library under certain
+# conditions as described in each individual source file and distribute
+# linked combinations including the program with the OpenSSL library. You
+# must comply with the Server Side Public License in all respects for
+# all of the code used other than as permitted herein. If you modify file(s)
+# with this exception, you may extend this exception to your version of the
+# file(s), but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version. If you delete this
+# exception statement from all source files in the program, then also delete
+# it in the license file.
+#
+"""Provide code generation information for generic arguments and reply fields."""
+
+from . import ast, common
+from .struct_types import MethodInfo
+
+
+class FieldListInfo:
+ """Class for generic argument and generic reply field list code generation."""
+
+ def __init__(self, field_list):
+ # type: (ast.FieldListBase) -> None
+ """Create a FieldListInfo instance."""
+ self._field_list = field_list
+
+ def get_has_field_method(self):
+ # type: () -> MethodInfo
+ """Get the hasField method for a generic argument or generic reply field list."""
+ class_name = common.title_case(self._field_list.cpp_name)
+ return MethodInfo(class_name, 'hasField', ['StringData fieldName'], 'bool', static=True)
+
+ def get_should_forward_name(self):
+ """Get the name of the shard-forwarding rule for a generic argument or reply field."""
+ return self._field_list.get_should_forward_name()
+
+ def get_should_forward_method(self):
+ # type: () -> MethodInfo
+ """Get the method for checking the shard-forwarding rule of an argument or reply field."""
+ class_name = common.title_case(self._field_list.cpp_name)
+ return MethodInfo(class_name, self.get_should_forward_name(), ['StringData fieldName'],
+ 'bool', static=True)
+
+
+def get_field_list_info(field_list):
+ # type: (ast.FieldListBase) -> FieldListInfo
+ """Get type information about the generic argument or reply field list to generate C++ code."""
+
+ return FieldListInfo(field_list)
diff --git a/buildscripts/idl/idl/parser.py b/buildscripts/idl/idl/parser.py
index 9378db49531..0ad4b7766df 100644
--- a/buildscripts/idl/idl/parser.py
+++ b/buildscripts/idl/idl/parser.py
@@ -461,6 +461,103 @@ def _parse_struct(ctxt, spec, name, node):
spec.symbols.add_struct(ctxt, struct)
+def _parse_generic_argument_list(ctxt, spec, name, node):
+ # type: (errors.ParserContext, syntax.IDLSpec, str, Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode]) -> None
+ """Parse a generic_argument_lists section in the IDL file."""
+ if not ctxt.is_mapping_node(node, "generic_argument_list"):
+ return
+
+ field_list = syntax.GenericArgumentList(ctxt.file_name, node.start_mark.line,
+ node.start_mark.column)
+ field_list.name = name
+
+ _generic_parser(
+ ctxt, node, "generic_argument_list", field_list, {
+ "description":
+ _RuleDesc('scalar', _RuleDesc.REQUIRED),
+ "cpp_name":
+ _RuleDesc('scalar'),
+ "fields":
+ _RuleDesc('mapping', mapping_parser_func=_parse_generic_argument_list_entries),
+ })
+
+ spec.symbols.add_generic_argument_list(ctxt, field_list)
+
+
+def _parse_generic_reply_field_list(ctxt, spec, name, node):
+ # type: (errors.ParserContext, syntax.IDLSpec, str, Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode]) -> None
+ """Parse a generic_reply_field_lists section in the IDL file."""
+ if not ctxt.is_mapping_node(node, "generic_reply_field_list"):
+ return
+
+ field_list = syntax.GenericReplyFieldList(ctxt.file_name, node.start_mark.line,
+ node.start_mark.column)
+ field_list.name = name
+
+ _generic_parser(
+ ctxt, node, "generic_reply_field_list", field_list, {
+ "description":
+ _RuleDesc('scalar', _RuleDesc.REQUIRED),
+ "cpp_name":
+ _RuleDesc('scalar'),
+ "fields":
+ _RuleDesc('mapping', mapping_parser_func=_parse_generic_reply_field_list_entries),
+ })
+
+ spec.symbols.add_generic_reply_field_list(ctxt, field_list)
+
+
+def _parse_field_list_entry(ctxt, name, node, is_generic_argument_field_list):
+ # type: (errors.ParserContext, str, Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode], bool) -> syntax.FieldListEntry
+ """Parse an entry in a generic argument or generic reply field list in the IDL file."""
+ entry = syntax.FieldListEntry(ctxt.file_name, node.start_mark.line, node.start_mark.column)
+ entry.name = name
+
+ if is_generic_argument_field_list:
+ mapping_rules = {"forward_to_shards": _RuleDesc("bool_scalar")}
+ else:
+ mapping_rules = {"forward_from_shards": _RuleDesc("bool_scalar")}
+
+ _generic_parser(ctxt, node, "field", entry, mapping_rules)
+ return entry
+
+
+def _parse_field_list_entries(ctxt, node, is_generic_argument_field_list):
+ # type: (errors.ParserContext, yaml.nodes.MappingNode, bool) -> List[syntax.FieldListEntry]
+ """Parse a fields section in a field list in the IDL file."""
+
+ entries = []
+
+ field_name_set = set() # type: Set[str]
+
+ for [first_node, second_node] in node.value:
+
+ first_name = first_node.value
+
+ if first_name in field_name_set:
+ ctxt.add_duplicate_error(first_node, first_name)
+ continue
+ entry = _parse_field_list_entry(ctxt, first_name, second_node,
+ is_generic_argument_field_list)
+ entries.append(entry)
+
+ field_name_set.add(first_name)
+
+ return entries
+
+
+def _parse_generic_argument_list_entries(ctxt, node):
+ # type: (errors.ParserContext, yaml.nodes.MappingNode) -> List[syntax.FieldListEntry]
+ """Parse a fields section in a generic argument list in the IDL file."""
+ return _parse_field_list_entries(ctxt, node, True)
+
+
+def _parse_generic_reply_field_list_entries(ctxt, node):
+ # type: (errors.ParserContext, yaml.nodes.MappingNode) -> List[syntax.FieldListEntry]
+ """Parse a fields section in a generic reply field list in the IDL file."""
+ return _parse_field_list_entries(ctxt, node, False)
+
+
def _parse_enum_values(ctxt, node):
# type: (errors.ParserContext, yaml.nodes.MappingNode) -> List[syntax.EnumValue]
"""Parse a values section in an enum in the IDL file."""
@@ -535,8 +632,6 @@ def _parse_command(ctxt, spec, name, node):
"api_version": _RuleDesc('scalar'),
"is_deprecated": _RuleDesc('bool_scalar'),
"unstable": _RuleDesc("bool_scalar"),
- "forward_to_shards": _RuleDesc("bool_scalar"),
- "forward_from_shards": _RuleDesc("bool_scalar"),
"strict": _RuleDesc("bool_scalar"),
"inline_chained_structs": _RuleDesc("bool_scalar"),
"immutable": _RuleDesc('bool_scalar'),
@@ -770,6 +865,12 @@ def _parse(stream, error_file_name):
_parse_mapping(ctxt, spec, second_node, 'structs', _parse_struct)
elif first_name == "commands":
_parse_mapping(ctxt, spec, second_node, 'commands', _parse_command)
+ elif first_name == "generic_argument_lists":
+ _parse_mapping(ctxt, spec, second_node, 'generic_argument_lists',
+ _parse_generic_argument_list)
+ elif first_name == "generic_reply_field_lists":
+ _parse_mapping(ctxt, spec, second_node, 'generic_reply_field_lists',
+ _parse_generic_reply_field_list)
elif first_name == "server_parameters":
_parse_mapping(ctxt, spec, second_node, "server_parameters", _parse_server_parameter)
elif first_name == "configs":
diff --git a/buildscripts/idl/idl/syntax.py b/buildscripts/idl/idl/syntax.py
index 2969a300791..159e0b31800 100644
--- a/buildscripts/idl/idl/syntax.py
+++ b/buildscripts/idl/idl/syntax.py
@@ -113,6 +113,8 @@ class SymbolTable(object):
self.enums = [] # type: List[Enum]
self.structs = [] # type: List[Struct]
self.types = [] # type: List[Type]
+ self.generic_argument_lists = [] # type: List[GenericArgumentList]
+ self.generic_reply_field_lists = [] # type: List[GenericReplyFieldList]
def _is_duplicate(self, ctxt, location, name, duplicate_class_name):
# type: (errors.ParserContext, common.SourceLocation, str, str) -> bool
@@ -122,6 +124,8 @@ class SymbolTable(object):
"enum": self.enums,
"struct": self.structs,
"type": self.types,
+ "generic_argument_list": self.generic_argument_lists,
+ "generic_reply_field_list": self.generic_reply_field_lists,
}):
if item.name == name:
ctxt.add_duplicate_symbol_error(location, name, duplicate_class_name, entity_type)
@@ -158,6 +162,18 @@ class SymbolTable(object):
if not self._is_duplicate(ctxt, command, command.name, "command"):
self.commands.append(command)
+ def add_generic_argument_list(self, ctxt, field_list):
+ # type: (errors.ParserContext, GenericArgumentList) -> None
+ """Add an IDL generic argument list to the symbol table and check for duplicates."""
+ if not self._is_duplicate(ctxt, field_list, field_list.name, "generic_argument_list"):
+ self.generic_argument_lists.append(field_list)
+
+ def add_generic_reply_field_list(self, ctxt, field_list):
+ # type: (errors.ParserContext, GenericReplyFieldList) -> None
+ """Add an IDL generic reply field list to the symbol table and check for duplicates."""
+ if not self._is_duplicate(ctxt, field_list, field_list.name, "generic_reply_field_list"):
+ self.generic_reply_field_lists.append(field_list)
+
def add_imported_symbol_table(self, ctxt, imported_symbols):
# type: (errors.ParserContext, SymbolTable) -> None
"""
@@ -420,16 +436,47 @@ class Command(Struct):
# type: (str, int, int) -> None
"""Construct a Command."""
self.namespace = None # type: str
+ self.command_name = None # type: str
self.type = None # type: str
self.reply_type = None # type: str
self.api_version = "" # type: str
self.is_deprecated = False # type: bool
self.unstable = False # type: bool
+ super(Command, self).__init__(file_name, line, column)
+
+
+class FieldListBase(common.SourceLocation):
+ """IDL field list information."""
+
+ def __init__(self, file_name, line, column):
+ # type: (str, int, int) -> None
+ """Construct a FieldList."""
+ self.name = None # type: str
+ self.cpp_name = None # type: str
+ self.description = None # type: str
+ self.fields = None # type: List[FieldListEntry]
+ super(FieldListBase, self).__init__(file_name, line, column)
+
+
+class GenericArgumentList(FieldListBase):
+ """IDL generic argument list."""
+
+
+class GenericReplyFieldList(FieldListBase):
+ """IDL generic reply field list."""
+
+
+class FieldListEntry(common.SourceLocation):
+ """Options for a field in a generic argument or generic reply field list."""
+
+ def __init__(self, file_name, line, column):
+ # type: (str, int, int) -> None
+ """Construct a FieldListEntry."""
+ self.name = None # type: str
self.forward_to_shards = False # type: bool
self.forward_from_shards = False # type: bool
self.command_name = None # type: str
-
- super(Command, self).__init__(file_name, line, column)
+ super(FieldListEntry, self).__init__(file_name, line, column)
class EnumValue(common.SourceLocation):
diff --git a/buildscripts/idl/tests/test_parser.py b/buildscripts/idl/tests/test_parser.py
index 17ec8f76d9f..fac46404bc7 100644
--- a/buildscripts/idl/tests/test_parser.py
+++ b/buildscripts/idl/tests/test_parser.py
@@ -842,8 +842,6 @@ class TestParser(testcase.IDLTestcase):
api_version: 1
is_deprecated: true
unstable: true
- forward_to_shards: true
- forward_from_shards: true
immutable: true
inline_chained_structs: true
generate_comparison_operators: true
@@ -865,8 +863,6 @@ class TestParser(testcase.IDLTestcase):
api_version: 1
is_deprecated: false
unstable: false
- forward_to_shards: false
- forward_from_shards: false
immutable: false
inline_chained_structs: false
generate_comparison_operators: false
@@ -1392,6 +1388,126 @@ class TestParser(testcase.IDLTestcase):
cpp_varname: gToaster
"""), idl.errors.ERROR_ID_MISSING_REQUIRED_FIELD)
+ def _test_field_list(self, field_list_name, should_forward_name):
+ # type: (str, str) -> None
+ """Positive field list test cases."""
+
+ # Generic field with no options
+ self.assert_parse(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo:
+ description: foo
+ fields:
+ foo: {{}}
+ """))
+
+ # All fields with true for bools
+ self.assert_parse(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo:
+ description: foo
+ fields:
+ foo:
+ {should_forward_name}: true
+ """))
+
+ # All fields with false for bools
+ self.assert_parse(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo:
+ description: foo
+ fields:
+ foo:
+ {should_forward_name}: false
+ """))
+
+ # cpp_name.
+ self.assert_parse(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo:
+ description: foo
+ cpp_name: foo
+ fields:
+ foo: {{}}
+ """))
+
+ def _test_field_list_negative(self, field_list_name, should_forward_name):
+ # type: (str, str) -> None
+ """Negative field list test cases."""
+
+ # Field list as a scalar
+ self.assert_parse_fail(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo: 1
+ """), idl.errors.ERROR_ID_IS_NODE_TYPE)
+
+ # No description
+ self.assert_parse_fail(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo:
+ fields:
+ foo: {{}}
+ """), idl.errors.ERROR_ID_MISSING_REQUIRED_FIELD)
+
+ # Unknown option
+ self.assert_parse_fail(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo:
+ description: foo
+ foo: bar
+ fields:
+ foo: {{}}
+ """), idl.errors.ERROR_ID_UNKNOWN_NODE)
+
+ # forward_to_shards is a bool.
+ self.assert_parse_fail(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo:
+ description: foo
+ fields:
+ foo:
+ {should_forward_name}: asdf
+ """), idl.errors.ERROR_ID_IS_NODE_VALID_BOOL)
+
+ # forward_from_shards is a bool
+ self.assert_parse_fail(
+ textwrap.dedent(f"""
+ {field_list_name}:
+ foo:
+ description: foo
+ fields:
+ foo:
+ {should_forward_name}: asdf
+ """), idl.errors.ERROR_ID_IS_NODE_VALID_BOOL)
+
+ def test_generic_arguments_list(self):
+ # type: () -> None
+ """Positive generic argument list test cases."""
+ self._test_field_list("generic_argument_lists", "forward_to_shards")
+
+ def test_generic_arguments_list_negative(self):
+ # type: () -> None
+ """Negative generic argument list test cases."""
+ self._test_field_list_negative("generic_argument_lists", "forward_to_shards")
+
+ def test_generic_reply_fields_list(self):
+ # type: () -> None
+ """Positive generic reply fields list test cases."""
+ self._test_field_list("generic_reply_field_lists", "forward_from_shards")
+
+ def test_generic_reply_fields_list_negative(self):
+ # type: () -> None
+ """Negative generic reply fields list test cases."""
+ self._test_field_list_negative("generic_reply_field_lists", "forward_from_shards")
+
if __name__ == '__main__':