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]

Reply via email to