Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
a66d4be
supprot auto generated hash for registration
chaokunyang Jan 31, 2026
3bd3ed6
extend meta string encoding and register tupe by hash as name
chaokunyang Jan 31, 2026
bb5a123
lint code
chaokunyang Jan 31, 2026
e0f3787
simplify numer string encoding
chaokunyang Jan 31, 2026
64bb94d
fix(xlang): align meta string name encoding
chaokunyang Jan 31, 2026
0e753ec
fix(compiler): qualify python nested bytes return types
chaokunyang Jan 31, 2026
0cc179e
chore(cpp): apply clang-format spacing
chaokunyang Jan 31, 2026
706c580
fix: align meta string handling for cpp and rust
chaokunyang Feb 1, 2026
f009ea2
fix(ci): resolve idl auto_id and metastring hash
chaokunyang Feb 1, 2026
a0dd1f7
fix(ci): keep auto_id envelope stable in cpp tests
chaokunyang Feb 1, 2026
efb870b
fix(python): reset metastring resolver state in tests
chaokunyang Feb 1, 2026
cbd374d
style: apply format updates
chaokunyang Feb 1, 2026
cc8ed2f
style(cpp): align type_info formatting
chaokunyang Feb 1, 2026
4f2fcaa
clear generated files
chaokunyang Feb 1, 2026
922a4a9
use int32 store user type id
chaokunyang Feb 1, 2026
4ddee57
fix(xlang): align type info handling
chaokunyang Feb 2, 2026
944ab58
fix(compiler): hash auto-ids by package name
chaokunyang Feb 2, 2026
3427c89
fix(compiler): error on auto-id collisions
chaokunyang Feb 2, 2026
0e0c792
update auto id doc
chaokunyang Feb 2, 2026
6c58fd9
add union example
chaokunyang Feb 2, 2026
f81994e
Merge asf/main into use_generated_hash_for_generated_class_as_name
chaokunyang Feb 2, 2026
8a30ca8
simplify type id check in c++
chaokunyang Feb 2, 2026
5f47c42
refactor type into write/read
chaokunyang Feb 2, 2026
306937f
fix rust error
chaokunyang Feb 2, 2026
5ca364d
refactor type into write/read
chaokunyang Feb 2, 2026
2a527d8
generate test code under generated dir
chaokunyang Feb 2, 2026
364c1ca
refactor more write/read type info
chaokunyang Feb 2, 2026
4b0f1ab
refine rust write/read typeinfo
chaokunyang Feb 2, 2026
645547f
refine c++ write/read typeinfo
chaokunyang Feb 2, 2026
ff26954
simplify go array/slice write/read typeinfo
chaokunyang Feb 2, 2026
6dc2a1b
refine write/read typeinfo for java/c++
chaokunyang Feb 2, 2026
9d169f4
simplify go write/read type info
chaokunyang Feb 2, 2026
6904cf5
update python type info write/read
chaokunyang Feb 2, 2026
29d5f08
lint code
chaokunyang Feb 2, 2026
4141059
use uint8 for fory type id
chaokunyang Feb 2, 2026
46dff7a
use uint8_t for fory python type id
chaokunyang Feb 2, 2026
4c0c9fc
add enable_auto_type_id option
chaokunyang Feb 2, 2026
a64ac39
fix java test
chaokunyang Feb 2, 2026
ac0e0e4
refactor: align type info encoding
chaokunyang Feb 2, 2026
b75db46
fix(xlang): handle unknown types and lint
chaokunyang Feb 3, 2026
8a79b09
use var_uint32 for type id with flags
chaokunyang Feb 3, 2026
779d800
fix(meta): encode user type ids in typedefs
chaokunyang Feb 3, 2026
9311495
align meta share for xlang
chaokunyang Feb 3, 2026
deeace0
make userTypeId and fory type id in ClassInfo immutable
chaokunyang Feb 3, 2026
d134865
rename ClassInfo/ClassDef to TypeInfo/TypeDef
chaokunyang Feb 3, 2026
052a879
support deserialize into different type for struct field
chaokunyang Feb 3, 2026
726f529
lint code
chaokunyang Feb 3, 2026
79a0b5e
optimize read to different field type
chaokunyang Feb 3, 2026
e5d560c
fix(xlang): align field type handling across languages
chaokunyang Feb 3, 2026
28cebeb
style: apply format tooling fixes
chaokunyang Feb 3, 2026
3247b6e
style(cpp): align clang-format output
chaokunyang Feb 3, 2026
1b79dec
fix rename classinfo to typeinfo
chaokunyang Feb 3, 2026
9f74f40
fix rename classinfo to typeinfo
chaokunyang Feb 3, 2026
cee6778
fix(compiler): emit long ids in java registration
chaokunyang Feb 3, 2026
962c4cf
fix go ild tests
chaokunyang Feb 3, 2026
7269c92
fix(ci): stabilize idl generation and type info
chaokunyang Feb 3, 2026
fc94983
fix(rust): expose idl generated modules
chaokunyang Feb 3, 2026
1521430
refactor(go): key user types by id
chaokunyang Feb 3, 2026
7bdc6c8
fix rename
chaokunyang Feb 3, 2026
8db27af
refine writeClassInternal
chaokunyang Feb 3, 2026
57a0ab8
refine write/read java typeinfo
chaokunyang Feb 3, 2026
2bef18d
refactor(cpp,python): classify type registration
chaokunyang Feb 3, 2026
9124e28
rename typeinfo to type_info
chaokunyang Feb 3, 2026
24bab82
refactor(rust): key user types by id
chaokunyang Feb 3, 2026
8db94d3
fix java read type info
chaokunyang Feb 3, 2026
7f567ac
refactor(rust): store type ids as TypeId
chaokunyang Feb 3, 2026
71c4bde
fix(rust): return type id in derive serializer
chaokunyang Feb 3, 2026
8fcff2d
refactor(js): key user types by id only
chaokunyang Feb 3, 2026
d4939f0
refactor(js): rename classResolver file
chaokunyang Feb 3, 2026
909df13
lint code
chaokunyang Feb 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 2 additions & 46 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ java/**/dependency-reduced-pom.xml
**/*.html
**/target
.idea/
generated/
java/dependency-reduced-pom.xml
java/foo
scala/.bsp
Expand All @@ -38,55 +39,10 @@ bazel-fory/**
**/generated
java/**/generated

# IDL compiler integration test outputs
integration_tests/idl_tests/cpp/generated/
integration_tests/idl_tests/go/addressbook*.go
integration_tests/idl_tests/go/any_example/
integration_tests/idl_tests/go/any_example_pb/
integration_tests/idl_tests/go/complex_fbs/
integration_tests/idl_tests/go/complex_pb/
integration_tests/idl_tests/go/monster/
integration_tests/idl_tests/go/optional_types/
integration_tests/idl_tests/go/tree/
integration_tests/idl_tests/go/graph/
integration_tests/idl_tests/go/collection/
integration_tests/idl_tests/java/src/main/
integration_tests/idl_tests/go/root/
integration_tests/idl_tests/go/addressbook/
integration_tests/idl_tests/java/src/main/java/collection/
integration_tests/idl_tests/java/src/main/java/addressbook/
integration_tests/idl_tests/java/src/main/java/complex_fbs/
integration_tests/idl_tests/java/src/main/java/complex_pb/
integration_tests/idl_tests/java/src/main/java/monster/
integration_tests/idl_tests/java/src/main/java/optional_types/
integration_tests/idl_tests/java/src/main/java/tree/
integration_tests/idl_tests/java/src/main/java/graph/
integration_tests/idl_tests/java/src/main/java/root/
integration_tests/idl_tests/python/src/addressbook.py
integration_tests/idl_tests/python/src/any_example.py
integration_tests/idl_tests/python/src/any_example_pb.py
integration_tests/idl_tests/python/src/complex_fbs.py
integration_tests/idl_tests/python/src/complex_pb.py
integration_tests/idl_tests/python/src/monster.py
integration_tests/idl_tests/python/src/optional_types.py
integration_tests/idl_tests/python/src/collection.py
integration_tests/idl_tests/python/src/tree.py
integration_tests/idl_tests/python/src/graph.py
integration_tests/idl_tests/python/src/root.py
integration_tests/idl_tests/rust/src/addressbook.rs
integration_tests/idl_tests/rust/src/any_example.rs
integration_tests/idl_tests/rust/src/any_example_pb.rs
integration_tests/idl_tests/rust/src/complex_fbs.rs
integration_tests/idl_tests/rust/src/complex_pb.rs
integration_tests/idl_tests/rust/src/monster.rs
integration_tests/idl_tests/rust/src/optional_types.rs
integration_tests/idl_tests/rust/src/collection.rs
integration_tests/idl_tests/rust/src/tree.rs
integration_tests/idl_tests/rust/src/graph.rs
integration_tests/idl_tests/rust/src/root.rs
javascript/**/dist/
javascript/**/node_modules/
javascript/**/build
javascript/junit.xml
MODULE.bazel.lock
.DS_Store
**/.DS_Store
Expand Down
4 changes: 4 additions & 0 deletions compiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,12 @@ FDL uses plain option keys without a `(fory)` prefix:
```fdl
option use_record_for_java_message = true;
option polymorphism = true;
option enable_auto_type_id = true;
```

`enable_auto_type_id` defaults to `true`. Set it to `false` to keep namespace-based registration
for types that omit explicit IDs.

**Message/Enum options:**

```fdl
Expand Down
7 changes: 7 additions & 0 deletions compiler/extension/fory_options.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
//
// File-level options:
// option (fory).use_record_for_java_message = true;
// option (fory).enable_auto_type_id = true;
//
// Message options:
// message MyMessage {
Expand Down Expand Up @@ -67,6 +68,12 @@ message ForyFileOptions {
// When true, type metadata is included in serialization for polymorphic
// dispatch. Default: false
optional bool polymorphism = 2;

// Control auto-generation of type IDs when none are specified.
// When true (default in the compiler), IDs are generated from the package
// namespace and type name. When false, types without explicit IDs are
// registered by namespace and name.
optional bool enable_auto_type_id = 3;
}

extend google.protobuf.FileOptions { optional ForyFileOptions fory = 50001; }
Expand Down
43 changes: 42 additions & 1 deletion compiler/fory_compiler/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def resolve_imports(
# Create merged schema with imported types first (so they can be referenced)
merged_schema = Schema(
package=schema.package,
package_alias=schema.package_alias,
imports=schema.imports,
enums=imported_enums + schema.enums,
messages=imported_messages + schema.messages,
Expand Down Expand Up @@ -181,8 +182,34 @@ def go_package_info(schema: Schema) -> Tuple[Optional[str], str]:
return None, ""


def _find_go_module_root(base_go_out: Path) -> Optional[Path]:
base_go_out = base_go_out.resolve()
for candidate in (base_go_out, *base_go_out.parents):
if (candidate / "go.mod").is_file():
return candidate
return None


def _read_go_module_path(go_module_root: Path) -> Optional[str]:
module_file = go_module_root / "go.mod"
if not module_file.is_file():
return None
try:
with module_file.open("r", encoding="utf-8") as handle:
for line in handle:
stripped = line.strip()
if stripped.startswith("module "):
return stripped.split(None, 1)[1].strip()
except OSError:
return None
return None


def resolve_go_module_root(base_go_out: Path, schema: Schema) -> Path:
"""Infer the Go module root for output layout."""
module_root = _find_go_module_root(base_go_out)
if module_root:
return module_root
_, package_name = go_package_info(schema)
if package_name and base_go_out.name == package_name:
return base_go_out.parent
Expand All @@ -195,6 +222,14 @@ def resolve_go_output_dir(base_go_out: Path, schema: Schema) -> Path:
if package_name and base_go_out.name == package_name:
return base_go_out
if import_path:
module_path = _read_go_module_path(base_go_out)
if module_path:
module_path = module_path.rstrip("/")
if import_path.startswith(module_path):
rel_path = import_path[len(module_path) :].lstrip("/")
if rel_path:
return base_go_out / rel_path
return base_go_out
last_segment = import_path.rstrip("/").split("/")[-1]
if package_name and last_segment == package_name:
return base_go_out / package_name
Expand Down Expand Up @@ -501,7 +536,13 @@ def compile_file_recursive(
effective_outputs = lang_output_dirs
if "go" in lang_output_dirs:
go_root = go_module_root or lang_output_dirs["go"]
go_out = resolve_go_output_dir(go_root, schema)
if (
schema.get_option("go_package") is None
and lang_output_dirs["go"] != go_root
):
go_out = lang_output_dirs["go"]
else:
go_out = resolve_go_output_dir(go_root, schema)
if go_out != lang_output_dirs["go"]:
effective_outputs = dict(lang_output_dirs)
effective_outputs["go"] = go_out
Expand Down
1 change: 1 addition & 0 deletions compiler/fory_compiler/frontend/fbs/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def _location(self, line: int, column: int) -> SourceLocation:
def translate(self) -> Schema:
return Schema(
package=self.schema.namespace,
package_alias=None,
imports=[Import(path=inc) for inc in self.schema.includes],
enums=[self._translate_enum(e) for e in self.schema.enums],
unions=[self._translate_union(u) for u in self.schema.unions],
Expand Down
32 changes: 26 additions & 6 deletions compiler/fory_compiler/frontend/fdl/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"""Recursive descent parser for FDL."""

import warnings
from typing import List, Set
from typing import List, Set, Optional

from fory_compiler.ir.ast import (
Schema,
Expand Down Expand Up @@ -47,6 +47,7 @@
"deprecated",
"use_record_for_java_message",
"polymorphism",
"enable_auto_type_id",
"go_nested_type_style",
}

Expand Down Expand Up @@ -92,6 +93,7 @@

KNOWN_MESSAGE_OPTIONS: Set[str] = {
"id",
"alias",
"evolving",
"use_record_for_java",
"deprecated",
Expand All @@ -100,6 +102,7 @@

KNOWN_UNION_OPTIONS: Set[str] = {
"id",
"alias",
"deprecated",
}

Expand Down Expand Up @@ -187,6 +190,7 @@ def error(self, message: str) -> ParseError:
def parse(self) -> Schema:
"""Parse the entire input and return a Schema."""
package = None
package_alias = None
imports = []
enums = []
messages = []
Expand All @@ -197,7 +201,7 @@ def parse(self) -> Schema:
if self.check(TokenType.PACKAGE):
if package is not None:
raise self.error("Duplicate package declaration")
package = self.parse_package()
package, package_alias = self.parse_package()
elif self.check(TokenType.IMPORT):
imports.append(self.parse_import())
elif self.check(TokenType.OPTION):
Expand All @@ -215,6 +219,7 @@ def parse(self) -> Schema:

return Schema(
package=package,
package_alias=package_alias,
imports=imports,
enums=enums,
messages=messages,
Expand All @@ -233,8 +238,8 @@ def make_location(self, token: Token) -> SourceLocation:
source_format=self.source_format,
)

def parse_package(self) -> str:
"""Parse a package declaration: package foo.bar;"""
def parse_package(self) -> tuple[str, Optional[str]]:
"""Parse a package declaration: package foo.bar [alias baz];"""
self.consume(TokenType.PACKAGE)

# Package name can be dotted: foo.bar.baz
Expand All @@ -245,8 +250,23 @@ def parse_package(self) -> str:
self.consume(TokenType.IDENT, "Expected identifier after '.'").value
)

self.consume(TokenType.SEMI, "Expected ';' after package name")
return ".".join(parts)
alias = None
if self.check(TokenType.IDENT) and self.current().value == "alias":
self.advance() # consume alias keyword
alias_parts = [
self.consume(TokenType.IDENT, "Expected identifier after 'alias'").value
]
while self.check(TokenType.DOT):
self.advance()
alias_parts.append(
self.consume(
TokenType.IDENT, "Expected identifier after '.' in alias"
).value
)
alias = ".".join(alias_parts)

self.consume(TokenType.SEMI, "Expected ';' after package declaration")
return ".".join(parts), alias

def parse_option_value(self):
"""Parse an option value (string, bool, int, or identifier)."""
Expand Down
1 change: 1 addition & 0 deletions compiler/fory_compiler/frontend/proto/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def _location(self, line: int, column: int) -> SourceLocation:
def translate(self) -> Schema:
return Schema(
package=self.proto_schema.package,
package_alias=None,
imports=self._translate_imports(),
enums=[self._translate_enum(e) for e in self.proto_schema.enums],
messages=[self._translate_message(m) for m in self.proto_schema.messages],
Expand Down
15 changes: 15 additions & 0 deletions compiler/fory_compiler/generators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,21 @@ def strip_enum_prefix(self, enum_name: str, value_name: str) -> str:

return remainder

def format_type_id_comment(self, type_def, comment_prefix: str) -> Optional[str]:
"""Format a type id comment for a message/union."""
type_id = getattr(type_def, "type_id", None)
if type_id is None:
return None
if getattr(type_def, "id_generated", False):
source = getattr(type_def, "id_source", None) or "unknown"
return f"{comment_prefix} Type ID {type_id} is generated from {source}"
return f"{comment_prefix} Type ID {type_id} is specified manually."

def should_register_by_id(self, type_def) -> bool:
"""Return True if a type should be registered by numeric ID."""
type_id = getattr(type_def, "type_id", None)
return type_id is not None

def get_license_header(self, comment_prefix: str = "//") -> str:
"""Get the Apache license header."""
lines = [
Expand Down
20 changes: 14 additions & 6 deletions compiler/fory_compiler/generators/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,9 @@ def generate_message_definition(
lineage = parent_stack + [message]
body_indent = f"{indent} "
field_indent = f"{indent} "
comment = self.format_type_id_comment(message, f"{indent}//")
if comment:
lines.append(comment)
lines.append(f"{indent}class {class_name} final {{")
lines.append(f"{body_indent}public:")
if message.fields:
Expand Down Expand Up @@ -982,6 +985,9 @@ def generate_union_definition(
]
variant_type = f"std::variant<{', '.join(case_types)}>"

comment = self.format_type_id_comment(union, f"{indent}//")
if comment:
lines.append(comment)
lines.append(f"{indent}class {class_name} final {{")
lines.append(f"{body_indent}public:")
lines.append(f"{body_indent} enum class {case_enum} : uint32_t {{")
Expand Down Expand Up @@ -1234,7 +1240,7 @@ def generate_union_serializer(
lines.append("")
lines.append(" static inline void write_type_info(WriteContext &ctx) {")
lines.append(
f" auto result = ctx.write_any_typeinfo(static_cast<uint32_t>(TypeId::TYPED_UNION), std::type_index(typeid({qualified_name})));"
f" auto result = ctx.write_any_type_info(static_cast<uint32_t>(TypeId::TYPED_UNION), std::type_index(typeid({qualified_name})));"
)
lines.append(" if (FORY_PREDICT_FALSE(!result.ok())) {")
lines.append(" ctx.set_error(std::move(result).error());")
Expand All @@ -1250,7 +1256,9 @@ def generate_union_serializer(
lines.append(" return;")
lines.append(" }")
lines.append(" const TypeInfo *expected = type_info_res.value();")
lines.append(" const TypeInfo *remote = ctx.read_any_typeinfo(ctx.error());")
lines.append(
" const TypeInfo *remote = ctx.read_any_type_info(ctx.error());"
)
lines.append(" if (FORY_PREDICT_FALSE(ctx.has_error())) {")
lines.append(" return;")
lines.append(" }")
Expand Down Expand Up @@ -1391,7 +1399,7 @@ def generate_union_serializer(
lines.append(" return default_value();")
lines.append(" }")
lines.append(
" const TypeInfo *type_info = ctx.read_any_typeinfo(ctx.error());"
" const TypeInfo *type_info = ctx.read_any_type_info(ctx.error());"
)
lines.append(" if (FORY_PREDICT_FALSE(ctx.has_error())) {")
lines.append(" return default_value();")
Expand Down Expand Up @@ -1858,7 +1866,7 @@ def generate_enum_registration(
code_name = self.get_qualified_type_name(enum.name, parent_stack)
type_name = self.get_registration_type_name(enum.name, parent_stack)

if enum.type_id is not None:
if self.should_register_by_id(enum):
lines.append(f" fory.register_enum<{code_name}>({enum.type_id});")
else:
ns = self.package or "default"
Expand Down Expand Up @@ -1889,7 +1897,7 @@ def generate_message_registration(
)

# Register this message
if message.type_id is not None:
if self.should_register_by_id(message):
lines.append(f" fory.register_struct<{code_name}>({message.type_id});")
else:
ns = self.package or "default"
Expand All @@ -1904,7 +1912,7 @@ def generate_union_registration(
code_name = self.get_qualified_type_name(union.name, parent_stack)
type_name = self.get_registration_type_name(union.name, parent_stack)

if union.type_id is not None:
if self.should_register_by_id(union):
lines.append(f" fory.register_union<{code_name}>({union.type_id});")
else:
ns = self.package or "default"
Expand Down
Loading
Loading