Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions compiler/fory_compiler/generators/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class CppGenerator(BaseGenerator):
PrimitiveKind.FLOAT64: "double",
PrimitiveKind.STRING: "std::string",
PrimitiveKind.BYTES: "std::vector<uint8_t>",
PrimitiveKind.DECIMAL: "fory::serialization::Decimal",
PrimitiveKind.DATE: "fory::serialization::Date",
PrimitiveKind.TIMESTAMP: "fory::serialization::Timestamp",
PrimitiveKind.ANY: "std::any",
Expand Down Expand Up @@ -1779,6 +1780,8 @@ def collect_includes(
includes.add("<string>")
elif field_type.kind == PrimitiveKind.BYTES:
includes.add("<vector>")
elif field_type.kind == PrimitiveKind.DECIMAL:
includes.add('"fory/serialization/decimal_serializers.h"')
elif field_type.kind in (PrimitiveKind.DATE, PrimitiveKind.TIMESTAMP):
includes.add('"fory/serialization/temporal_serializers.h"')
elif field_type.kind == PrimitiveKind.ANY:
Expand Down
2 changes: 2 additions & 0 deletions compiler/fory_compiler/generators/dart.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class DartGenerator(BaseGenerator):
PrimitiveKind.BYTES: "Uint8List",
PrimitiveKind.DATE: "LocalDate",
PrimitiveKind.TIMESTAMP: "Timestamp",
PrimitiveKind.DECIMAL: "Decimal",
PrimitiveKind.ANY: "Object?",
}

Expand Down Expand Up @@ -610,6 +611,7 @@ def _default_value_for_type(
PrimitiveKind.BYTES: "Uint8List(0)",
PrimitiveKind.DATE: "const LocalDate(1970, 1, 1)",
PrimitiveKind.TIMESTAMP: "Timestamp(0, 0)",
PrimitiveKind.DECIMAL: "const Decimal.zero()",
PrimitiveKind.ANY: "null",
}[t.kind]
if isinstance(t, ListType):
Expand Down
2 changes: 2 additions & 0 deletions compiler/fory_compiler/generators/go.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ def message_has_unions(self, message: Message) -> bool:
PrimitiveKind.BYTES: "[]byte",
PrimitiveKind.DATE: "fory.Date",
PrimitiveKind.TIMESTAMP: "time.Time",
PrimitiveKind.DECIMAL: "fory.Decimal",
PrimitiveKind.ANY: "any",
}

Expand Down Expand Up @@ -666,6 +667,7 @@ def get_union_case_type_id_expr(
PrimitiveKind.BYTES: "fory.BINARY",
PrimitiveKind.DATE: "fory.DATE",
PrimitiveKind.TIMESTAMP: "fory.TIMESTAMP",
PrimitiveKind.DECIMAL: "fory.DECIMAL",
PrimitiveKind.ANY: "fory.UNKNOWN",
}
return primitive_type_ids.get(kind, "fory.UNKNOWN")
Expand Down
50 changes: 38 additions & 12 deletions compiler/fory_compiler/generators/javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class JavaScriptGenerator(BaseGenerator):
PrimitiveKind.DATE: "Date",
PrimitiveKind.TIMESTAMP: "Date",
PrimitiveKind.DURATION: "number",
# DECIMAL is not supported by the JS runtime; rejected in _field_type_expr.
PrimitiveKind.DECIMAL: "Decimal",
PrimitiveKind.ANY: "any",
}

Expand Down Expand Up @@ -177,7 +177,7 @@ class JavaScriptGenerator(BaseGenerator):
PrimitiveKind.DATE: "Type.date()",
PrimitiveKind.TIMESTAMP: "Type.timestamp()",
PrimitiveKind.DURATION: "Type.duration()",
# DECIMAL is not yet supported by the JS runtime; omitted intentionally.
PrimitiveKind.DECIMAL: "Type.decimal()",
PrimitiveKind.ANY: "Type.any()",
}

Expand Down Expand Up @@ -531,6 +531,9 @@ def generate_imports(self) -> List[str]:
lines: List[str] = []
imported_regs = self._collect_imported_registrations()

if self._schema_uses_primitive_kind(PrimitiveKind.DECIMAL):
lines.append("import { Decimal } from '@apache-fory/core';")

# Collect all imported types used in this schema
imported_types_by_module: Dict[str, Set[str]] = {}

Expand Down Expand Up @@ -577,6 +580,39 @@ def generate_imports(self) -> List[str]:

return lines

def _schema_uses_primitive_kind(self, primitive_kind: PrimitiveKind) -> bool:
def uses_field_type(field_type: FieldType) -> bool:
if isinstance(field_type, PrimitiveType):
return field_type.kind == primitive_kind
if isinstance(field_type, NamedType):
return field_type.name.lower() == primitive_kind.value
if isinstance(field_type, ListType):
return uses_field_type(field_type.element_type)
if isinstance(field_type, MapType):
return uses_field_type(field_type.key_type) or uses_field_type(
field_type.value_type
)
return False

def uses_message(message: Message) -> bool:
for field in message.fields:
if uses_field_type(field.field_type):
return True
for nested_union in message.nested_unions:
if any(
uses_field_type(field.field_type) for field in nested_union.fields
):
return True
return any(uses_message(nested) for nested in message.nested_messages)

if any(
uses_field_type(field.field_type)
for union in self.schema.unions
for field in union.fields
):
return True
return any(uses_message(message) for message in self.schema.messages)

def generate(self) -> List[GeneratedFile]:
"""Generate JavaScript files for the schema."""
return [self.generate_file()]
Expand Down Expand Up @@ -806,11 +842,6 @@ def _field_type_expr(
"""Return the Fory JS runtime ``Type.xxx()`` expression for a field type."""
parent_stack = parent_stack or []
if isinstance(field_type, PrimitiveType):
if field_type.kind == PrimitiveKind.DECIMAL:
raise ValueError(
"decimal is not supported by the JavaScript runtime. "
"Use a different type or wait for runtime support."
)
expr = self.PRIMITIVE_RUNTIME_MAP.get(field_type.kind)
if expr is None:
return "Type.any()"
Expand All @@ -826,11 +857,6 @@ def _field_type_expr(
return self.PRIMITIVE_RUNTIME_MAP[shorthand_map[lower]]
for pk in PrimitiveKind:
if pk.value == lower:
if pk == PrimitiveKind.DECIMAL:
raise ValueError(
"decimal is not supported by the JavaScript runtime. "
"Use a different type or wait for runtime support."
)
expr = self.PRIMITIVE_RUNTIME_MAP.get(pk)
if expr is None:
raise ValueError(
Expand Down
4 changes: 4 additions & 0 deletions compiler/fory_compiler/generators/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class PythonGenerator(BaseGenerator):
PrimitiveKind.BYTES: "bytes",
PrimitiveKind.DATE: "datetime.date",
PrimitiveKind.TIMESTAMP: "datetime.datetime",
PrimitiveKind.DECIMAL: "decimal.Decimal",
PrimitiveKind.ANY: "Any",
}

Expand Down Expand Up @@ -138,6 +139,7 @@ class PythonGenerator(BaseGenerator):
PrimitiveKind.BYTES: 'b""',
PrimitiveKind.DATE: "None",
PrimitiveKind.TIMESTAMP: "None",
PrimitiveKind.DECIMAL: 'decimal.Decimal("0")',
PrimitiveKind.ANY: "None",
}

Expand Down Expand Up @@ -962,6 +964,8 @@ def collect_imports(
if isinstance(field_type, PrimitiveType):
if field_type.kind in (PrimitiveKind.DATE, PrimitiveKind.TIMESTAMP):
imports.add("import datetime")
elif field_type.kind == PrimitiveKind.DECIMAL:
imports.add("import decimal")
elif field_type.kind == PrimitiveKind.ANY:
imports.add("from typing import Any")

Expand Down
1 change: 1 addition & 0 deletions compiler/fory_compiler/generators/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class RustGenerator(BaseGenerator):
PrimitiveKind.BYTES: "Vec<u8>",
PrimitiveKind.DATE: "chrono::NaiveDate",
PrimitiveKind.TIMESTAMP: "chrono::NaiveDateTime",
PrimitiveKind.DECIMAL: "fory::Decimal",
PrimitiveKind.ANY: "Box<dyn Any>",
}

Expand Down
18 changes: 10 additions & 8 deletions compiler/fory_compiler/generators/swift.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ class SwiftGenerator(BaseGenerator):
PrimitiveKind.FLOAT64: "Double",
PrimitiveKind.STRING: "String",
PrimitiveKind.BYTES: "Data",
PrimitiveKind.DATE: "ForyDate",
PrimitiveKind.TIMESTAMP: "ForyTimestamp",
PrimitiveKind.DATE: "LocalDate",
PrimitiveKind.TIMESTAMP: "Date",
PrimitiveKind.DECIMAL: "Decimal",
PrimitiveKind.ANY: "Any",
}

Expand Down Expand Up @@ -963,12 +964,13 @@ def generate_message_fields(

encoding = self.field_encoding_argument(field)
field_id = self.message_field_id_argument(field)
if field_id is not None and encoding is not None:
lines.append(f"{ind}@ForyField(id: {field_id}, encoding: {encoding})")
elif field_id is not None:
lines.append(f"{ind}@ForyField(id: {field_id})")
elif encoding is not None:
lines.append(f"{ind}@ForyField(encoding: {encoding})")
attr_parts: List[str] = []
if field_id is not None:
attr_parts.append(f"id: {field_id}")
if encoding is not None:
attr_parts.append(f"encoding: {encoding}")
if attr_parts:
lines.append(f"{ind}@ForyField({', '.join(attr_parts)})")

field_type = self.field_swift_type(field, lineage)
weak_prefix = "weak " if self.is_weak_ref_field(field) else ""
Expand Down
20 changes: 20 additions & 0 deletions compiler/fory_compiler/tests/test_dart_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,26 @@ def test_dart_generator_uses_typed_lists_for_non_nullable_primitive_lists():
assert "factory ValueUnion.values(Uint32List value)" in file.content


def test_dart_generator_supports_decimal_fields_and_unions():
file = generate_dart(
"""
package demo;

message Money [id=100] {
decimal amount = 1;
}

union ValueUnion [id=101] {
decimal amount = 1;
Money money = 2;
}
"""
)

assert "Decimal amount = const Decimal.zero();" in file.content
assert "factory ValueUnion.amount(Decimal value)" in file.content


def test_dart_generator_emits_container_ref_annotations_for_builder_metadata():
file = generate_dart(
"""
Expand Down
25 changes: 25 additions & 0 deletions compiler/fory_compiler/tests/test_generated_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,31 @@ def test_java_repeated_float16_generation_uses_float16_list():
assert "private Float16List vals;" in java_output


def test_cpp_generator_supports_decimal_fields_and_unions():
schema = parse_fdl(
dedent(
"""
package gen;

message Money {
decimal amount = 1;
}

union Value {
decimal amount = 1;
Money money = 2;
}
"""
)
)

cpp_output = render_files(generate_files(schema, CppGenerator))
assert '#include "fory/serialization/decimal_serializers.h"' in cpp_output
assert "const fory::serialization::Decimal& amount() const" in cpp_output
assert "std::variant<fory::serialization::Decimal, Money> value_" in cpp_output
assert "(fory::serialization::Decimal, amount, fory::F(1))" in cpp_output


def test_java_enum_generation_uses_fory_enum_ids():
schema = parse_fdl(
dedent(
Expand Down
23 changes: 23 additions & 0 deletions compiler/fory_compiler/tests/test_javascript_codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,29 @@ def test_javascript_collection_types():
assert "config: Map<string, number>;" in output


def test_javascript_decimal_generation_uses_runtime_decimal_type():
source = dedent(
"""
package example;

message Money [id=100] {
decimal amount = 1;
}

union Value [id=101] {
decimal amount = 1;
Money money = 2;
}
"""
)
output = generate_javascript(source)

assert "import { Decimal } from '@apache-fory/core';" in output
assert "amount: Decimal;" in output
assert "{ case: ValueCase.AMOUNT; value: Decimal }" in output
assert "amount: Type.decimal()" in output


def test_javascript_map_key_fallback_to_map():
"""Test that map keys not valid for Record use Map<K, V> instead."""
source = dedent(
Expand Down
41 changes: 41 additions & 0 deletions compiler/fory_compiler/tests/test_swift_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,47 @@ def test_swift_generator_emits_tagged_union_case_ids():
assert "fory.register(Demo.Animal.self, id: 101)" in content


def test_swift_generator_supports_decimal_fields_and_unions():
source = """
package demo;

message Money [id=100] {
decimal amount = 1;
}

union Value [id=101] {
decimal amount = 1;
Money money = 2;
}
"""
content = generate_swift(source)
assert "public var amount: Decimal = Decimal.foryDefault()" in content
assert "case amount(Decimal)" in content


def test_swift_generator_maps_date_to_local_date():
source = """
package demo;

message Temporal [id=100] {
date day = 1;
timestamp instant = 2;
}

union Value [id=101] {
date day = 1;
timestamp instant = 2;
}
"""
content = generate_swift(source)
assert "@ForyField(id: 1)" in content
assert "@ForyField(id: 2)" in content
assert "public var day: LocalDate = LocalDate.foryDefault()" in content
assert "public var instant: Date = Date.foryDefault()" in content
assert "case day(LocalDate)" in content
assert "case instant(Date)" in content


def test_swift_generator_uses_class_for_ref_targets_and_weak_fields():
source = """
package tree;
Expand Down
1 change: 1 addition & 0 deletions cpp/fory/serialization/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ cc_library(
"collection_serializer.h",
"config.h",
"context.h",
"decimal_serializers.h",
"enum_serializer.h",
"fory.h",
"map_serializer.h",
Expand Down
Loading
Loading