Skip to content

Commit ddd29d9

Browse files
bulentvmehmetb
andcommitted
[TS] Add support for fixed length arrays on Typescript (#5864) (#7021)
* Typescript / Javascript don't have fixed arrays but it is important to support these languages for compatibility. * Generated TS code checks the length of the given array and do truncating / padding to conform to the schema. * Supports the both standard API and Object Based API. * Added a test. Co-authored-by: Mehmet Baker <[email protected]> Signed-off-by: Bulent Vural <[email protected]>
1 parent 04cd037 commit ddd29d9

File tree

6 files changed

+500
-14
lines changed

6 files changed

+500
-14
lines changed

src/idl_gen_ts.cpp

Lines changed: 302 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,21 @@ class TsGenerator : public BaseGenerator {
406406
// return the bigint value directly since typescript does not support
407407
// enums with bigint backing types.
408408
switch (value.type.base_type) {
409+
case BASE_TYPE_ARRAY: {
410+
std::string ret = "[";
411+
for (auto i = 0; i < value.type.fixed_length; ++i) {
412+
ret +=
413+
AddImport(imports, *value.type.enum_def, *value.type.enum_def)
414+
.name +
415+
"." +
416+
namer_.Variant(
417+
*value.type.enum_def->FindByValue(value.constant));
418+
if (i < value.type.fixed_length - 1) { ret += ", "; }
419+
}
420+
ret += "]";
421+
return ret;
422+
break;
423+
}
409424
case BASE_TYPE_LONG:
410425
case BASE_TYPE_ULONG: {
411426
return "BigInt('" + value.constant + "')";
@@ -432,6 +447,7 @@ class TsGenerator : public BaseGenerator {
432447
return "null";
433448
}
434449

450+
case BASE_TYPE_ARRAY:
435451
case BASE_TYPE_VECTOR: return "[]";
436452

437453
case BASE_TYPE_LONG:
@@ -464,6 +480,22 @@ class TsGenerator : public BaseGenerator {
464480
case BASE_TYPE_BOOL: return allowNull ? "boolean|null" : "boolean";
465481
case BASE_TYPE_LONG:
466482
case BASE_TYPE_ULONG: return allowNull ? "bigint|null" : "bigint";
483+
case BASE_TYPE_ARRAY: {
484+
if (type.element == BASE_TYPE_LONG || type.element == BASE_TYPE_ULONG) {
485+
return allowNull ? "bigint[]|null" : "bigint[]";
486+
}
487+
if (type.element != BASE_TYPE_STRUCT) {
488+
return allowNull ? "number[]|null" : "number[]";
489+
}
490+
491+
std::string name = "any";
492+
493+
if (parser_.opts.generate_object_based_api) {
494+
name += "|" + GetTypeName(*type.struct_def, /*object_api =*/true);
495+
}
496+
497+
return allowNull ? " (" + name + ")[] | null" : name;
498+
}
467499
default:
468500
if (IsScalar(type.base_type)) {
469501
if (type.enum_def) {
@@ -537,11 +569,78 @@ class TsGenerator : public BaseGenerator {
537569
// don't clash, and to make it obvious these arguments are constructing
538570
// a nested struct, prefix the name with the field name.
539571
GenStructBody(*field.value.type.struct_def, body,
540-
nameprefix + field.name + "_");
572+
nameprefix.length() ? nameprefix + "_" + field.name : field.name);
541573
} else {
542-
*body += " builder.write" + GenWriteMethod(field.value.type) + "(";
543-
if (field.value.type.base_type == BASE_TYPE_BOOL) { *body += "+"; }
544-
*body += nameprefix + field.name + ");\n";
574+
auto element_type = field.value.type.element;
575+
576+
if (field.value.type.base_type == BASE_TYPE_ARRAY) {
577+
switch (field.value.type.element) {
578+
case BASE_TYPE_STRUCT: {
579+
std::string str_last_item_idx =
580+
NumToString(field.value.type.fixed_length - 1);
581+
*body += "\n for (let i = " + str_last_item_idx + "; i >= 0; --i" + ") {\n";
582+
583+
std::string fname = nameprefix.length() ? nameprefix + "_" + field.name : field.name;
584+
585+
*body += " const item = " + fname + "?.[i];\n\n";
586+
587+
if (parser_.opts.generate_object_based_api) {
588+
*body += " if (item instanceof " + GetTypeName(*field.value.type.struct_def, /*object_api =*/true) + ") {\n";
589+
*body += " item.pack(builder);\n";
590+
*body += " continue;\n";
591+
*body += " }\n\n";
592+
}
593+
594+
std::string class_name = GetPrefixedName(*field.value.type.struct_def);
595+
std::string pack_func_create_call =
596+
class_name + ".create" + class_name + "(builder,\n";
597+
pack_func_create_call +=
598+
" " + GenStructMemberValueTS(*field.value.type.struct_def,
599+
"item", ",\n ", false) +
600+
"\n ";
601+
*body += " " + pack_func_create_call;
602+
*body += " );\n }\n\n";
603+
604+
break;
605+
}
606+
default: {
607+
std::string str_last_item_idx =
608+
NumToString(field.value.type.fixed_length - 1);
609+
std::string fname = nameprefix.length() ? nameprefix + "_" + field.name : field.name;
610+
611+
*body += "\n for (let i = " + str_last_item_idx + "; i >= 0; --i) {\n";
612+
*body += " builder.write";
613+
*body +=
614+
GenWriteMethod((flatbuffers::Type)field.value.type.element);
615+
*body += "(";
616+
*body += element_type == BASE_TYPE_BOOL ? "+" : "";
617+
618+
if (element_type == BASE_TYPE_LONG ||
619+
element_type == BASE_TYPE_ULONG) {
620+
*body += "BigInt(" + fname + "?.[i] ?? 0));\n";
621+
} else {
622+
*body += "(" + fname + "?.[i] ?? 0));\n\n";
623+
}
624+
*body += " }\n\n";
625+
break;
626+
}
627+
}
628+
} else {
629+
std::string fname = nameprefix.length() ? nameprefix + "_" + field.name : field.name;
630+
631+
*body += " builder.write" +
632+
GenWriteMethod(field.value.type) + "(";
633+
if (field.value.type.base_type == BASE_TYPE_BOOL) {
634+
*body += "Number(Boolean(" + fname + ")));\n";
635+
continue;
636+
} else if (field.value.type.base_type == BASE_TYPE_LONG ||
637+
field.value.type.base_type == BASE_TYPE_ULONG) {
638+
*body += "BigInt(" + fname + " ?? 0));\n";
639+
continue;
640+
}
641+
642+
*body += fname + ");\n";
643+
}
545644
}
546645
}
547646
}
@@ -916,7 +1015,7 @@ class TsGenerator : public BaseGenerator {
9161015
const auto conversion_function = GenUnionListConvFuncName(enum_def);
9171016

9181017
ret = "(() => {\n";
919-
ret += " const ret = [];\n";
1018+
ret += " const ret: (" + GenObjApiUnionTypeTS(imports, *union_type.struct_def, parser_.opts, *union_type.enum_def) + ")[] = [];\n";
9201019
ret += " for(let targetEnumIndex = 0; targetEnumIndex < this." +
9211020
namer_.Method(field_name, "TypeLength") + "()" +
9221021
"; "
@@ -973,6 +1072,11 @@ class TsGenerator : public BaseGenerator {
9731072
std::string nullValue = "0";
9741073
if (field.value.type.base_type == BASE_TYPE_BOOL) {
9751074
nullValue = "false";
1075+
} else if (field.value.type.base_type == BASE_TYPE_LONG ||
1076+
field.value.type.base_type == BASE_TYPE_ULONG) {
1077+
nullValue = "BigInt(0)";
1078+
} else if (field.value.type.base_type == BASE_TYPE_ARRAY) {
1079+
nullValue = "[]";
9761080
}
9771081
ret += "(" + curr_member_accessor + " ?? " + nullValue + ")";
9781082
} else {
@@ -1091,6 +1195,95 @@ class TsGenerator : public BaseGenerator {
10911195
break;
10921196
}
10931197

1198+
case BASE_TYPE_ARRAY: {
1199+
auto vectortype = field.value.type.VectorType();
1200+
auto vectortypename =
1201+
GenTypeName(imports, struct_def, vectortype, false);
1202+
is_vector = true;
1203+
1204+
field_type = "(";
1205+
1206+
switch (vectortype.base_type) {
1207+
case BASE_TYPE_STRUCT: {
1208+
const auto &sd = *field.value.type.struct_def;
1209+
const auto field_type_name =
1210+
GetTypeName(sd, /*object_api=*/true);
1211+
field_type += field_type_name;
1212+
field_type += ")[]";
1213+
1214+
field_val = GenBBAccess() + ".createObjList<" + vectortypename +
1215+
", " + field_type_name + ">(" +
1216+
field_binded_method + ", " +
1217+
NumToString(field.value.type.fixed_length) + ")";
1218+
1219+
if (sd.fixed) {
1220+
field_offset_decl =
1221+
"builder.createStructOffsetList(this." + field_field +
1222+
", " + AddImport(imports, struct_def, struct_def).name +
1223+
"." + namer_.Method("start", field, "Vector") + ")";
1224+
} else {
1225+
field_offset_decl =
1226+
AddImport(imports, struct_def, struct_def).name + "." +
1227+
namer_.Method("create", field, "Vector") +
1228+
"(builder, builder.createObjectOffsetList(" + "this." +
1229+
field_field + "))";
1230+
}
1231+
1232+
break;
1233+
}
1234+
1235+
case BASE_TYPE_STRING: {
1236+
field_type += "string)[]";
1237+
field_val = GenBBAccess() + ".createScalarList<string>(" +
1238+
field_binded_method + ", this." +
1239+
namer_.Field(field, "Length") + "())";
1240+
field_offset_decl =
1241+
AddImport(imports, struct_def, struct_def).name + "." +
1242+
namer_.Method("create", field, "Vector") +
1243+
"(builder, builder.createObjectOffsetList(" + "this." +
1244+
namer_.Field(field) + "))";
1245+
break;
1246+
}
1247+
1248+
case BASE_TYPE_UNION: {
1249+
field_type += GenObjApiUnionTypeTS(
1250+
imports, struct_def, parser.opts, *(vectortype.enum_def));
1251+
field_type += ")[]";
1252+
field_val = GenUnionValTS(imports, struct_def, field_method,
1253+
vectortype, true);
1254+
1255+
field_offset_decl =
1256+
AddImport(imports, struct_def, struct_def).name + "." +
1257+
namer_.Method("create", field, "Vector") +
1258+
"(builder, builder.createObjectOffsetList(" + "this." +
1259+
namer_.Field(field) + "))";
1260+
1261+
break;
1262+
}
1263+
default: {
1264+
if (vectortype.enum_def) {
1265+
field_type += GenTypeName(imports, struct_def, vectortype,
1266+
false, HasNullDefault(field));
1267+
} else {
1268+
field_type += vectortypename;
1269+
}
1270+
field_type += ")[]";
1271+
field_val = GenBBAccess() + ".createScalarList<" +
1272+
vectortypename + ">(" + field_binded_method + ", " +
1273+
NumToString(field.value.type.fixed_length) + ")";
1274+
1275+
field_offset_decl =
1276+
AddImport(imports, struct_def, struct_def).name + "." +
1277+
namer_.Method("create", field, "Vector") +
1278+
"(builder, this." + field_field + ")";
1279+
1280+
break;
1281+
}
1282+
}
1283+
1284+
break;
1285+
}
1286+
10941287
case BASE_TYPE_VECTOR: {
10951288
auto vectortype = field.value.type.VectorType();
10961289
auto vectortypename =
@@ -1344,9 +1537,16 @@ class TsGenerator : public BaseGenerator {
13441537
it != struct_def.fields.vec.end(); ++it) {
13451538
auto &field = **it;
13461539
if (field.deprecated) continue;
1347-
auto offset_prefix =
1348-
" const offset = " + GenBBAccess() + ".__offset(this.bb_pos, " +
1349-
NumToString(field.value.offset) + ");\n return offset ? ";
1540+
std::string offset_prefix = "";
1541+
1542+
if (field.value.type.base_type == BASE_TYPE_ARRAY) {
1543+
offset_prefix = " return ";
1544+
} else {
1545+
offset_prefix = " const offset = " + GenBBAccess() +
1546+
".__offset(this.bb_pos, " +
1547+
NumToString(field.value.offset) + ");\n";
1548+
offset_prefix += " return offset ? ";
1549+
}
13501550

13511551
// Emit a scalar field
13521552
const auto is_string = IsString(field.value.type);
@@ -1386,9 +1586,11 @@ class TsGenerator : public BaseGenerator {
13861586
} else {
13871587
std::string index = "this.bb_pos + offset";
13881588
if (is_string) { index += ", optionalEncoding"; }
1389-
code += offset_prefix +
1390-
GenGetter(field.value.type, "(" + index + ")") + " : " +
1391-
GenDefaultValue(field, imports);
1589+
code +=
1590+
offset_prefix + GenGetter(field.value.type, "(" + index + ")");
1591+
if (field.value.type.base_type != BASE_TYPE_ARRAY) {
1592+
code += " : " + GenDefaultValue(field, imports);
1593+
}
13921594
code += ";\n";
13931595
}
13941596
}
@@ -1421,6 +1623,95 @@ class TsGenerator : public BaseGenerator {
14211623
break;
14221624
}
14231625

1626+
case BASE_TYPE_ARRAY: {
1627+
auto vectortype = field.value.type.VectorType();
1628+
auto vectortypename =
1629+
GenTypeName(imports, struct_def, vectortype, false);
1630+
auto inline_size = InlineSize(vectortype);
1631+
auto index = "this.bb_pos + " + NumToString(field.value.offset) +
1632+
" + index" + MaybeScale(inline_size);
1633+
std::string ret_type;
1634+
bool is_union = false;
1635+
switch (vectortype.base_type) {
1636+
case BASE_TYPE_STRUCT: ret_type = vectortypename; break;
1637+
case BASE_TYPE_STRING: ret_type = vectortypename; break;
1638+
case BASE_TYPE_UNION:
1639+
ret_type = "?flatbuffers.Table";
1640+
is_union = true;
1641+
break;
1642+
default: ret_type = vectortypename;
1643+
}
1644+
GenDocComment(field.doc_comment, code_ptr);
1645+
std::string prefix = namer_.Method(field);
1646+
// TODO: make it work without any
1647+
// if (is_union) { prefix += "<T extends flatbuffers.Table>"; }
1648+
if (is_union) { prefix += ""; }
1649+
prefix += "(index: number";
1650+
if (is_union) {
1651+
const auto union_type =
1652+
GenUnionGenericTypeTS(*(field.value.type.enum_def));
1653+
1654+
vectortypename = union_type;
1655+
code += prefix + ", obj:" + union_type;
1656+
} else if (vectortype.base_type == BASE_TYPE_STRUCT) {
1657+
code += prefix + ", obj?:" + vectortypename;
1658+
} else if (IsString(vectortype)) {
1659+
code += prefix + "):string\n";
1660+
code += prefix + ",optionalEncoding:flatbuffers.Encoding" +
1661+
"):" + vectortypename + "\n";
1662+
code += prefix + ",optionalEncoding?:any";
1663+
} else {
1664+
code += prefix;
1665+
}
1666+
code += "):" + vectortypename + "|null {\n";
1667+
1668+
if (vectortype.base_type == BASE_TYPE_STRUCT) {
1669+
code += offset_prefix + "(obj || " +
1670+
GenerateNewExpression(vectortypename);
1671+
code += ").__init(";
1672+
code += vectortype.struct_def->fixed
1673+
? index
1674+
: GenBBAccess() + ".__indirect(" + index + ")";
1675+
code += ", " + GenBBAccess() + ")";
1676+
} else {
1677+
if (is_union) {
1678+
index = "obj, " + index;
1679+
} else if (IsString(vectortype)) {
1680+
index += ", optionalEncoding";
1681+
}
1682+
code += offset_prefix + GenGetter(vectortype, "(" + index + ")");
1683+
}
1684+
1685+
switch (field.value.type.base_type) {
1686+
case BASE_TYPE_ARRAY: {
1687+
break;
1688+
}
1689+
case BASE_TYPE_BOOL: {
1690+
code += " : false";
1691+
break;
1692+
}
1693+
case BASE_TYPE_LONG:
1694+
case BASE_TYPE_ULONG: {
1695+
code += " : BigInt(0)";
1696+
break;
1697+
}
1698+
default: {
1699+
if (IsScalar(field.value.type.element)) {
1700+
if (field.value.type.enum_def) {
1701+
code += field.value.constant;
1702+
} else {
1703+
code += " : 0";
1704+
}
1705+
} else {
1706+
code += ": null";
1707+
}
1708+
break;
1709+
}
1710+
}
1711+
code += ";\n";
1712+
break;
1713+
}
1714+
14241715
case BASE_TYPE_VECTOR: {
14251716
auto vectortype = field.value.type.VectorType();
14261717
auto vectortypename =

src/idl_parser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2581,7 +2581,7 @@ bool Parser::SupportsAdvancedArrayFeatures() const {
25812581
return (opts.lang_to_generate &
25822582
~(IDLOptions::kCpp | IDLOptions::kPython | IDLOptions::kJava |
25832583
IDLOptions::kCSharp | IDLOptions::kJsonSchema | IDLOptions::kJson |
2584-
IDLOptions::kBinary | IDLOptions::kRust)) == 0;
2584+
IDLOptions::kBinary | IDLOptions::kRust | IDLOptions::kTs)) == 0;
25852585
}
25862586

25872587
Namespace *Parser::UniqueNamespace(Namespace *ns) {

0 commit comments

Comments
 (0)