Skip to content

Commit

Permalink
[TS] Add support for fixed length arrays on Typescript (google#5864) (g…
Browse files Browse the repository at this point in the history
…oogle#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 <mehmet.baker@zerodensity.tv>
Signed-off-by: Bulent Vural <bulent.vural@zerodensity.tv>
  • Loading branch information
bulentv and mehmetb committed Oct 18, 2022
1 parent d243b90 commit 68027ab
Show file tree
Hide file tree
Showing 6 changed files with 500 additions and 14 deletions.
313 changes: 302 additions & 11 deletions src/idl_gen_ts.cpp
Expand Up @@ -406,6 +406,21 @@ class TsGenerator : public BaseGenerator {
// return the bigint value directly since typescript does not support
// enums with bigint backing types.
switch (value.type.base_type) {
case BASE_TYPE_ARRAY: {
std::string ret = "[";
for (auto i = 0; i < value.type.fixed_length; ++i) {
ret +=
AddImport(imports, *value.type.enum_def, *value.type.enum_def)
.name +
"." +
namer_.Variant(
*value.type.enum_def->FindByValue(value.constant));
if (i < value.type.fixed_length - 1) { ret += ", "; }
}
ret += "]";
return ret;
break;
}
case BASE_TYPE_LONG:
case BASE_TYPE_ULONG: {
return "BigInt('" + value.constant + "')";
Expand All @@ -432,6 +447,7 @@ class TsGenerator : public BaseGenerator {
return "null";
}

case BASE_TYPE_ARRAY:
case BASE_TYPE_VECTOR: return "[]";

case BASE_TYPE_LONG:
Expand Down Expand Up @@ -464,6 +480,22 @@ class TsGenerator : public BaseGenerator {
case BASE_TYPE_BOOL: return allowNull ? "boolean|null" : "boolean";
case BASE_TYPE_LONG:
case BASE_TYPE_ULONG: return allowNull ? "bigint|null" : "bigint";
case BASE_TYPE_ARRAY: {
if (type.element == BASE_TYPE_LONG || type.element == BASE_TYPE_ULONG) {
return allowNull ? "bigint[]|null" : "bigint[]";
}
if (type.element != BASE_TYPE_STRUCT) {
return allowNull ? "number[]|null" : "number[]";
}

std::string name = "any";

if (parser_.opts.generate_object_based_api) {
name += "|" + GetTypeName(*type.struct_def, /*object_api =*/true);
}

return allowNull ? " (" + name + ")[] | null" : name;
}
default:
if (IsScalar(type.base_type)) {
if (type.enum_def) {
Expand Down Expand Up @@ -537,11 +569,78 @@ class TsGenerator : public BaseGenerator {
// don't clash, and to make it obvious these arguments are constructing
// a nested struct, prefix the name with the field name.
GenStructBody(*field.value.type.struct_def, body,
nameprefix + field.name + "_");
nameprefix.length() ? nameprefix + "_" + field.name : field.name);
} else {
*body += " builder.write" + GenWriteMethod(field.value.type) + "(";
if (field.value.type.base_type == BASE_TYPE_BOOL) { *body += "+"; }
*body += nameprefix + field.name + ");\n";
auto element_type = field.value.type.element;

if (field.value.type.base_type == BASE_TYPE_ARRAY) {
switch (field.value.type.element) {
case BASE_TYPE_STRUCT: {
std::string str_last_item_idx =
NumToString(field.value.type.fixed_length - 1);
*body += "\n for (let i = " + str_last_item_idx + "; i >= 0; --i" + ") {\n";

std::string fname = nameprefix.length() ? nameprefix + "_" + field.name : field.name;

*body += " const item = " + fname + "?.[i];\n\n";

if (parser_.opts.generate_object_based_api) {
*body += " if (item instanceof " + GetTypeName(*field.value.type.struct_def, /*object_api =*/true) + ") {\n";
*body += " item.pack(builder);\n";
*body += " continue;\n";
*body += " }\n\n";
}

std::string class_name = GetPrefixedName(*field.value.type.struct_def);
std::string pack_func_create_call =
class_name + ".create" + class_name + "(builder,\n";
pack_func_create_call +=
" " + GenStructMemberValueTS(*field.value.type.struct_def,
"item", ",\n ", false) +
"\n ";
*body += " " + pack_func_create_call;
*body += " );\n }\n\n";

break;
}
default: {
std::string str_last_item_idx =
NumToString(field.value.type.fixed_length - 1);
std::string fname = nameprefix.length() ? nameprefix + "_" + field.name : field.name;

*body += "\n for (let i = " + str_last_item_idx + "; i >= 0; --i) {\n";
*body += " builder.write";
*body +=
GenWriteMethod((flatbuffers::Type)field.value.type.element);
*body += "(";
*body += element_type == BASE_TYPE_BOOL ? "+" : "";

if (element_type == BASE_TYPE_LONG ||
element_type == BASE_TYPE_ULONG) {
*body += "BigInt(" + fname + "?.[i] ?? 0));\n";
} else {
*body += "(" + fname + "?.[i] ?? 0));\n\n";
}
*body += " }\n\n";
break;
}
}
} else {
std::string fname = nameprefix.length() ? nameprefix + "_" + field.name : field.name;

*body += " builder.write" +
GenWriteMethod(field.value.type) + "(";
if (field.value.type.base_type == BASE_TYPE_BOOL) {
*body += "Number(Boolean(" + fname + ")));\n";
continue;
} else if (field.value.type.base_type == BASE_TYPE_LONG ||
field.value.type.base_type == BASE_TYPE_ULONG) {
*body += "BigInt(" + fname + " ?? 0));\n";
continue;
}

*body += fname + ");\n";
}
}
}
}
Expand Down Expand Up @@ -916,7 +1015,7 @@ class TsGenerator : public BaseGenerator {
const auto conversion_function = GenUnionListConvFuncName(enum_def);

ret = "(() => {\n";
ret += " const ret = [];\n";
ret += " const ret: (" + GenObjApiUnionTypeTS(imports, *union_type.struct_def, parser_.opts, *union_type.enum_def) + ")[] = [];\n";
ret += " for(let targetEnumIndex = 0; targetEnumIndex < this." +
namer_.Method(field_name, "TypeLength") + "()" +
"; "
Expand Down Expand Up @@ -973,6 +1072,11 @@ class TsGenerator : public BaseGenerator {
std::string nullValue = "0";
if (field.value.type.base_type == BASE_TYPE_BOOL) {
nullValue = "false";
} else if (field.value.type.base_type == BASE_TYPE_LONG ||
field.value.type.base_type == BASE_TYPE_ULONG) {
nullValue = "BigInt(0)";
} else if (field.value.type.base_type == BASE_TYPE_ARRAY) {
nullValue = "[]";
}
ret += "(" + curr_member_accessor + " ?? " + nullValue + ")";
} else {
Expand Down Expand Up @@ -1091,6 +1195,95 @@ class TsGenerator : public BaseGenerator {
break;
}

case BASE_TYPE_ARRAY: {
auto vectortype = field.value.type.VectorType();
auto vectortypename =
GenTypeName(imports, struct_def, vectortype, false);
is_vector = true;

field_type = "(";

switch (vectortype.base_type) {
case BASE_TYPE_STRUCT: {
const auto &sd = *field.value.type.struct_def;
const auto field_type_name =
GetTypeName(sd, /*object_api=*/true);
field_type += field_type_name;
field_type += ")[]";

field_val = GenBBAccess() + ".createObjList<" + vectortypename +
", " + field_type_name + ">(" +
field_binded_method + ", " +
NumToString(field.value.type.fixed_length) + ")";

if (sd.fixed) {
field_offset_decl =
"builder.createStructOffsetList(this." + field_field +
", " + AddImport(imports, struct_def, struct_def).name +
"." + namer_.Method("start", field, "Vector") + ")";
} else {
field_offset_decl =
AddImport(imports, struct_def, struct_def).name + "." +
namer_.Method("create", field, "Vector") +
"(builder, builder.createObjectOffsetList(" + "this." +
field_field + "))";
}

break;
}

case BASE_TYPE_STRING: {
field_type += "string)[]";
field_val = GenBBAccess() + ".createScalarList<string>(" +
field_binded_method + ", this." +
namer_.Field(field, "Length") + "())";
field_offset_decl =
AddImport(imports, struct_def, struct_def).name + "." +
namer_.Method("create", field, "Vector") +
"(builder, builder.createObjectOffsetList(" + "this." +
namer_.Field(field) + "))";
break;
}

case BASE_TYPE_UNION: {
field_type += GenObjApiUnionTypeTS(
imports, struct_def, parser.opts, *(vectortype.enum_def));
field_type += ")[]";
field_val = GenUnionValTS(imports, struct_def, field_method,
vectortype, true);

field_offset_decl =
AddImport(imports, struct_def, struct_def).name + "." +
namer_.Method("create", field, "Vector") +
"(builder, builder.createObjectOffsetList(" + "this." +
namer_.Field(field) + "))";

break;
}
default: {
if (vectortype.enum_def) {
field_type += GenTypeName(imports, struct_def, vectortype,
false, HasNullDefault(field));
} else {
field_type += vectortypename;
}
field_type += ")[]";
field_val = GenBBAccess() + ".createScalarList<" +
vectortypename + ">(" + field_binded_method + ", " +
NumToString(field.value.type.fixed_length) + ")";

field_offset_decl =
AddImport(imports, struct_def, struct_def).name + "." +
namer_.Method("create", field, "Vector") +
"(builder, this." + field_field + ")";

break;
}
}

break;
}

case BASE_TYPE_VECTOR: {
auto vectortype = field.value.type.VectorType();
auto vectortypename =
Expand Down Expand Up @@ -1344,9 +1537,16 @@ class TsGenerator : public BaseGenerator {
it != struct_def.fields.vec.end(); ++it) {
auto &field = **it;
if (field.deprecated) continue;
auto offset_prefix =
" const offset = " + GenBBAccess() + ".__offset(this.bb_pos, " +
NumToString(field.value.offset) + ");\n return offset ? ";
std::string offset_prefix = "";

if (field.value.type.base_type == BASE_TYPE_ARRAY) {
offset_prefix = " return ";
} else {
offset_prefix = " const offset = " + GenBBAccess() +
".__offset(this.bb_pos, " +
NumToString(field.value.offset) + ");\n";
offset_prefix += " return offset ? ";
}

// Emit a scalar field
const auto is_string = IsString(field.value.type);
Expand Down Expand Up @@ -1386,9 +1586,11 @@ class TsGenerator : public BaseGenerator {
} else {
std::string index = "this.bb_pos + offset";
if (is_string) { index += ", optionalEncoding"; }
code += offset_prefix +
GenGetter(field.value.type, "(" + index + ")") + " : " +
GenDefaultValue(field, imports);
code +=
offset_prefix + GenGetter(field.value.type, "(" + index + ")");
if (field.value.type.base_type != BASE_TYPE_ARRAY) {
code += " : " + GenDefaultValue(field, imports);
}
code += ";\n";
}
}
Expand Down Expand Up @@ -1421,6 +1623,95 @@ class TsGenerator : public BaseGenerator {
break;
}

case BASE_TYPE_ARRAY: {
auto vectortype = field.value.type.VectorType();
auto vectortypename =
GenTypeName(imports, struct_def, vectortype, false);
auto inline_size = InlineSize(vectortype);
auto index = "this.bb_pos + " + NumToString(field.value.offset) +
" + index" + MaybeScale(inline_size);
std::string ret_type;
bool is_union = false;
switch (vectortype.base_type) {
case BASE_TYPE_STRUCT: ret_type = vectortypename; break;
case BASE_TYPE_STRING: ret_type = vectortypename; break;
case BASE_TYPE_UNION:
ret_type = "?flatbuffers.Table";
is_union = true;
break;
default: ret_type = vectortypename;
}
GenDocComment(field.doc_comment, code_ptr);
std::string prefix = namer_.Method(field);
// TODO: make it work without any
// if (is_union) { prefix += "<T extends flatbuffers.Table>"; }
if (is_union) { prefix += ""; }
prefix += "(index: number";
if (is_union) {
const auto union_type =
GenUnionGenericTypeTS(*(field.value.type.enum_def));

vectortypename = union_type;
code += prefix + ", obj:" + union_type;
} else if (vectortype.base_type == BASE_TYPE_STRUCT) {
code += prefix + ", obj?:" + vectortypename;
} else if (IsString(vectortype)) {
code += prefix + "):string\n";
code += prefix + ",optionalEncoding:flatbuffers.Encoding" +
"):" + vectortypename + "\n";
code += prefix + ",optionalEncoding?:any";
} else {
code += prefix;
}
code += "):" + vectortypename + "|null {\n";

if (vectortype.base_type == BASE_TYPE_STRUCT) {
code += offset_prefix + "(obj || " +
GenerateNewExpression(vectortypename);
code += ").__init(";
code += vectortype.struct_def->fixed
? index
: GenBBAccess() + ".__indirect(" + index + ")";
code += ", " + GenBBAccess() + ")";
} else {
if (is_union) {
index = "obj, " + index;
} else if (IsString(vectortype)) {
index += ", optionalEncoding";
}
code += offset_prefix + GenGetter(vectortype, "(" + index + ")");
}

switch (field.value.type.base_type) {
case BASE_TYPE_ARRAY: {
break;
}
case BASE_TYPE_BOOL: {
code += " : false";
break;
}
case BASE_TYPE_LONG:
case BASE_TYPE_ULONG: {
code += " : BigInt(0)";
break;
}
default: {
if (IsScalar(field.value.type.element)) {
if (field.value.type.enum_def) {
code += field.value.constant;
} else {
code += " : 0";
}
} else {
code += ": null";
}
break;
}
}
code += ";\n";
break;
}

case BASE_TYPE_VECTOR: {
auto vectortype = field.value.type.VectorType();
auto vectortypename =
Expand Down
2 changes: 1 addition & 1 deletion src/idl_parser.cpp
Expand Up @@ -2578,7 +2578,7 @@ bool Parser::SupportsAdvancedArrayFeatures() const {
return (opts.lang_to_generate &
~(IDLOptions::kCpp | IDLOptions::kPython | IDLOptions::kJava |
IDLOptions::kCSharp | IDLOptions::kJsonSchema | IDLOptions::kJson |
IDLOptions::kBinary | IDLOptions::kRust)) == 0;
IDLOptions::kBinary | IDLOptions::kRust | IDLOptions::kTs)) == 0;
}

Namespace *Parser::UniqueNamespace(Namespace *ns) {
Expand Down

0 comments on commit 68027ab

Please sign in to comment.