This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new fe19ac3ce feat(compiler): add list keyword for list fields (#3295)
fe19ac3ce is described below
commit fe19ac3ceff5f9c61386e798fdb20e2f5207e799
Author: Shawn Yang <[email protected]>
AuthorDate: Thu Feb 5 23:34:59 2026 +0800
feat(compiler): add list keyword for list fields (#3295)
## Summary
- add the `list` generic keyword as a repeated alias in the FDL
lexer/parser and emit list for list fields
- update compiler docs and README to use list while keeping repeated as
an alias
- add tests to ensure list and repeated generate identical code
## Testing
- python -m pytest fory_compiler/tests/test_fdl_emitter.py
fory_compiler/tests/test_generated_code.py
Fixes #3294
---
compiler/README.md | 12 +--
compiler/examples/demo.fdl | 10 +--
compiler/fory_compiler/frontend/fdl/lexer.py | 2 +
compiler/fory_compiler/frontend/fdl/parser.py | 60 ++++++++++++--
.../fory_compiler/frontend/proto/translator.py | 2 +
compiler/fory_compiler/ir/ast.py | 14 +++-
compiler/fory_compiler/ir/emitter.py | 15 ++--
compiler/fory_compiler/ir/validator.py | 50 ++++++++++--
.../fory_compiler/tests/test_collection_nesting.py | 81 +++++++++++++++++++
compiler/fory_compiler/tests/test_fdl_emitter.py | 8 +-
.../fory_compiler/tests/test_generated_code.py | 42 ++++++++++
compiler/fory_compiler/tests/test_weak_ref.py | 40 +++++++++-
docs/compiler/generated-code.md | 8 +-
docs/compiler/index.md | 8 +-
docs/compiler/protobuf-idl.md | 14 ++--
docs/compiler/schema-idl.md | 92 +++++++++++-----------
integration_tests/idl_tests/idl/addressbook.fdl | 6 +-
integration_tests/idl_tests/idl/collection.fdl | 80 +++++++++----------
integration_tests/idl_tests/idl/graph.fdl | 8 +-
integration_tests/idl_tests/idl/optional_types.fdl | 4 +-
integration_tests/idl_tests/idl/tree.fdl | 2 +-
21 files changed, 412 insertions(+), 146 deletions(-)
diff --git a/compiler/README.md b/compiler/README.md
index 5e318c79e..bd0704ffd 100644
--- a/compiler/README.md
+++ b/compiler/README.md
@@ -8,7 +8,7 @@ The FDL compiler generates cross-language serialization code
from schema definit
- **Rich type system**: Primitives, enums, messages, lists, maps
- **Cross-language serialization**: Generated code works seamlessly with
Apache Fory
- **Type ID and namespace support**: Both numeric IDs and name-based type
registration
-- **Field modifiers**: Optional fields, reference tracking, repeated fields
+- **Field modifiers**: Optional fields, reference tracking, list fields
- **File imports**: Modular schemas with import support
## Documentation
@@ -51,7 +51,7 @@ message Dog [id=102] {
message Cat [id=103] {
ref Dog friend = 1;
optional string name = 2;
- repeated string tags = 3;
+ list<string> tags = 3;
map<string, int32> scores = 4;
int32 lives = 5;
}
@@ -202,21 +202,21 @@ message Config { ... } // Registered as "package.Config"
### Collection Types
```fdl
-repeated string tags = 1; // List<String>
-map<string, int32> scores = 2; // Map<String, Integer>
+list<string> tags = 1; // List<String>
+map<string, int32> scores = 2; // Map<String, Integer>
```
### Field Modifiers
- **`optional`**: Field can be null/None
- **`ref`**: Enable reference tracking for shared/circular references
-- **`repeated`**: Field is a list/array
+- **`list`**: Field is a list/array (alias: `repeated`)
```fdl
message Example {
optional string nullable_field = 1;
ref OtherMessage shared_ref = 2;
- repeated int32 numbers = 3;
+ list<int32> numbers = 3;
}
```
diff --git a/compiler/examples/demo.fdl b/compiler/examples/demo.fdl
index 98e9b4b6c..4d9c56b7e 100644
--- a/compiler/examples/demo.fdl
+++ b/compiler/examples/demo.fdl
@@ -21,7 +21,7 @@ message Dog [id=103] {
message Cat [id=104] {
ref Dog friend = 1;
optional string name = 2;
- repeated string tags = 3;
+ list<string> tags = 3;
map<string, int32> scores = 4;
int32 lives = 5;
}
@@ -29,10 +29,10 @@ message Cat [id=104] {
// Demonstrates primitive arrays for numeric types
message SensorData [id=105] {
string sensor_id = 1;
- repeated int32 readings = 2;
- repeated float64 temperatures = 3;
- repeated int64 timestamps = 4;
- repeated bool flags = 5;
+ list<int32> readings = 2;
+ list<float64> temperatures = 3;
+ list<int64> timestamps = 4;
+ list<bool> flags = 5;
}
// No type ID - uses name-based registration
diff --git a/compiler/fory_compiler/frontend/fdl/lexer.py
b/compiler/fory_compiler/frontend/fdl/lexer.py
index 183d7c47d..8bd160849 100644
--- a/compiler/fory_compiler/frontend/fdl/lexer.py
+++ b/compiler/fory_compiler/frontend/fdl/lexer.py
@@ -36,6 +36,7 @@ class TokenType(Enum):
OPTIONAL = auto()
REF = auto()
REPEATED = auto()
+ LIST = auto()
MAP = auto()
OPTION = auto()
TRUE = auto()
@@ -103,6 +104,7 @@ class Lexer:
"optional": TokenType.OPTIONAL,
"ref": TokenType.REF,
"repeated": TokenType.REPEATED,
+ "list": TokenType.LIST,
"map": TokenType.MAP,
"option": TokenType.OPTION,
"true": TokenType.TRUE,
diff --git a/compiler/fory_compiler/frontend/fdl/parser.py
b/compiler/fory_compiler/frontend/fdl/parser.py
index 0b574b1bb..f2caf1be4 100644
--- a/compiler/fory_compiler/frontend/fdl/parser.py
+++ b/compiler/fory_compiler/frontend/fdl/parser.py
@@ -587,13 +587,14 @@ class Parser:
"""Parse a field: optional ref repeated Type name = 1 [options];
Supports:
- - Keyword modifiers: optional ref repeated
+ - Keyword modifiers: optional ref repeated (repeated is an alias for
list)
+ - list<T> type syntax for list fields
- Bracket options: [deprecated=true, ref=true]
"""
start = self.current()
# Parse modifiers (optional/ref before repeated apply to the
collection/field,
- # optional/ref after repeated apply to elements)
+ # optional/ref after repeated apply to elements).
optional = False
ref = False
ref_options = {}
@@ -617,19 +618,30 @@ class Parser:
ref = True
ref_options = options
continue
- if self.match(TokenType.REPEATED):
+ if self.check(TokenType.REPEATED):
if repeated:
raise self.error("Repeated modifier specified more than
once")
+ self.advance()
repeated = True
continue
break
# Parse type
field_type = self.parse_type()
+ if not repeated and isinstance(field_type, ListType):
+ element_optional = field_type.element_optional
+ element_ref = field_type.element_ref
+ element_ref_options = field_type.element_ref_options
# Wrap in ListType if repeated
if repeated:
- field_type = ListType(field_type,
location=self.make_location(start))
+ field_type = ListType(
+ field_type,
+ element_optional=element_optional,
+ element_ref=element_ref,
+ element_ref_options=element_ref_options,
+ location=self.make_location(start),
+ )
# Parse field name
if self.check(TokenType.IDENT) or self.check(TokenType.TO):
@@ -665,6 +677,10 @@ class Parser:
ref_options,
element_ref_options,
)
+ if isinstance(field_type, ListType):
+ field_type.element_optional = element_optional
+ field_type.element_ref = element_ref
+ field_type.element_ref_options = element_ref_options
self.consume(TokenType.SEMI, "Expected ';' after field declaration")
@@ -704,6 +720,9 @@ class Parser:
elif self.check(TokenType.REPEATED):
self.advance()
option_name = "repeated"
+ elif self.check(TokenType.LIST):
+ self.advance()
+ option_name = "list"
elif self.check(TokenType.WEAK):
self.advance()
option_name = "weak"
@@ -871,9 +890,11 @@ class Parser:
return options
def parse_type(self) -> FieldType:
- """Parse a type: int32, string, map<K, V>, Parent.Child, or a named
type."""
+ """Parse a type: int32, string, list<T>, map<K, V>, Parent.Child, or a
named type."""
if self.check(TokenType.MAP):
return self.parse_map_type()
+ if self.check(TokenType.LIST):
+ return self.parse_list_type()
if not self.check(TokenType.IDENT):
raise self.error(f"Expected type name, got
{self.current().type.name}")
@@ -896,6 +917,35 @@ class Parser:
# It's a named type (reference to message or enum)
return NamedType(type_name, location=type_location)
+ def parse_list_type(self) -> ListType:
+ """Parse a list type: list<ElementType>."""
+ start = self.consume(TokenType.LIST)
+ self.consume(TokenType.LANGLE, "Expected '<' after 'list'")
+
+ element_optional = False
+ element_ref = False
+ element_ref_options = {}
+ while True:
+ if self.match(TokenType.OPTIONAL):
+ element_optional = True
+ continue
+ if self.match(TokenType.REF):
+ element_ref = True
+ element_ref_options = self.parse_ref_options(name="list
element")
+ continue
+ break
+
+ element_type = self.parse_type()
+ self.consume(TokenType.RANGLE, "Expected '>' after list element type")
+
+ return ListType(
+ element_type,
+ element_optional=element_optional,
+ element_ref=element_ref,
+ element_ref_options=element_ref_options,
+ location=self.make_location(start),
+ )
+
def parse_map_type(self) -> MapType:
"""Parse a map type: map<KeyType, ValueType>"""
start = self.consume(TokenType.MAP)
diff --git a/compiler/fory_compiler/frontend/proto/translator.py
b/compiler/fory_compiler/frontend/proto/translator.py
index bf2758af4..2828dc0e8 100644
--- a/compiler/fory_compiler/frontend/proto/translator.py
+++ b/compiler/fory_compiler/frontend/proto/translator.py
@@ -191,6 +191,8 @@ class ProtoTranslator:
if ref and isinstance(field_type, ListType):
element_ref = True
element_ref_options = ref_options
+ field_type.element_ref = True
+ field_type.element_ref_options = ref_options
ref = False
if ref and isinstance(field_type, MapType):
field_type = MapType(
diff --git a/compiler/fory_compiler/ir/ast.py b/compiler/fory_compiler/ir/ast.py
index f84736160..4a1f2296b 100644
--- a/compiler/fory_compiler/ir/ast.py
+++ b/compiler/fory_compiler/ir/ast.py
@@ -57,13 +57,23 @@ class NamedType:
@dataclass
class ListType:
- """A list/repeated type."""
+ """A list type (list/repeated)."""
element_type: "FieldType"
+ element_optional: bool = False
+ element_ref: bool = False
+ element_ref_options: dict = field(default_factory=dict)
location: Optional[SourceLocation] = None
def __repr__(self) -> str:
- return f"ListType({self.element_type})"
+ suffix = ""
+ if self.element_optional:
+ suffix = ", element_optional=True"
+ if self.element_ref:
+ suffix += ", element_ref=True"
+ if self.element_ref_options:
+ suffix += f", element_ref_options={self.element_ref_options}"
+ return f"ListType({self.element_type}{suffix})"
@dataclass
diff --git a/compiler/fory_compiler/ir/emitter.py
b/compiler/fory_compiler/ir/emitter.py
index 7bc0d2094..66596115a 100644
--- a/compiler/fory_compiler/ir/emitter.py
+++ b/compiler/fory_compiler/ir/emitter.py
@@ -143,13 +143,6 @@ class FDLEmitter:
parts.append("optional")
if field.ref:
parts.append(self._emit_ref_modifier(field.ref_options))
- is_list = isinstance(field.field_type, ListType)
- if is_list:
- parts.append("repeated")
- if field.element_optional:
- parts.append("optional")
- if field.element_ref:
-
parts.append(self._emit_ref_modifier(field.element_ref_options))
parts.append(self._emit_type(field.field_type))
parts.append(field.name)
parts.append("=")
@@ -166,7 +159,13 @@ class FDLEmitter:
if isinstance(field_type, NamedType):
return field_type.name
if isinstance(field_type, ListType):
- return self._emit_type(field_type.element_type)
+ parts: List[str] = []
+ if field_type.element_optional:
+ parts.append("optional")
+ if field_type.element_ref:
+
parts.append(self._emit_ref_modifier(field_type.element_ref_options))
+ parts.append(self._emit_type(field_type.element_type))
+ return f"list<{' '.join(parts)}>"
if isinstance(field_type, MapType):
key = self._emit_type(field_type.key_type)
value = self._emit_type(field_type.value_type)
diff --git a/compiler/fory_compiler/ir/validator.py
b/compiler/fory_compiler/ir/validator.py
index 0a456ca5f..712760cc0 100644
--- a/compiler/fory_compiler/ir/validator.py
+++ b/compiler/fory_compiler/ir/validator.py
@@ -66,6 +66,7 @@ class SchemaValidator:
self._check_duplicate_type_ids()
self._check_messages()
self._check_type_references()
+ self._check_collection_nesting()
self._check_ref_rules()
self._check_weak_refs()
return not self.errors
@@ -435,7 +436,7 @@ class SchemaValidator:
and field.element_ref
):
self._error(
- "ref is not allowed on repeated any fields",
+ "ref is not allowed on list<any> fields",
field.location,
)
@@ -452,8 +453,8 @@ class SchemaValidator:
if field.ref:
if isinstance(field.field_type, (ListType, MapType)):
self._error(
- "ref is not allowed on repeated/map fields; "
- "use `repeated ref` for list elements or `map<..., ref
T>` for map values",
+ "ref is not allowed on list/map fields; "
+ "use `list<ref T>` (or `repeated ref T`) for list
elements or `map<..., ref T>` for map values",
field.location,
)
else:
@@ -467,7 +468,7 @@ class SchemaValidator:
if field.element_ref:
if not isinstance(field.field_type, ListType):
self._error(
- "repeated ref is only valid for list fields",
+ "`list<ref T>` (or `repeated ref T`) is only valid for
list fields",
field.location,
)
else:
@@ -505,6 +506,45 @@ class SchemaValidator:
for f in union.fields:
check_field(f, None)
+ def _check_collection_nesting(self) -> None:
+ def check_field(
+ field: Field, enclosing_messages: Optional[List[Message]] = None
+ ):
+ field_type = field.field_type
+ if isinstance(field_type, ListType):
+ if isinstance(field_type.element_type, (ListType, MapType)):
+ self._error(
+ "nested list/map types are not allowed; only one
collection layer is supported",
+ field.location,
+ )
+ elif isinstance(field_type, MapType):
+ if isinstance(field_type.key_type, (ListType, MapType)) or
isinstance(
+ field_type.value_type, (ListType, MapType)
+ ):
+ self._error(
+ "nested list/map types are not allowed; only one
collection layer is supported",
+ field.location,
+ )
+
+ def check_message_fields(
+ message: Message,
+ enclosing_messages: Optional[List[Message]] = None,
+ ) -> None:
+ lineage = (enclosing_messages or []) + [message]
+ for f in message.fields:
+ check_field(f, lineage)
+ for nested_msg in message.nested_messages:
+ check_message_fields(nested_msg, lineage)
+ for nested_union in message.nested_unions:
+ for f in nested_union.fields:
+ check_field(f, lineage)
+
+ for message in self.schema.messages:
+ check_message_fields(message)
+ for union in self.schema.unions:
+ for f in union.fields:
+ check_field(f, None)
+
def _check_weak_refs(self) -> None:
def check_field(
field: Field,
@@ -526,7 +566,7 @@ class SchemaValidator:
if isinstance(field.field_type, ListType):
if not field.element_ref:
self._error(
- "weak_ref requires repeated ref fields (use `repeated
ref`)",
+ "weak_ref requires list element refs (use `list<ref
T>` or `repeated ref T`)",
field.location,
)
return
diff --git a/compiler/fory_compiler/tests/test_collection_nesting.py
b/compiler/fory_compiler/tests/test_collection_nesting.py
new file mode 100644
index 000000000..881451aeb
--- /dev/null
+++ b/compiler/fory_compiler/tests/test_collection_nesting.py
@@ -0,0 +1,81 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Tests for collection nesting validation."""
+
+from fory_compiler.frontend.fdl.lexer import Lexer
+from fory_compiler.frontend.fdl.parser import Parser
+from fory_compiler.ir.validator import SchemaValidator
+
+
+def parse_schema(source: str):
+ return Parser(Lexer(source).tokenize()).parse()
+
+
+def assert_nested_collections_rejected(source: str) -> None:
+ schema = parse_schema(source)
+ validator = SchemaValidator(schema)
+ assert not validator.validate()
+ assert any(
+ "nested list/map types are not allowed" in err.message
+ for err in validator.errors
+ )
+
+
+def test_nested_list_rejected():
+ source = """
+ message Foo {
+ list<list<int32>> values = 1;
+ }
+ """
+ assert_nested_collections_rejected(source)
+
+
+def test_list_of_map_rejected():
+ source = """
+ message Foo {
+ list<map<string, int32>> values = 1;
+ }
+ """
+ assert_nested_collections_rejected(source)
+
+
+def test_map_with_list_value_rejected():
+ source = """
+ message Foo {
+ map<string, list<int32>> values = 1;
+ }
+ """
+ assert_nested_collections_rejected(source)
+
+
+def test_map_with_map_value_rejected():
+ source = """
+ message Foo {
+ map<string, map<string, int32>> values = 1;
+ }
+ """
+ assert_nested_collections_rejected(source)
+
+
+def test_map_with_list_key_rejected():
+ source = """
+ message Foo {
+ map<list<int32>, int32> values = 1;
+ }
+ """
+ assert_nested_collections_rejected(source)
diff --git a/compiler/fory_compiler/tests/test_fdl_emitter.py
b/compiler/fory_compiler/tests/test_fdl_emitter.py
index d453ad47b..add85dd03 100644
--- a/compiler/fory_compiler/tests/test_fdl_emitter.py
+++ b/compiler/fory_compiler/tests/test_fdl_emitter.py
@@ -28,8 +28,8 @@ def test_emit_round_trip_preserves_list_modifiers():
package demo;
message Foo [id=10] {
- repeated optional string tags = 1;
- repeated ref Bar items = 2;
+ list<optional string> tags = 1;
+ list<ref Bar> items = 2;
fixed_uint32 count = 3;
}
@@ -43,8 +43,8 @@ def test_emit_round_trip_preserves_list_modifiers():
emitted = FDLEmitter(schema).emit()
assert "message Foo [id=10]" in emitted
- assert "repeated optional string tags = 1;" in emitted
- assert "repeated ref Bar items = 2;" in emitted
+ assert "list<optional string> tags = 1;" in emitted
+ assert "list<ref Bar> items = 2;" in emitted
assert "fixed_uint32 count = 3;" in emitted
round_trip = Parser(Lexer(emitted).tokenize()).parse()
diff --git a/compiler/fory_compiler/tests/test_generated_code.py
b/compiler/fory_compiler/tests/test_generated_code.py
index 2855fbf5b..7fda28f48 100644
--- a/compiler/fory_compiler/tests/test_generated_code.py
+++ b/compiler/fory_compiler/tests/test_generated_code.py
@@ -187,6 +187,48 @@ def
test_generated_code_integer_encoding_variants_equivalent():
assert "pyfory.tagged_uint64" in python_output
+def test_generated_code_list_modifier_aliases_equivalent():
+ repeated = dedent(
+ """
+ package gen;
+
+ message Item {
+ string name = 1;
+ }
+
+ message Container {
+ repeated string tags = 1;
+ optional repeated string labels = 2;
+ repeated optional string aliases = 3;
+ ref repeated Item items = 4;
+ repeated ref Item children = 5;
+ }
+ """
+ )
+ list_syntax = dedent(
+ """
+ package gen;
+
+ message Item {
+ string name = 1;
+ }
+
+ message Container {
+ list<string> tags = 1;
+ optional list<string> labels = 2;
+ list<optional string> aliases = 3;
+ ref list<Item> items = 4;
+ list<ref Item> children = 5;
+ }
+ """
+ )
+ schemas = {
+ "repeated": parse_fdl(repeated),
+ "list": parse_fdl(list_syntax),
+ }
+ assert_all_languages_equal(schemas)
+
+
def test_generated_code_primitive_arrays_equivalent():
fdl = dedent(
"""
diff --git a/compiler/fory_compiler/tests/test_weak_ref.py
b/compiler/fory_compiler/tests/test_weak_ref.py
index ae70b1284..35fb1f64c 100644
--- a/compiler/fory_compiler/tests/test_weak_ref.py
+++ b/compiler/fory_compiler/tests/test_weak_ref.py
@@ -118,6 +118,42 @@ def test_weak_ref_requires_repeated_ref():
validator = SchemaValidator(schema)
assert not validator.validate()
assert any(
- "weak_ref requires repeated ref fields" in err.message
- for err in validator.errors
+ "weak_ref requires list element refs" in err.message for err in
validator.errors
)
+
+
+def test_list_and_map_ref_options_with_thread_safe():
+ source = """
+ message Foo {
+ int32 id = 1;
+ }
+
+ message Bar {
+ int32 id = 1;
+ }
+
+ message Holder {
+ list<ref Foo> foos = 1;
+ list<ref(weak=true, thread_safe=true) Bar> bars = 2;
+ map<Foo, ref(weak=true, thread_safe=true) Bar> bar_map = 3;
+ }
+ """
+ schema = parse_schema(source)
+ validator = SchemaValidator(schema)
+ assert validator.validate()
+
+ holder = next(m for m in schema.messages if m.name == "Holder")
+ foos = holder.fields[0]
+ bars = holder.fields[1]
+ bar_map = holder.fields[2]
+
+ assert foos.element_ref is True
+ assert foos.element_ref_options == {}
+
+ assert bars.element_ref is True
+ assert bars.element_ref_options.get("weak_ref") is True
+ assert bars.element_ref_options.get("thread_safe_pointer") is True
+
+ assert bar_map.field_type.value_ref is True
+ assert bar_map.field_type.value_ref_options.get("weak_ref") is True
+ assert bar_map.field_type.value_ref_options.get("thread_safe_pointer") is
True
diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md
index 7abed8110..e90942510 100644
--- a/docs/compiler/generated-code.md
+++ b/docs/compiler/generated-code.md
@@ -44,7 +44,7 @@ message User [id=101] {
message Order [id=102] {
string id = 1;
ref User customer = 2;
- repeated string items = 3;
+ list<string> items = 3;
map<string, int32> quantities = 4;
Status status = 5;
}
@@ -88,7 +88,7 @@ message SearchResponse {
string url = 1;
string title = 2;
}
- repeated Result results = 1;
+ list<Result> results = 1;
}
```
@@ -642,7 +642,7 @@ fn register_all_types(fory: &mut Fory) -> Result<(),
fory::Error> {
`register_types`.
**Note:** Rust uses `Arc` by default for `ref` fields. In FDL, use
-`ref(thread_safe = false)` to generate `Rc`, and `ref(weak = true)` to generate
+`ref(thread_safe=false)` to generate `Rc`, and `ref(weak=true)` to generate
`ArcWeak`/`RcWeak`. For protobuf/IDL extensions, use
`[(fory).thread_safe_pointer = false]` and `[(fory).weak_ref = true]`.
@@ -787,7 +787,7 @@ int main() {
```
**Note:** C++ uses `std::shared_ptr<T>` for `ref` fields. Set
-`ref(weak = true)` in FDL (or `[(fory).weak_ref = true]` in protobuf) to
generate
+`ref(weak=true)` in FDL (or `[(fory).weak_ref = true]` in protobuf) to generate
`fory::serialization::SharedWeak<T>` for weak references.
## Generated Annotations Summary
diff --git a/docs/compiler/index.md b/docs/compiler/index.md
index fd62dd62d..f3998b47d 100644
--- a/docs/compiler/index.md
+++ b/docs/compiler/index.md
@@ -38,12 +38,12 @@ message User {
string name = 1;
int32 age = 2;
optional string email = 3;
- repeated string tags = 4;
+ list<string> tags = 4;
}
message Order {
ref User customer = 1;
- repeated Item items = 2;
+ list<Item> items = 2;
Status status = 3;
map<string, int32> metadata = 4;
}
@@ -164,13 +164,13 @@ data = bytes(person) # or `person.to_bytes()`
- **`optional`**: Field can be null/None
- **`ref`**: Enable reference tracking for shared/circular references
-- **`repeated`**: Field is a list/array
+- **`list`**: Field is a list/array (alias: `repeated`)
```protobuf
message Example {
optional string nullable = 1;
ref Node parent = 2;
- repeated int32 numbers = 3;
+ list<int32> numbers = 3;
}
```
diff --git a/docs/compiler/protobuf-idl.md b/docs/compiler/protobuf-idl.md
index 589d33c39..b72b00095 100644
--- a/docs/compiler/protobuf-idl.md
+++ b/docs/compiler/protobuf-idl.md
@@ -105,7 +105,7 @@ message User [id=101] {
string name = 2;
optional string email = 3;
int32 age = 4;
- repeated string tags = 5;
+ list<string> tags = 5;
map<string, string> metadata = 6;
}
```
@@ -138,7 +138,7 @@ message OrderItem [id=200] {
}
message Order [id=201] {
- repeated OrderItem items = 1;
+ list<OrderItem> items = 1;
}
```
@@ -169,12 +169,12 @@ FDL's killer feature is first-class reference tracking:
message TreeNode [id=300] {
string value = 1;
ref TreeNode parent = 2;
- repeated ref TreeNode children = 3; // Element refs
- ref repeated TreeNode path = 4; // Collection ref
+ list<ref TreeNode> children = 3; // Element refs
+ ref list<TreeNode> path = 4; // Collection ref
}
message Graph [id=301] {
- repeated ref Node nodes = 1; // Shared references preserved (elements)
+ list<ref Node> nodes = 1; // Shared references preserved (elements)
}
```
@@ -215,7 +215,7 @@ message TreeNode {
| Timestamp | `google.protobuf.Timestamp`
| `timestamp`
|
| Date | Not built-in
| `date`
|
| Duration | `google.protobuf.Duration`
| Not built-in
|
-| List | `repeated T`
| `repeated T`
|
+| List | `repeated T`
| `list<T>` (alias: `repeated T`)
|
| Map | `map<K, V>`
| `map<K, V>`
|
| Nullable | `optional T` (proto3)
| `optional T`
|
| Oneof | `oneof`
| `union` (case id = field number)
|
@@ -346,7 +346,7 @@ message Address [id=100] {
message Person [id=101] {
string name = 1;
int32 age = 2;
- repeated string emails = 3;
+ list<string> emails = 3;
Address address = 4;
}
```
diff --git a/docs/compiler/schema-idl.md b/docs/compiler/schema-idl.md
index 9eace213f..69944c89a 100644
--- a/docs/compiler/schema-idl.md
+++ b/docs/compiler/schema-idl.md
@@ -630,9 +630,9 @@ message SearchResponse {
message Result {
string url = 1;
string title = 2;
- repeated string snippets = 3;
+ list<string> snippets = 3;
}
- repeated Result results = 1;
+ list<Result> results = 1;
}
```
@@ -664,7 +664,7 @@ message SearchResponse {
message SearchResultCache {
// Reference nested type with qualified name
SearchResponse.Result cached_result = 1;
- repeated SearchResponse.Result all_results = 2;
+ list<SearchResponse.Result> all_results = 2;
}
```
@@ -735,7 +735,7 @@ message Person [id=100] {
### Rules
- Case IDs must be unique within the union
-- Cases cannot be `optional`, `repeated`, or `ref`
+- Cases cannot be `optional` or `ref`
- Union cases do not support field options
- Case types can be primitives, enums, messages, or other named types
- Union type IDs follow the rules in [Type IDs](#type-ids).
@@ -760,22 +760,23 @@ field_type field_name = field_number;
### With Modifiers
```protobuf
-optional repeated string tags = 1; // Nullable list
-repeated optional string tags = 2; // Elements may be null
-ref repeated Node nodes = 3; // Collection tracked as a reference
-repeated ref Node nodes = 4; // Elements tracked as references
+optional list<string> tags = 1; // Nullable list
+list<optional string> tags = 2; // Elements may be null
+ref list<Node> nodes = 3; // Collection tracked as a reference
+list<ref Node> nodes = 4; // Elements tracked as references
```
**Grammar:**
```
field_def := [modifiers] field_type IDENTIFIER '=' INTEGER ';'
-modifiers := { 'optional' | 'ref' } ['repeated' { 'optional' | 'ref' }]
-field_type := primitive_type | named_type | map_type
+modifiers := { 'optional' | 'ref' }
+field_type := primitive_type | named_type | list_type | map_type
+list_type := 'list' '<' { 'optional' | 'ref' } field_type '>'
```
-Modifiers before `repeated` apply to the field/collection. Modifiers after
-`repeated` apply to list elements.
+Modifiers apply to the field/collection. Use `list<...>` to describe element
+modifiers. `repeated` is accepted as an alias for `list`.
### Field Modifiers
@@ -815,7 +816,7 @@ Enables reference tracking for shared/circular references:
message Node {
string value = 1;
ref Node parent = 2; // Can point to shared object
- repeated ref Node children = 3;
+ list<ref Node> children = 3;
}
```
@@ -835,17 +836,17 @@ message Node {
| Rust | `parent: Node` | `parent: Arc<Node>` |
| C++ | `Node parent` | `std::shared_ptr<Node> parent` |
-Rust uses `Arc` by default; use `ref(thread_safe = false)` or `ref(weak =
true)`
+Rust uses `Arc` by default; use `ref(thread_safe=false)` or `ref(weak=true)`
to customize pointer types (see [Field-Level Fory
Options](#field-level-fory-options)).
-#### `repeated`
+#### `list`
Marks the field as a list/array:
```protobuf
message Document {
- repeated string tags = 1;
- repeated User authors = 2;
+ list<string> tags = 1;
+ list<User> authors = 2;
}
```
@@ -865,27 +866,27 @@ Modifiers can be combined:
```fdl
message Example {
- optional repeated string tags = 1; // Nullable list
- repeated optional string aliases = 2; // Elements may be null
- ref repeated Node nodes = 3; // Collection tracked as a reference
- repeated ref Node children = 4; // Elements tracked as references
+ optional list<string> tags = 1; // Nullable list
+ list<optional string> aliases = 2; // Elements may be null
+ ref list<Node> nodes = 3; // Collection tracked as a reference
+ list<ref Node> children = 4; // Elements tracked as references
optional ref User owner = 5; // Nullable tracked reference
}
```
-Modifiers before `repeated` apply to the field/collection. Modifiers after
-`repeated` apply to elements.
+Modifiers before `list` apply to the field/collection. Modifiers after `list`
+apply to elements. `repeated` is accepted as an alias for `list`.
**List modifier mapping:**
-| FDL | Java
| Python | Go | Rust
| C++ |
-| -------------------------- | ----------------------------------------------
| --------------------------------------- | ----------------------- |
--------------------- | ----------------------------------------- |
-| `optional repeated string` | `List<String>` + `@ForyField(nullable = true)`
| `Optional[List[str]]` | `[]string` + `nullable` |
`Option<Vec<String>>` | `std::optional<std::vector<std::string>>` |
-| `repeated optional string` | `List<String>` (nullable elements)
| `List[Optional[str]]` | `[]*string` |
`Vec<Option<String>>` | `std::vector<std::optional<std::string>>` |
-| `ref repeated User` | `List<User>` + `@ForyField(ref = true)`
| `List[User]` + `pyfory.field(ref=True)` | `[]User` + `ref` |
`Arc<Vec<User>>` | `std::shared_ptr<std::vector<User>>` |
-| `repeated ref User` | `List<User>`
| `List[User]` | `[]*User` + `ref=false` |
`Vec<Arc<User>>` | `std::vector<std::shared_ptr<User>>` |
+| FDL | Java |
Python | Go | Rust
| C++ |
+| ----------------------- | ---------------------------------------------- |
--------------------------------------- | ----------------------- |
--------------------- | ----------------------------------------- |
+| `optional list<string>` | `List<String>` + `@ForyField(nullable = true)` |
`Optional[List[str]]` | `[]string` + `nullable` |
`Option<Vec<String>>` | `std::optional<std::vector<std::string>>` |
+| `list<optional string>` | `List<String>` (nullable elements) |
`List[Optional[str]]` | `[]*string` |
`Vec<Option<String>>` | `std::vector<std::optional<std::string>>` |
+| `ref list<User>` | `List<User>` + `@ForyField(ref = true)` |
`List[User]` + `pyfory.field(ref=True)` | `[]User` + `ref` |
`Arc<Vec<User>>` | `std::shared_ptr<std::vector<User>>` |
+| `list<ref User>` | `List<User>` |
`List[User]` | `[]*User` + `ref=false` |
`Vec<Arc<User>>` | `std::vector<std::shared_ptr<User>>` |
-Use `ref(thread_safe = false)` in FDL (or `[(fory).thread_safe_pointer =
false]` in protobuf)
+Use `ref(thread_safe=false)` in FDL (or `[(fory).thread_safe_pointer = false]`
in protobuf)
to generate `Rc` instead of `Arc` in Rust.
## Field Numbers
@@ -916,7 +917,7 @@ message Example {
## Type System
FDL provides a cross-language type system for primitives, named types, and
collections.
-Field modifiers like `optional`, `repeated`, and `ref` define nullability,
collections, and
+Field modifiers like `optional`, `list`, and `ref` define nullability,
collections, and
reference tracking (see [Field Modifiers](#field-modifiers)).
### Primitive Types
@@ -1141,7 +1142,7 @@ any payload = 1;
- Allowed runtime values are limited to `bool`, `string`, `enum`, `message`,
and `union`.
Other primitives (numeric, bytes, date/time) and list/map are not supported;
wrap them in a
message or use explicit fields instead.
-- `ref` is not allowed on `any` fields (including repeated/map values). Wrap
`any` in a message
+- `ref` is not allowed on `any` fields (including list/map values). Wrap `any`
in a message
if you need reference tracking.
- The runtime type must be registered in the target language schema/IDL
registration; unknown
types fail to deserialize.
@@ -1162,11 +1163,14 @@ message Order {
### Collection Types
-#### List (repeated)
+#### List (`list`)
-Use the `repeated` modifier for list types. See [Field
Modifiers](#field-modifiers) for
+Use the `list<...>` type for list fields. `repeated` is accepted as an alias.
See [Field Modifiers](#field-modifiers) for
modifier combinations and language mapping.
+Nested collection types are not supported. Use a message wrapper if you need
+`list<list<...>>`, `list<map<...>>`, or `map<..., list<...>>`.
+
#### Map
Maps with typed keys and values:
@@ -1217,7 +1221,7 @@ Y = Safe conversion, - = Not recommended
- Use `string` for text data (UTF-8) and `bytes` for binary data.
- Use `optional` only when the field may legitimately be absent.
- Use `ref` only when needed for shared or circular references.
-- Prefer `repeated` for ordered sequences and `map` for key-value lookups.
+- Prefer `list` for ordered sequences and `map` for key-value lookups.
## Type IDs
@@ -1333,7 +1337,7 @@ message Product [id=202] {
string description = 3;
float64 price = 4;
int32 stock = 5;
- repeated string categories = 6;
+ list<string> categories = 6;
map<string, string> attributes = 7;
}
@@ -1346,7 +1350,7 @@ message OrderItem [id=203] {
message Order [id=204] {
string id = 1;
ref Customer customer = 2;
- repeated OrderItem items = 3;
+ list<OrderItem> items = 3;
OrderStatus status = 4;
PaymentMethod payment_method = 5;
float64 total = 6;
@@ -1360,7 +1364,7 @@ message ShopConfig {
string store_name = 1;
string currency = 2;
float64 tax_rate = 3;
- repeated string supported_countries = 4;
+ list<string> supported_countries = 4;
}
```
@@ -1449,7 +1453,7 @@ message Example {
ref MyType friend = 1;
string nickname = 2 [nullable = true];
ref MyType data = 3 [nullable = true];
- ref(weak = true) MyType parent = 4;
+ ref(weak=true) MyType parent = 4;
}
```
@@ -1462,17 +1466,17 @@ message Example {
| `weak_ref` | bool | C++/Rust only: generate weak pointers for
`ref` fields |
**Note:** For FDL, use `ref` (and optional `ref(...)`) modifiers:
-`ref MyType friend = 1;`, `repeated ref(weak = true) Child children = 2;`,
-`map<string, ref(weak = true) Node> nodes = 3;`. For protobuf, use
+`ref MyType friend = 1;`, `list<ref(weak=true) Child> children = 2;`,
+`map<string, ref(weak=true) Node> nodes = 3;`. For protobuf, use
`[(fory).ref = true]` and `[(fory).weak_ref = true]`. `weak_ref` is a codegen
hint for C++/Rust and is ignored by Java/Python/Go. It must be used with `ref`
-(`repeated ref` for collections, or `map<..., ref T>` for map values).
+(`list<ref T>` for collections, or `map<..., ref T>` for map values).
To use `Rc` instead of `Arc` in Rust for a specific field:
```fdl
message Graph {
- ref(thread_safe = false) Node root = 1;
+ ref(thread_safe=false) Node root = 1;
}
```
@@ -1568,7 +1572,7 @@ reserved_stmt := 'reserved' reserved_items ';'
reserved_items := reserved_item (',' reserved_item)*
reserved_item := INTEGER | INTEGER 'to' INTEGER | INTEGER 'to' 'max' | STRING
-modifiers := { 'optional' | 'ref' } ['repeated' { 'optional' | 'ref' }]
+modifiers := { 'optional' | 'ref' } ['list' { 'optional' | 'ref' }]
field_type := primitive_type | named_type | map_type
primitive_type := 'bool'
diff --git a/integration_tests/idl_tests/idl/addressbook.fdl
b/integration_tests/idl_tests/idl/addressbook.fdl
index 756a2b2d6..0fd576dfb 100644
--- a/integration_tests/idl_tests/idl/addressbook.fdl
+++ b/integration_tests/idl_tests/idl/addressbook.fdl
@@ -25,7 +25,7 @@ message Person [id=100] {
string name = 1;
int32 id = 2;
string email = 3;
- repeated string tags = 4;
+ list<string> tags = 4;
map<string, int32> scores = 5;
float64 salary = 6;
@@ -40,7 +40,7 @@ message Person [id=100] {
PhoneType phone_type = 2;
}
- repeated PhoneNumber phones = 7;
+ list<PhoneNumber> phones = 7;
Animal pet = 8;
}
@@ -60,6 +60,6 @@ union Animal [id=106] {
}
message AddressBook [id=103] {
- repeated Person people = 1;
+ list<Person> people = 1;
map<string, Person> people_by_name = 2;
}
diff --git a/integration_tests/idl_tests/idl/collection.fdl
b/integration_tests/idl_tests/idl/collection.fdl
index 091f11243..113cc64cb 100644
--- a/integration_tests/idl_tests/idl/collection.fdl
+++ b/integration_tests/idl_tests/idl/collection.fdl
@@ -18,53 +18,53 @@
package collection;
message NumericCollections [id=210] {
- repeated int8 int8_values = 1;
- repeated int16 int16_values = 2;
- repeated int32 int32_values = 3;
- repeated int64 int64_values = 4;
- repeated uint8 uint8_values = 5;
- repeated uint16 uint16_values = 6;
- repeated uint32 uint32_values = 7;
- repeated uint64 uint64_values = 8;
- repeated float32 float32_values = 9;
- repeated float64 float64_values = 10;
+ list<int8> int8_values = 1;
+ list<int16> int16_values = 2;
+ list<int32> int32_values = 3;
+ list<int64> int64_values = 4;
+ list<uint8> uint8_values = 5;
+ list<uint16> uint16_values = 6;
+ list<uint32> uint32_values = 7;
+ list<uint64> uint64_values = 8;
+ list<float32> float32_values = 9;
+ list<float64> float64_values = 10;
}
union NumericCollectionUnion [id=211] {
- repeated int8 int8_values = 1;
- repeated int16 int16_values = 2;
- repeated int32 int32_values = 3;
- repeated int64 int64_values = 4;
- repeated uint8 uint8_values = 5;
- repeated uint16 uint16_values = 6;
- repeated uint32 uint32_values = 7;
- repeated uint64 uint64_values = 8;
- repeated float32 float32_values = 9;
- repeated float64 float64_values = 10;
+ list<int8> int8_values = 1;
+ list<int16> int16_values = 2;
+ list<int32> int32_values = 3;
+ list<int64> int64_values = 4;
+ list<uint8> uint8_values = 5;
+ list<uint16> uint16_values = 6;
+ list<uint32> uint32_values = 7;
+ list<uint64> uint64_values = 8;
+ list<float32> float32_values = 9;
+ list<float64> float64_values = 10;
}
message NumericCollectionsArray [id=212] {
- repeated int8 int8_values = 1 [java_array = true];
- repeated int16 int16_values = 2 [java_array = true];
- repeated int32 int32_values = 3 [java_array = true];
- repeated int64 int64_values = 4 [java_array = true];
- repeated uint8 uint8_values = 5 [java_array = true];
- repeated uint16 uint16_values = 6 [java_array = true];
- repeated uint32 uint32_values = 7 [java_array = true];
- repeated uint64 uint64_values = 8 [java_array = true];
- repeated float32 float32_values = 9 [java_array = true];
- repeated float64 float64_values = 10 [java_array = true];
+ list<int8> int8_values = 1 [java_array = true];
+ list<int16> int16_values = 2 [java_array = true];
+ list<int32> int32_values = 3 [java_array = true];
+ list<int64> int64_values = 4 [java_array = true];
+ list<uint8> uint8_values = 5 [java_array = true];
+ list<uint16> uint16_values = 6 [java_array = true];
+ list<uint32> uint32_values = 7 [java_array = true];
+ list<uint64> uint64_values = 8 [java_array = true];
+ list<float32> float32_values = 9 [java_array = true];
+ list<float64> float64_values = 10 [java_array = true];
}
union NumericCollectionArrayUnion [id=213] {
- repeated int8 int8_values = 1 [java_array = true];
- repeated int16 int16_values = 2 [java_array = true];
- repeated int32 int32_values = 3 [java_array = true];
- repeated int64 int64_values = 4 [java_array = true];
- repeated uint8 uint8_values = 5 [java_array = true];
- repeated uint16 uint16_values = 6 [java_array = true];
- repeated uint32 uint32_values = 7 [java_array = true];
- repeated uint64 uint64_values = 8 [java_array = true];
- repeated float32 float32_values = 9 [java_array = true];
- repeated float64 float64_values = 10 [java_array = true];
+ list<int8> int8_values = 1 [java_array = true];
+ list<int16> int16_values = 2 [java_array = true];
+ list<int32> int32_values = 3 [java_array = true];
+ list<int64> int64_values = 4 [java_array = true];
+ list<uint8> uint8_values = 5 [java_array = true];
+ list<uint16> uint16_values = 6 [java_array = true];
+ list<uint32> uint32_values = 7 [java_array = true];
+ list<uint64> uint64_values = 8 [java_array = true];
+ list<float32> float32_values = 9 [java_array = true];
+ list<float64> float64_values = 10 [java_array = true];
}
diff --git a/integration_tests/idl_tests/idl/graph.fdl
b/integration_tests/idl_tests/idl/graph.fdl
index 89785936d..47e968ab6 100644
--- a/integration_tests/idl_tests/idl/graph.fdl
+++ b/integration_tests/idl_tests/idl/graph.fdl
@@ -20,8 +20,8 @@ package graph;
message Node {
string id = 1;
- repeated ref Edge out_edges = 2;
- repeated ref Edge in_edges = 3;
+ list<ref Edge> out_edges = 2;
+ list<ref Edge> in_edges = 3;
}
message Edge {
@@ -33,6 +33,6 @@ message Edge {
}
message Graph {
- repeated ref Node nodes = 1;
- repeated ref Edge edges = 2;
+ list<ref Node> nodes = 1;
+ list<ref Edge> edges = 2;
}
diff --git a/integration_tests/idl_tests/idl/optional_types.fdl
b/integration_tests/idl_tests/idl/optional_types.fdl
index 9f26073ce..f0a23a830 100644
--- a/integration_tests/idl_tests/idl/optional_types.fdl
+++ b/integration_tests/idl_tests/idl/optional_types.fdl
@@ -43,8 +43,8 @@ message AllOptionalTypes [id=120] {
optional bytes bytes_value = 24;
optional date date_value = 25;
optional timestamp timestamp_value = 26;
- optional repeated int32 int32_list = 27;
- optional repeated string string_list = 28;
+ optional list<int32> int32_list = 27;
+ optional list<string> string_list = 28;
optional map<string, int64> int64_map = 29;
}
diff --git a/integration_tests/idl_tests/idl/tree.fdl
b/integration_tests/idl_tests/idl/tree.fdl
index 2ab8491ae..825449116 100644
--- a/integration_tests/idl_tests/idl/tree.fdl
+++ b/integration_tests/idl_tests/idl/tree.fdl
@@ -23,6 +23,6 @@ message TreeNode {
string id = 1;
string name = 2;
- repeated ref TreeNode children = 3;
+ list<ref TreeNode> children = 3;
ref(weak=true) TreeNode parent = 4; // back-pointer
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]