* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed 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.
*/
import * as idl from "../idl";
import { Language } from "../Language";
import {
BlockStatement,
BranchStatement,
ExpressionAssigner,
LanguageExpression,
LanguageStatement,
LanguageWriter,
PrintHint,
StringExpression,
NamedMethodSignature,
ProxyStatement,
ExpressionStatement,
MethodSignature
} from "./LanguageWriter";
import { NativeModuleType, RuntimeType } from "./common";
import { generatorConfiguration, generatorTypePrefix } from "../config"
import { getTransformer, LibraryInterface } from "../LibraryInterface";
import { capitalize, getExtractor, hashCodeFromString, isDefined, throwException, warn } from "../util";
import { UnionRuntimeTypeChecker } from "../peer-generation/unions";
import { CppConvertor, CppNameConvertor } from "./convertors/CppConvertors";
import { ReferenceResolver } from "../peer-generation/ReferenceResolver";
import { PrimitiveTypesInstance } from "../peer-generation/PrimitiveType";
import { PeerLibrary } from "../peer-generation/PeerLibrary";
import { LayoutNodeRole } from "../peer-generation/LayoutManager";
import { isInExternalModule } from "../peer-generation/modules";
import { isTopLevelConflicted } from "../peer-generation/ConflictingDeclarations";
import { ETSLanguageWriter } from "./writers/ETSLanguageWriter";
import { maybeRestoreGenerics } from "../transformers/GenericTransformer";
export function getSerializerName(library: LibraryInterface, language: Language, declaration:idl.IDLEntry) {
const qualifier = isTopLevelConflicted(library, language, declaration)
? "package.namespace.name" : "namespace.name"
return `${idl.getQualifiedName(declaration, qualifier).split('.').join('_')}_serializer`;
}
export interface ArgConvertor {
param: string
idlType: idl.IDLType
isScoped: boolean
useArray: boolean
runtimeTypes: RuntimeType[]
isOut?: true
convertorArg(param: string, writer: LanguageWriter): string
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement
holdResource(resourceName: string, holder:string, writer:LanguageWriter): void
interopType(): idl.IDLType
nativeType(): idl.IDLType
targetType(writer: LanguageWriter): string
isPointerType(): boolean
unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression|undefined
getMembers(): string[]
getObjectAccessor(languge: Language, value: string, args?: Record<string, string>, writer?: LanguageWriter): string
}
export abstract class BaseArgConvertor implements ArgConvertor {
protected constructor(
public idlType: idl.IDLType,
public runtimeTypes: RuntimeType[],
public isScoped: boolean,
public useArray: boolean,
public param: string
) { }
holdResource(_resourceName: string, _holder: string, _writer: LanguageWriter): void {}
nativeType(): idl.IDLType {
throw new Error("Define")
}
isPointerType(): boolean {
throw new Error("Define")
}
interopType(): idl.IDLType {
throw new Error("Define")
}
targetType(writer: LanguageWriter): string {
return writer.getNodeName(this.idlType)
}
abstract convertorArg(param: string, writer: LanguageWriter): string
abstract convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement
abstract convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement
unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression|undefined {
return undefined
}
getMembers(): string[] { return [] }
getObjectAccessor(language: Language, value: string, args?: Record<string, string>, writer?: LanguageWriter): string {
if (writer) return writer.getObjectAccessor(this, value, args)
return this.useArray && args?.index ? `${value}[${args.index}]` : value
}
protected discriminatorFromFields<T>(value: string,
writer: LanguageWriter,
uniqueFields: T[] | undefined,
nameAccessor: (field: T) => string,
optionalAccessor: (field: T) => boolean)
{
if (!uniqueFields || uniqueFields.length === 0) return undefined
const firstNonOptional = uniqueFields.find(it => !optionalAccessor(it))
const accessors = firstNonOptional ? [nameAccessor(firstNonOptional)] : uniqueFields.map(it => nameAccessor(it))
return writer.makeNaryOp("||", accessors.map(it => writer.makeHasOwnProperty(value + "!", it)))
}
}
export class BooleanConvertor extends BaseArgConvertor {
constructor(param: string) {
super(idl.IDLBooleanType, [RuntimeType.BOOLEAN], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return writer.castToBoolean(param)
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
return printer.makeStatement(
printer.makeMethodCall(`${param}Serializer`, "writeBoolean",
[printer.makeString(value)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeString(`${deserializerName}.readBoolean()`))
}
nativeType(): idl.IDLType {
return idl.IDLBooleanType
}
interopType(): idl.IDLType {
return idl.IDLBooleanType
}
isPointerType(): boolean {
return false
}
}
export class UndefinedConvertor extends BaseArgConvertor {
constructor(param: string) {
super(idl.IDLUndefinedType, [RuntimeType.UNDEFINED], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return writer.makeUndefined().asString()
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
return printer.makeStatement(printer.makeString(""))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeUndefined())
}
nativeType(): idl.IDLType {
return idl.IDLUndefinedType
}
interopType(): idl.IDLType {
return idl.IDLUndefinedType
}
isPointerType(): boolean {
return false
}
}
export class VoidConvertor extends UndefinedConvertor {
convertorArg(param: string, writer: LanguageWriter): string {
return writer.makeVoid().asString()
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeVoid())
}
nativeType(): idl.IDLType {
return idl.IDLVoidType
}
}
export class StringConvertor extends BaseArgConvertor {
private literalValue?: string
constructor(param: string) {
super(idl.IDLStringType, [RuntimeType.STRING], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return writer.language == Language.CPP
? writer.makeUnsafeCast_(writer.makeString(`&${param}`), this.idlType, PrintHint.AsConstPointer)
: writer.escapeKeyword(param)
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
return writer.makeStatement(
writer.makeMethodCall(`${param}Serializer`, "writeString",
[writer.makeString(value)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeCast(
writer.makeString(`${deserializerName}.readString()`),
this.idlType, { optional: false }
))
}
nativeType(): idl.IDLType {
return idl.IDLStringType
}
interopType(): idl.IDLType {
return idl.IDLStringType
}
isPointerType(): boolean {
return true
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
return this.literalValue
? writer.makeString(`${value} === "${this.literalValue}"`)
: undefined
}
targetType(writer: LanguageWriter): string {
if (this.literalValue) {
return writer.getNodeName(idl.IDLStringType)
}
return super.targetType(writer);
}
}
export class EnumConvertor extends BaseArgConvertor {
constructor(param: string, public enumEntry: idl.IDLEnum) {
super(idl.createReferenceType(enumEntry),
[idl.isStringEnum(enumEntry) ? RuntimeType.STRING : RuntimeType.NUMBER],
false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return writer.i32FromEnum(writer.makeString(writer.escapeKeyword(param)), this.enumEntry).asString()
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
return writer.makeStatement(
writer.makeMethodCall(`${param}Serializer`, "writeInt32",
[writer.i32FromEnum(writer.makeString(value), this.enumEntry)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const readExpr = writer.makeMethodCall(`${deserializerName}`, "readInt32", [])
const enumExpr = writer.enumFromI32(readExpr, this.enumEntry)
return assigneer(enumExpr)
}
nativeType(): idl.IDLType {
return this.idlType
}
interopType(): idl.IDLType {
return idl.IDLI32Type
}
isPointerType(): boolean {
return false
}
targetType(writer: LanguageWriter): string {
return writer.getNodeName(this.idlType)
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
const convertorNativeType = this.nativeType()
const decl = writer.resolver.resolveTypeReference(
idl.isReferenceType(convertorNativeType)
? convertorNativeType
: idl.createReferenceType(this.targetType(writer))
)
if (decl === undefined || !idl.isEnum(decl)) {
throwException(`The type reference ${decl?.name} must be Enum`)
}
const ordinal = idl.isStringEnum(decl)
? writer.i32FromEnum(
writer.makeCast(writer.makeString(writer.getObjectAccessor(this, value)), this.idlType),
decl,
)
: writer.makeUnionVariantCast(writer.getObjectAccessor(this, value), writer.getNodeName(idl.IDLI32Type), this, index)
const {low, high} = idl.extremumOfOrdinals(decl)
return writer.makeNaryOp("&&", [
writer.makeNaryOp(">=", [ordinal, writer.makeString(low.toString())]),
writer.makeNaryOp("<=", [ordinal, writer.makeString(high.toString())])
])
}
}
export class NumberConvertor extends BaseArgConvertor {
constructor(param: string) {
super(idl.IDLNumberType, [RuntimeType.NUMBER], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return writer.language == Language.CPP
? writer.makeUnsafeCast_(writer.makeString(`&${param}`), this.idlType, PrintHint.AsConstPointer)
: writer.escapeKeyword(param)
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
return writer.makeStatement(
writer.makeMethodCall(`${param}Serializer`, "writeNumber",
[writer.makeString(value)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeCast(
writer.makeString(`${deserializerName}.readNumber()`),
this.idlType, { optional: false })
)
}
nativeType(): idl.IDLType {
return idl.IDLNumberType
}
interopType(): idl.IDLType {
return idl.IDLNumberType
}
isPointerType(): boolean {
return true
}
}
export class NumericConvertor extends BaseArgConvertor {
private readonly interopNameConvertor = new CppNameConvertor(this.library)
constructor(protected library: LibraryInterface, param: string, type: idl.IDLPrimitiveType) {
super(type, [RuntimeType.NUMBER], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return param
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
return writer.makeStatement(
writer.makeMethodCall(`${param}Serializer`, `write${this.interopNameConvertor.convert(this.idlType)}`,
[writer.makeString(value)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(
writer.makeString(`${deserializerName}.read${this.interopNameConvertor.convert(this.idlType)}()`)
)
}
nativeType(): idl.IDLType {
return this.idlType
}
interopType(): idl.IDLType {
return this.idlType
}
isPointerType(): boolean {
return false
}
}
export class BigIntToU64Convertor extends BaseArgConvertor {
constructor(param: string) {
super(idl.IDLBigintType, [RuntimeType.BIGINT], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return writer.escapeKeyword(param)
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
return writer.makeStatement(
writer.makeMethodCall(`${param}Serializer`, `writeInt64`,
[writer.makeString(value)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeCast(
writer.makeString(`${deserializerName}.readInt64()`),
this.idlType, { optional: false })
)
}
nativeType(): idl.IDLType {
return idl.IDLI64Type
}
interopType(): idl.IDLType {
return idl.IDLI64Type
}
isPointerType(): boolean {
return false
}
}
export class ObjectConvertor extends BaseArgConvertor {
constructor(param: string, type: idl.IDLType) {
super(
type,
[
RuntimeType.BIGINT,
RuntimeType.BOOLEAN,
RuntimeType.FUNCTION,
RuntimeType.MATERIALIZED,
RuntimeType.NUMBER,
RuntimeType.OBJECT,
RuntimeType.STRING,
RuntimeType.SYMBOL,
],
false,
true,
param
)
}
convertorArg(param: string, writer: LanguageWriter): string {
return writer.escapeKeyword(param)
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
return writer.makeStatement(
writer.makeMethodCall(`${param}Serializer`, writer.language === Language.CPP ? `writeObject` : "holdAndWriteObject",
[writer.makeString(value)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeCast(
writer.makeMethodCall(deserializerName, 'readObject', []),
this.idlType, { optional: false })
)
}
holdResource(name: string, holder: string, writer: LanguageWriter): void {
writer.writeStatement(
writer.makeAssign(name, idl.createReferenceType(`idlize.stdlib.CallbackResource`),
writer.makeString(`{${this.param}.resource.resourceId, holdManagedCallbackResource, releaseManagedCallbackResource}`), true))
writer.writeExpressionStatement(
writer.makeMethodCall(holder, 'holdCallbackResource', [
writer.makeString('&' + name)])
)
}
nativeType(): idl.IDLType {
return idl.IDLAnyType
}
interopType(): idl.IDLType {
return idl.IDLAnyType
}
isPointerType(): boolean {
return true
}
}
export class PointerConvertor extends BaseArgConvertor {
constructor(param: string) {
super(idl.IDLPointerType, [RuntimeType.NUMBER, RuntimeType.OBJECT], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return param
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
return writer.makeStatement(
writer.makeMethodCall(`${param}Serializer`, "writePointer",
[writer.makeString(value)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(
writer.makeString(`${deserializerName}.readPointer()`)
)
}
nativeType(): idl.IDLType {
return this.idlType
}
interopType(): idl.IDLType {
return this.idlType
}
isPointerType(): boolean {
return false
}
}
export class BufferConvertor extends BaseArgConvertor {
constructor(param: string) {
super(idl.IDLBufferType, [RuntimeType.OBJECT], false, true, param)
}
convertorArg(param: string, _: LanguageWriter): string {
return param
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
return writer.makeStatement(
writer.makeMethodCall(`${param}Serializer`, "writeBuffer",
[writer.makeString(value)]
))
}
convertorDeserialize(_: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeCast(
writer.makeString(`${deserializerName}.readBuffer()`),
this.idlType, { optional: false })
)
}
nativeType(): idl.IDLType {
return idl.IDLBufferType
}
interopType(): idl.IDLType {
return idl.IDLBufferType
}
isPointerType(): boolean {
return true
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
return writer.instanceOf(value, this.idlType);
}
}
export class AggregateConvertor extends BaseArgConvertor {
protected memberConvertors: ArgConvertor[]
public members: [string, boolean][] = []
public readonly aliasName: string | undefined
constructor(protected library: LibraryInterface, param: string, type: idl.IDLType, protected decl: idl.IDLInterface) {
super(type, [RuntimeType.OBJECT], false, true, param)
this.memberConvertors = decl
.properties
.map((member, index) => {
this.members[index] = [member.name, member.isOptional]
return library.typeConvertor(param, member.type!, member.isOptional)
})
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Do not use for aggregates")
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
const stmts: LanguageStatement[] = this.memberConvertors.flatMap((it, index) => {
const memberName = this.members[index][0]
const memberAccess = `${value}.${printer.escapeKeyword(memberName)}`
const memberMangledName = `${value}${capitalize(memberName)}`
return [
printer.makeAssign(memberMangledName, undefined, printer.makeString(memberAccess), true),
it.convertorSerialize(param, memberMangledName, printer)
]
})
return printer.makeBlock(stmts, false)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const statements: LanguageStatement[] = []
if (writer.language === Language.CPP) {
statements.push(writer.makeAssign(bufferName, this.idlType, undefined, true, false))
}
if (writer.language === Language.ARKTS
&& this.memberConvertors.find(it => it instanceof FunctionConvertor)) {
return new BlockStatement([writer.makeThrowError("Not implemented yet")], false)
}
for (let i = 0; i < this.decl.properties.length; i++) {
const prop = this.decl.properties[i]
const propConvertor = this.memberConvertors[i]
const propName = `${bufferName}${capitalize(prop.name)}`
statements.push(propConvertor.convertorDeserialize(`${propName}TempBuf`, deserializerName, (expr) => {
if (writer.language === Language.CPP) {
return writer.makeAssign(`${bufferName}.${writer.escapeKeyword(prop.name)}`, undefined, expr, false)
}
* todo: check UnionType name creation for union of unnamed nodes (isNamedNode() == false)
*/
const memberType = idl.maybeOptional(prop.type, prop.isOptional)
return writer.makeAssign(propName, memberType, expr, true, true)
}, writer))
}
if (writer.language === Language.CPP) {
statements.push(assigneer(writer.makeString(bufferName)))
} else if (writer.language == Language.CJ) {
const resultExpression = writer.makeString(`${writer.getNodeName(this.idlType)}(${this.decl.properties.map(prop => `${bufferName}${capitalize(prop.name)}`).join(", ")})`)
statements.push(assigneer(resultExpression))
} else if (writer.language == Language.KOTLIN) {
const resultExpression = this.decl.subkind === idl.IDLInterfaceSubkind.Tuple ?
writer.makeString(`${writer.getNodeName(this.idlType)}(${this.decl.properties.map(prop => `${bufferName}_${prop.name}`).join(', ')})`) :
writer.makeString(`object: ${writer.getNodeName(this.idlType)} { ${this.decl.properties.map(prop => `override var ${prop.name} = ${bufferName}_${prop.name}`).join("; ")} }`)
statements.push(assigneer(resultExpression))
} else {
const resultExpression = this.makeAssigneeExpression(this.decl.properties.map(prop => {
return [prop.name, writer.makeString(`${bufferName}${capitalize(prop.name)}`)]
}), writer)
statements.push(assigneer(resultExpression))
}
return new BlockStatement(statements, false)
}
protected makeAssigneeExpression(fields: [string, LanguageExpression][], writer: LanguageWriter): LanguageExpression {
const content = fields.map(it => `${it[0]}: ${it[1].asString()}`).join(', ')
return writer.makeCast(writer.makeString(`{${content}}`), this.idlType)
}
nativeType(): idl.IDLType {
return idl.createReferenceType(this.decl)
}
interopType(): idl.IDLType {
throw new Error("Must never be used")
}
isPointerType(): boolean {
return true
}
getMembers(): string[] {
return this.members.map(it => it[0])
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
const uniqueFields = this.members.filter(it => !duplicates.has(it[0]))
return this.discriminatorFromFields(value,
writer,
uniqueFields,
it => it[0],
it => it[1])
}
}
export class TupleConvertor extends AggregateConvertor {
constructor(library: LibraryInterface, param: string, type: idl.IDLType, decl: idl.IDLInterface) {
super(library, param, type, decl)
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Must never be used")
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
const stmts: LanguageStatement[] = this.memberConvertors.flatMap((it, index) => {
return [
printer.makeAssign(`${value}_${index}`, undefined, printer.makeTupleAccess(value, index), true),
it.convertorSerialize(param, `${value}_${index}`, printer)
]
})
return printer.makeBlock(stmts, false)
}
protected override makeAssigneeExpression(fields: [string, LanguageExpression][], writer: LanguageWriter): LanguageExpression {
return writer.makeCast(writer.makeString(`[${fields.map(it => it[1].asString()).join(', ')}]`), this.idlType)
}
nativeType(): idl.IDLType {
return idl.createReferenceType(this.decl)
}
interopType(): idl.IDLType {
throw new Error("Must never be used")
}
isPointerType(): boolean {
return true
}
override getObjectAccessor(language: Language, value: string, args?: Record<string, string>): string {
return args?.index
? language === Language.CPP
? `${value}.value${args.index}`
: `${value}[${args.index}]`
: value
}
}
export class InterfaceConvertor extends BaseArgConvertor {
constructor(private library: LibraryInterface, name: string , param: string, public declaration: idl.IDLInterface) {
super(idl.createReferenceType(declaration), [RuntimeType.OBJECT], false, true, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Must never be used")
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
const accessor = getSerializerName(this.library, writer.language, this.declaration)
writer.addFeature(accessor, this.library.layout.resolve({ node: this.declaration, role: LayoutNodeRole.SERIALIZER }))
return writer.makeStatement(
writer.makeStaticMethodCall(accessor, 'write', [writer.makeString(`${param}Serializer`), writer.makeString(writer.escapeKeyword(value))])
)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const accessor = getSerializerName(this.library, writer.language, this.declaration)
writer.addFeature(accessor, this.library.layout.resolve({ node: this.declaration, role: LayoutNodeRole.SERIALIZER }))
return assigneer(writer.makeStaticMethodCall(accessor, 'read', [writer.makeString(deserializerName)]))
}
nativeType(): idl.IDLType {
return this.idlType
}
interopType(): idl.IDLType {
return idl.IDLSerializerBuffer
}
isPointerType(): boolean {
return true
}
getMembers(): string[] {
return this.declaration?.properties.map(it => it.name) ?? []
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
const uniqueFields = this.declaration?.properties.filter(it => !duplicates.has(it.name))
return this.discriminatorFromFields(value, writer, uniqueFields, it => it.name, it => it.isOptional)
}
}
export class ClassConvertor extends InterfaceConvertor {
constructor(library: LibraryInterface, name: string, param: string, declaration: idl.IDLInterface) {
super(library, name, param, declaration)
}
override unionDiscriminator(value: string,
index: number,
writer: LanguageWriter,
duplicateMembers: Set<string>): LanguageExpression | undefined {
return writer.instanceOf(value, this.idlType)
}
}
export class ArrayConvertor extends BaseArgConvertor {
elementConvertor: ArgConvertor
constructor(private library: LibraryInterface, param: string, private type: idl.IDLContainerType, protected elementType: idl.IDLType) {
super(idl.createContainerType('sequence', [elementType]), [RuntimeType.OBJECT], false, true, param)
this.elementConvertor = library.typeConvertor(param, elementType)
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Must never be used")
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
const statements: LanguageStatement[] = []
const valueLength = printer.makeArrayLength(value).asString()
statements.push(printer.makeStatement(
printer.makeMethodCall(`${param}Serializer`, "writeInt32",
[printer.makeString(printer.castToInt(valueLength, 32))]
))
)
const loopCounter = `${value}CounterI`
const elementName = `${value}TmpElement`
statements.push(printer.makeLoop(loopCounter, valueLength, printer.makeBlock([
printer.makeAssign(elementName,
this.elementType,
printer.makeArrayAccess(value, loopCounter), true),
this.elementConvertor.convertorSerialize(param, elementName, printer)
], false)))
return printer.makeBlock(statements, false)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const lengthBuffer = `${bufferName}Length`
const counterBuffer = `${bufferName}BufCounterI`
const statements: LanguageStatement[] = []
const arrayType = this.idlType
statements.push(writer.makeAssign(lengthBuffer, idl.IDLI32Type, writer.makeString(`${deserializerName}.readInt32()`), true))
if (writer instanceof ETSLanguageWriter) {
statements.push(writer.makeAssign(bufferName, arrayType, writer.makeArrayInit(this.type, lengthBuffer, {
initializerFunction: writer.makeLambda(new MethodSignature(this.elementType, [idl.IDLI32Type]), [
this.elementConvertor.convertorDeserialize(`${bufferName}TempBuf`, deserializerName, (expr) => {
return writer.makeLambdaReturn(expr)
}, writer)
])
})))
} else {
statements.push(writer.makeAssign(bufferName, arrayType, writer.makeArrayInit(this.type, lengthBuffer), true, false))
statements.push(writer.makeArrayResize(bufferName, writer.getNodeName(arrayType), lengthBuffer, deserializerName))
statements.push(writer.makeLoop(counterBuffer, lengthBuffer,
this.elementConvertor.convertorDeserialize(`${bufferName}TempBuf`, deserializerName, (expr) => {
return writer.makeAssign(writer.makeArrayAccess(bufferName, counterBuffer).asString(), undefined, expr, false)
}, writer)))
}
statements.push(assigneer(writer.makeString(bufferName)))
return new BlockStatement(statements, false)
}
nativeType(): idl.IDLType {
return idl.createContainerType('sequence', [this.elementType])
}
interopType(): idl.IDLType {
throw new Error("Must never be used")
}
isPointerType(): boolean {
return true
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
return writer.instanceOf(value, this.idlType)
}
override getObjectAccessor(language: Language, value: string, args?: Record<string, string>): string {
const array = language === Language.CPP ? ".array" : ""
return args?.index ? `${value}${array}${args.index}` : value
}
}
export class SetConvertor extends ArrayConvertor {
constructor(library: PeerLibrary, param: string, declaration: idl.IDLTypedef) {
const elementType = maybeRestoreGenerics(declaration, library)!.typeArguments![0]
super(library, param, idl.createContainerType('sequence', [elementType]), elementType)
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
if (printer.language === Language.CPP) {
return super.convertorSerialize(param, value, printer)
}
const elementBuffer = `${value}Element`
return printer.makeBlock([
printer.makeStatement(
printer.makeMethodCall(`${param}Serializer`, "writeInt32",
[printer.makeString(printer.castToInt(printer.makeSetSize(value).asString(), 32))]
)),
printer.makeSetForEach(value, elementBuffer, [
this.elementConvertor.convertorSerialize(param, elementBuffer, printer)
])
], false)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
if (writer.language === Language.CPP) {
return super.convertorDeserialize(bufferName, deserializerName, assigneer, writer)
}
const lengthBuffer = `${bufferName}Length`
const counterBuffer = `${bufferName}BufCounterI`
const statements: LanguageStatement[] = []
statements.push(writer.makeAssign(lengthBuffer, idl.IDLI32Type, writer.makeString(`${deserializerName}.readInt32()`), true))
statements.push(writer.makeAssign(bufferName, undefined, writer.makeSetInit(this.elementType), true, false))
statements.push(writer.makeLoop(counterBuffer, lengthBuffer,
this.elementConvertor.convertorDeserialize(`${bufferName}TempBuf`, deserializerName, (expr) => {
return writer.makeSetAdd(bufferName, expr)
}, writer)))
if (writer.language === Language.KOTLIN) {
statements.push(assigneer(writer.makeMethodCall(bufferName, "toSet", [])))
} else {
statements.push(assigneer(writer.makeString(bufferName)))
}
return new BlockStatement(statements, false)
}
}
export class MapConvertor extends BaseArgConvertor {
keyConvertor: ArgConvertor
valueConvertor: ArgConvertor
constructor(private library: LibraryInterface, param: string, type: idl.IDLType, public keyType: idl.IDLType, public valueType: idl.IDLType) {
super(
idl.createContainerType('record', [keyType, valueType], { extendedAttributes: type.extendedAttributes }),
[RuntimeType.OBJECT],
false,
true,
param
)
this.keyConvertor = library.typeConvertor(param, keyType)
this.valueConvertor = library.typeConvertor(param, valueType)
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Must never be used")
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
const mapSize = printer.makeMapSize(value)
return printer.makeBlock([
printer.makeStatement(printer.makeMethodCall(`${param}Serializer`, "writeInt32", [printer.makeString(printer.castToInt(mapSize.asString(), 32))])),
printer.makeMapForEach(value, `${value}KeyVar`, `${value}ValueVar`, [
this.keyConvertor.convertorSerialize(param, `${value}KeyVar`, printer),
this.valueConvertor.convertorSerialize(param, `${value}ValueVar`, printer)
])
], false)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const mapTypeName = writer.getNodeName(this.idlType)
const keyType = this.keyType
const valueType = this.valueType
const sizeBuffer = `${bufferName}SizeVar`
const keyBuffer = `${bufferName}KeyVar`
const valueBuffer = `${bufferName}ValueVar`
const counterBuffer = `${bufferName}IVar`
const keyAccessor = this.getObjectAccessor(writer.language, bufferName, {index: counterBuffer, field: "keys"})
const valueAccessor = this.getObjectAccessor(writer.language, bufferName, {index: counterBuffer, field: "values"})
return new BlockStatement([
writer.makeAssign(sizeBuffer, idl.IDLI32Type,
writer.makeString(`${deserializerName}.readInt32()`), true, true),
writer.makeAssign(bufferName, this.idlType, writer.makeMapInit(this.idlType), true, false),
writer.makeMapResize(mapTypeName, keyType, valueType, bufferName, sizeBuffer, deserializerName),
writer.makeLoop(counterBuffer, sizeBuffer, new BlockStatement([
this.keyConvertor.convertorDeserialize(`${keyBuffer}TempBuf`, deserializerName, (expr) => {
return writer.makeAssign(keyBuffer, keyType, expr, true, true)
}, writer),
this.valueConvertor.convertorDeserialize(`${valueBuffer}TempBuf`, deserializerName, (expr) => {
return writer.makeAssign(valueBuffer, valueType, expr, true, true)
}, writer),
writer.makeMapInsert(keyAccessor, keyBuffer, valueAccessor, valueBuffer),
], false)),
assigneer(writer.makeString(bufferName))
], false)
}
nativeType(): idl.IDLType {
return idl.createContainerType('record', [this.keyType, this.valueType])
}
interopType(): idl.IDLType {
throw new Error("Must never be used")
}
isPointerType(): boolean {
return true
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
return writer.makeString(`${value} instanceof Map`)
}
override getObjectAccessor(language: Language, value: string, args?: Record<string, string>): string {
return language === Language.CPP && args?.index && args?.field
? `${value}.${args.field}[${args.index}]`
: value
}
}
export class DateConvertor extends BaseArgConvertor {
constructor(param: string) {
super(idl.IDLDate, [RuntimeType.NUMBER], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
if (writer.language === Language.CPP) {
return param
}
return `${param}.getTime()`
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
if (writer.language === Language.CPP) {
return writer.makeStatement(writer.makeMethodCall(`${param}Serializer`, "writeInt64", [writer.makeString(value)]))
} else if (writer.language === Language.CJ) {
return writer.makeStatement(writer.makeMethodCall(`${param}Serializer`, "writeInt64", [
writer.makeCast(writer.makeString(`${value}`), idl.IDLI64Type)
]))
} else {
return writer.makeStatement(writer.makeMethodCall(`${param}Serializer`, "writeInt64", [
writer.makeCast(writer.makeString(`${value}.getTime()`), idl.IDLI64Type)
]))
}
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const deserializeTime = writer.makeMethodCall(`${deserializerName}`, "readInt64", [])
if (writer.language === Language.CPP) {
return assigneer(deserializeTime)
}
if (writer.language === Language.CJ) {
return assigneer(writer.makeString(`DateTime.now()`))
}
return assigneer(writer.makeString(`new Date(${deserializeTime.asString()})`))
}
nativeType(): idl.IDLType {
return idl.IDLDate
}
interopType(): idl.IDLType {
return idl.IDLDate
}
isPointerType(): boolean {
return false
}
}
export class ProxyConvertor extends BaseArgConvertor {
constructor(public convertor: ArgConvertor, suggestedReference?: idl.IDLReferenceType) {
super(suggestedReference ? suggestedReference : convertor.idlType, convertor.runtimeTypes, convertor.isScoped, convertor.useArray, convertor.param)
}
convertorArg(param: string, writer: LanguageWriter): string {
return this.convertor.convertorArg(param, writer)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return this.convertor.convertorDeserialize(bufferName, deserializerName, assigneer, writer)
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
return this.convertor.convertorSerialize(param, value, printer)
}
nativeType(): idl.IDLType {
return this.convertor.nativeType()
}
interopType(): idl.IDLType {
return this.convertor.interopType()
}
isPointerType(): boolean {
return this.convertor.isPointerType()
}
unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
return this.convertor.unionDiscriminator(value, index, writer, duplicates)
}
getMembers(): string[] {
return this.convertor.getMembers()
}
}
export class TypeAliasConvertor extends ProxyConvertor {
constructor(library: LibraryInterface, param: string, typedef: idl.IDLTypedef) {
super(library.typeConvertor(param, typedef.type), idl.createReferenceType(typedef))
}
}
export class CustomTypeConvertor extends BaseArgConvertor {
constructor(param: string,
public readonly customTypeName: string,
private readonly isGenericType: boolean,
tsType: string) {
super(idl.createReferenceType(tsType ?? "Object"), [RuntimeType.OBJECT], false, true, param)
warnCustomObject(`${customTypeName}: ${tsType}`)
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Must never be used")
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
return printer.makeStatement(printer.makeMethodCall(
`${param}Serializer`,
`writeCustomObject`,
[printer.makeString(`"${this.customTypeName}"`), printer.makeCastCustomObject(value, this.isGenericType)]
))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const type = writer.language === Language.CPP
? this.nativeType()
: this.idlType
return assigneer(writer.makeCast(
writer.makeMethodCall(`${deserializerName}`,
"readCustomObject",
[writer.makeString(`"${this.customTypeName}"`)]),
type, { optional: false }))
}
nativeType(): idl.IDLType {
return idl.IDLCustomObjectType
}
interopType(): idl.IDLType {
throw new Error("Must never be used")
}
isPointerType(): boolean {
return true
}
}
export class OptionConvertor extends BaseArgConvertor {
private readonly typeConvertor: ArgConvertor
constructor(library: LibraryInterface, param: string, public type: idl.IDLOptionalType) {
let conv = library.typeConvertor(param, type.type)
let currentConv:ArgConvertor = conv
while (currentConv instanceof ProxyConvertor) {
currentConv = currentConv.convertor
}
let runtimeTypes = conv.runtimeTypes;
if (!runtimeTypes.includes(RuntimeType.UNDEFINED)) {
runtimeTypes.push(RuntimeType.UNDEFINED)
}
super(idl.createOptionalType(conv.idlType), runtimeTypes, conv.isScoped, true, param)
this.typeConvertor = conv
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Must never be used")
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
const valueValue = `${value}TmpValue`.replaceAll('.', '_')
return printer.makeCondition(
printer.makeDefinedCheck(value, this.type),
new BlockStatement([
printer.makeStatement(printer.makeMethodCall(`${param}Serializer`, "writeInt8", [printer.makeRuntimeType(RuntimeType.OBJECT)])),
printer.makeAssign(valueValue, undefined, printer.makeValueFromOption(value, this.typeConvertor), true),
this.typeConvertor.convertorSerialize(param, this.typeConvertor.getObjectAccessor(printer.language, valueValue), printer)
], true, false),
new BlockStatement([printer.makeStatement(
printer.makeMethodCall(`${param}Serializer`, "writeInt8", [printer.makeRuntimeType(RuntimeType.UNDEFINED)])
)], true, false)
)
}
convertorCArg(param: string): string {
throw new Error("Must never be used")
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const runtimeBufferName = `${bufferName}_runtimeType`
const statements: LanguageStatement[] = []
statements.push(writer.makeAssign(runtimeBufferName, undefined,
writer.makeCast(writer.makeString(`${deserializerName}.readInt8()`), writer.getRuntimeType()), true))
const bufferType = this.nativeType()
statements.push(writer.makeAssign(bufferName, bufferType, writer.language === Language.CPP ? undefined : writer.makeNull(this.type), true, false))
const thenStatement = new BlockStatement([
this.typeConvertor.convertorDeserialize(`${bufferName}Opt`, deserializerName, (expr) => {
const receiver = writer.language === Language.CPP
? `${bufferName}.value` : bufferName
return writer.makeAssign(receiver, undefined, expr, false)
}, writer)
], true, false)
statements.push(writer.makeSetOptionTag(bufferName, writer.makeCast(writer.makeString(runtimeBufferName), writer.getTagType())))
statements.push(writer.makeCondition(writer.makeRuntimeTypeDefinedCheck(runtimeBufferName), thenStatement))
statements.push(assigneer(writer.makeString(bufferName)))
return writer.makeBlock(statements, false)
}
nativeType(): idl.IDLType {
return this.type
}
interopType(): idl.IDLType {
return this.type
}
isPointerType(): boolean {
return true
}
override getObjectAccessor(language: Language, value: string, args?: Record<string, string>): string {
return language === Language.CPP ? `${value}.value` : value
}
}
class ConvertorItem {
constructor(
public convertor: ArgConvertor,
public index: number,
public type: idl.IDLType
) {
}
}
export class UnionConvertor extends BaseArgConvertor {
private readonly memberConvertors: ArgConvertor[]
private unionChecker: UnionRuntimeTypeChecker
constructor(private library: LibraryInterface, param: string, private type: idl.IDLUnionType) {
super(idl.IDLObjectType, [], false, true, param)
this.memberConvertors = type.types.map(member => library.typeConvertor(param, member))
this.unionChecker = new UnionRuntimeTypeChecker(this.memberConvertors)
this.runtimeTypes = this.memberConvertors.flatMap(it => it.runtimeTypes)
this.idlType = type
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Do not use for union")
}
isSequence(type: idl.IDLType): boolean {
return idl.isContainerType(type) && idl.IDLContainerUtils.isSequence(type)
}
isIndexedDiscriminator(writer: LanguageWriter) {
if (writer.language == Language.CPP) return true
return false
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
let convertorItems = this.memberConvertors.map((it, index) => new ConvertorItem(it, index, getSourceType(it)))
convertorItems = UnionConvertor.sortInterfacesInheritance(this.library, convertorItems)
if (this.isIndexedDiscriminator(printer))
return printer.makeMultiBranchCondition(convertorItems.map(it => this.makeBranch(param, value, value, printer, it)));
return this.convertorSerializeMultiBranch(param, value, value, printer, convertorItems)
}
makeStoreSelector(param: string, index: number, printer: LanguageWriter): LanguageStatement {
return printer.makeStatement(
printer.makeMethodCall(
`${param}Serializer`, "writeInt8",
[printer.makeString(printer.castToInt(index.toString(), 8))]
)
)
}
makeBranch(param: string, value: string, array: string, printer: LanguageWriter, convertorItem: ConvertorItem): BranchStatement {
const convertor = convertorItem.convertor
const index = convertorItem.index
const type = convertorItem.type
const discriminator = this.unionChecker.makeDiscriminator(value, index, printer, this.library, type)
const statements: LanguageStatement[] = []
statements.push(this.makeStoreSelector(param, index, printer))
if (!(convertor instanceof UndefinedConvertor)) {
const varName = `${array}ForIdx${index}`
statements.push(
printer.makeAssign(varName, undefined,
printer.makeUnionVariantCast(convertor.getObjectAccessor(printer.language, array), printer.getNodeName(getSourceType(convertor)), convertor, index), true)
)
statements.push(convertor.convertorSerialize(param, varName, printer))
}
const dicriminatorCondition = withDiscriminatorCondition(this.library, this.memberConvertors, value, discriminator, type, printer)
const stmt = new BlockStatement(statements, false)
return { expr: dicriminatorCondition, stmt }
}
makeArrayBranch(param: string, value: string, array: string, printer: LanguageWriter, arrayConvertorItems: ConvertorItem[]): BranchStatement[] {
if (arrayConvertorItems.length == 0) return []
const arrayConvertorItem = arrayConvertorItems[0]
const elemName = `${value}Elem`
const elemAccess = printer.makeString(`${value}[0]`)
const checkZeroArray = printer.makeCondition(
printer.makeString(`${value}.length == 0`),
new BlockStatement([
this.makeStoreSelector(param, arrayConvertorItem.index, printer),
printer.makeStatement(
printer.makeMethodCall(`${param}Serializer`, "writeInt32", [printer.makeString("0")]))
], true, false),
new BlockStatement([
printer.makeAssign(elemName, undefined, elemAccess, true, true),
this.convertorSerializeMultiBranch(param, elemName, array, printer, arrayConvertorItems.map(it =>
new ConvertorItem(it.convertor, it.index, (it.type as idl.IDLContainerType).elementType[0])))
], true, false)
)
const arrayMultiBranch: BranchStatement = {
expr: this.unionChecker.makeDiscriminator(value, arrayConvertorItem.index, printer, this.library, arrayConvertorItem.type),
stmt: checkZeroArray
}
return [arrayMultiBranch]
}
convertorSerializeMultiBranch(param: string, value: string, array: string, printer: LanguageWriter, convertors: ConvertorItem[]): LanguageStatement {
return printer.makeMultiBranchCondition([
...convertors
.filter(it => !this.isSequence(it.type))
.map(it => this.makeBranch(param, value, array, printer, it)),
...this.makeArrayBranch(param, value, array, printer, convertors.filter(it => this.isSequence(it.type)))
])
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const statements: LanguageStatement[] = []
let selectorBuffer = `${bufferName}UnionSelector`
const maybeOptionalUnion = writer.language === Language.CPP || writer.language == Language.CJ
? this.type
: idl.createOptionalType(this.type)
statements.push(writer.makeAssign(selectorBuffer, idl.IDLI8Type,
writer.makeString(`${deserializerName}.readInt8()`), true))
statements.push(writer.makeAssign(bufferName, maybeOptionalUnion, undefined, true, false))
if (writer.language === Language.CPP)
statements.push(writer.makeAssign(`${bufferName}.selector`, undefined, writer.makeString(selectorBuffer), false))
const branches: BranchStatement[] = this.memberConvertors.map((it, index) => {
const receiver = this.getObjectAccessor(writer.language, bufferName, {index: `${index}`})
const expr = writer.makeString(`${selectorBuffer} == ${writer.castToInt(index.toString(), 8)}`)
const stmt = new BlockStatement([
writer.makeSetUnionSelector(bufferName, `${index}`),
it.convertorDeserialize(`${bufferName}BufU`, deserializerName, (expr) => {
if (writer.language == Language.CJ || writer.language == Language.KOTLIN) {
return writer.makeAssign(receiver, undefined, writer.makeFunctionCall(writer.getNodeName(this.type), [expr]), false)
} else {
return writer.makeAssign(receiver, undefined, expr, false)
}
}, writer),
], false)
return { expr, stmt }
})
statements.push(writer.makeMultiBranchCondition(branches, writer.makeThrowError(`One of the branches for ${bufferName} has to be chosen through deserialisation.`)))
statements.push(assigneer(writer.makeCast(writer.makeString(bufferName), this.nativeType())))
return new BlockStatement(statements, false)
}
nativeType(): idl.IDLType {
return this.type
}
interopType(): idl.IDLType {
throw new Error("Union")
}
isPointerType(): boolean {
return true
}
override getObjectAccessor(language: Language, value: string, args?: Record<string, string>): string {
return language === Language.CPP && args?.index ? `${value}.value${args.index}` : value
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
return writer.makeNaryOp("||",
this.memberConvertors.map((_, n) => this.unionChecker.makeDiscriminator(value, n, writer, this.library)))
}
private static sortInterfacesInheritance(resolver: ReferenceResolver, items: ConvertorItem[]): ConvertorItem[] {
let resolveInterface = (type: idl.IDLType): idl.IDLInterface | undefined => {
if (!idl.isReferenceType(type)) {
return undefined
}
const decl = resolver.resolveTypeReference(type)
return decl && idl.isInterface(decl) ? decl : undefined
}
let resolveParents = (decl: idl.IDLInterface): idl.IDLInterface[] => {
return [...decl.inheritance
.map(resolveInterface)
.filter(isDefined)
.flatMap(it => [it, ...resolveParents(it)])]
}
const queue = items.map<{
convertorItem: ConvertorItem,
needs?: ConvertorItem[],
}>(it => {
return { convertorItem: it }
})
queue.forEach((it, _, sortItems) => {
const decl = resolveInterface(it.convertorItem.type)
const parents = decl ? resolveParents(decl) : undefined
if (parents) {
for (const other of sortItems) {
const otherDecl = resolveInterface(other.convertorItem.type)
if (otherDecl && parents.includes(otherDecl)) {
other.needs ??= []
other.needs.push(it.convertorItem)
}
}
}
})
const sorted: ConvertorItem[] = []
while (queue.length) {
for (let i = 0; i < queue.length;) {
const item = queue[i]
if (!item.needs || item.needs?.every(it => sorted.includes(it))) {
queue.splice(i, 1)
sorted.push(item.convertorItem)
} else {
i++
}
}
}
return sorted
}
}
export class FunctionConvertor extends BaseArgConvertor {
constructor(private library: LibraryInterface, param: string) {
super(idl.IDLFunctionType, [RuntimeType.FUNCTION], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error('Shall not be used')
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
throw new Error('Shall not be used')
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
throw new Error('Shall not be used')
}
nativeType(): idl.IDLType {
return idl.IDLFunctionType
}
interopType(): idl.IDLType {
return idl.IDLFunctionType
}
isPointerType(): boolean {
return false
}
}
export class MaterializedClassConvertor extends BaseArgConvertor {
constructor(private library: LibraryInterface, param: string, public declaration: idl.IDLInterface) {
super(idl.createReferenceType(declaration), [RuntimeType.OBJECT], false, false, param)
}
convertorArg(param: string, writer: LanguageWriter): string {
const nameConvertor = this.library.createTypeNameConvertor(Language.CPP)
switch (writer.language) {
case Language.CPP:
return `static_cast<${nameConvertor.convert(this.declaration)}>(${param})`
case Language.CJ:
return `MaterializedBase.toPeerPtr(${writer.escapeKeyword(param)})`
default:
if (isInExternalModule(this.declaration)) {
const extractor = getExtractor(this.declaration, writer.language, true)
return `${extractor.receiver}.${extractor.method}(${param})`
}
return `toPeerPtr(${param})`
}
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
const accessorRoot = getSerializerName(this.library, printer.language, this.declaration)
printer.addFeature(accessorRoot, this.library.layout.resolve({ node: this.declaration, role: LayoutNodeRole.SERIALIZER }))
return printer.makeStatement(
printer.makeStaticMethodCall(
accessorRoot, 'write',
[printer.makeString(`${param}Serializer`), printer.makeString(value)]
)
)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
const accessorRoot = getSerializerName(this.library, writer.language, this.declaration)
writer.addFeature(accessorRoot, this.library.layout.resolve({ node: this.declaration, role: LayoutNodeRole.SERIALIZER }))
const readStatement = writer.makeCast(
writer.makeStaticMethodCall(accessorRoot, "read", [writer.makeString(deserializerName)]),
this.declaration)
return assigneer(readStatement)
}
nativeType(): idl.IDLType {
return idl.createReferenceType(this.declaration)
}
interopType(): idl.IDLType {
return idl.IDLPointerType
}
isPointerType(): boolean {
return false
}
override unionDiscriminator(value: string, index: number, writer: LanguageWriter, duplicates: Set<string>): LanguageExpression | undefined {
if (idl.isInterface(this.declaration)) {
if (this.declaration.subkind === idl.IDLInterfaceSubkind.Class) {
return writer.instanceOf(value, this.idlType)
}
if (this.declaration.subkind === idl.IDLInterfaceSubkind.Interface) {
const uniqueFields = this.declaration.properties.filter(it => !duplicates.has(it.name))
return this.discriminatorFromFields(value, writer, uniqueFields, it => it.name, it => it.isOptional)
}
}
}
}
export class ImportTypeConvertor extends BaseArgConvertor {
protected importedName: string
constructor(param: string, importedName: string) {
super(idl.IDLObjectType, [RuntimeType.OBJECT], false, true, param)
this.importedName = importedName
warnCustomObject(importedName, `imported`)
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Must never be used")
}
convertorSerialize(param: string, value: string, printer: LanguageWriter): LanguageStatement {
return printer.makeStatement(printer.makeMethodCall(`${param}Serializer`, "writeCustomObject", [printer.makeString(`"${this.importedName}"`), printer.makeString(value)]))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return assigneer(writer.makeString(`${deserializerName}.readCustomObject("${this.importedName}")`))
}
nativeType(): idl.IDLType {
return idl.IDLCustomObjectType
}
interopType(): idl.IDLType {
throw new Error("Must never be used")
}
isPointerType(): boolean {
return true
}
}
export class CallbackConvertor extends BaseArgConvertor {
constructor(
private readonly library: LibraryInterface,
param: string,
private readonly decl: idl.IDLCallback,
private readonly interopModuleName: NativeModuleType
) {
super(idl.createReferenceType(decl), [RuntimeType.FUNCTION], false, true, param)
}
private get isTransformed(): boolean {
return this.decl !== this.transformedDecl
}
private get transformedDecl(): idl.IDLCallback {
return maybeTransformManagedCallback(this.decl, this.library) ?? this.decl
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Must never be used")
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
if (writer.language == Language.CPP) {
return writer.makeBlock([
writer.makeStatement(writer.makeMethodCall(`${param}Serializer`, "writeCallbackResource", [writer.makeString(`${value}.resource`)])),
writer.makeStatement(writer.makeMethodCall(`${param}Serializer`, "writePointer", [writer.makeCast(
new StringExpression(`${value}.call`), idl.IDLPointerType, { unsafe: true })])),
writer.makeStatement(writer.makeMethodCall(`${param}Serializer`, "writePointer", [writer.makeCast(
new StringExpression(`${value}.callSync`), idl.IDLPointerType, { unsafe: true })]))
], false)
}
if (this.isTransformed) {
const convertor = this.library.createTypeNameConvertor(Language.CPP)
value = `CallbackTransformer.transformFrom${convertor.convert(this.decl)}(${value})`
}
return writer.makeStatement(writer.makeMethodCall(`${param}Serializer`, `holdAndWriteCallback`, [writer.makeString(`${value}`)]))
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter, useSyncVersion: boolean = true): LanguageStatement {
if (writer.language == Language.CPP) {
const callerInvocation = writer.makeString(`getManagedCallbackCaller(${generateCallbackKindAccess(this.transformedDecl, writer.language)})`)
const callerSyncInvocation = writer.makeString(`getManagedCallbackCallerSync(${generateCallbackKindAccess(this.transformedDecl, writer.language)})`)
const resourceReadExpr = writer.makeMethodCall(`${deserializerName}`, `readCallbackResource`, [])
const callReadExpr = writer.makeCast(
writer.makeMethodCall(`${deserializerName}`, `readPointerOrDefault`,
[writer.makeCast(callerInvocation, idl.IDLPointerType, { unsafe: true })]),
idl.IDLUndefinedType ,
{
unsafe: true,
overrideTypeName: `void(*)(${generateCallbackAPIArguments(this.library, this.transformedDecl).join(", ")})`
}
)
const callSyncReadExpr = writer.makeCast(
writer.makeMethodCall(`${deserializerName}`, `readPointerOrDefault`,
[writer.makeCast(callerSyncInvocation, idl.IDLPointerType, { unsafe: true })]),
idl.IDLUndefinedType ,
{
unsafe: true,
overrideTypeName: `void(*)(${[`${generatorTypePrefix()}VMContext vmContext`].concat(generateCallbackAPIArguments(this.library, this.transformedDecl)).join(", ")})`
}
)
return assigneer(writer.makeString(`{${resourceReadExpr.asString()}, ${callReadExpr.asString()}, ${callSyncReadExpr.asString()}}`))
}
const resourceName = bufferName + "BufResource"
const callName = bufferName + "BufCall"
const callSyncName = bufferName + 'BufCallSync'
const argsSerializer = bufferName + "BufArgs"
const continuationValueName = bufferName + "BufContinuationValue"
const continuationCallbackName = bufferName + "BufContinuationCallback"
const statements: LanguageStatement[] = []
statements.push(writer.makeAssign(
resourceName,
idl.createReferenceType("idlize.stdlib.CallbackResource"),
writer.makeMethodCall(deserializerName, 'readCallbackResource', []),
true,
))
statements.push(writer.makeAssign(
callName,
idl.IDLPointerType,
writer.makeMethodCall(deserializerName, `readPointer`, []),
true,
))
statements.push(writer.makeAssign(
callSyncName,
idl.IDLPointerType,
writer.makeMethodCall(deserializerName, 'readPointer', []),
true,
))
const callbackSignature = new NamedMethodSignature(
this.decl.returnType,
this.decl.parameters.map(it => idl.maybeOptional(it.type!, it.isOptional)),
this.decl.parameters.map(it => it.name),
)
const hasContinuation = !idl.isVoidType(this.decl.returnType)
let continuation: LanguageStatement[] = []
if (hasContinuation) {
const continuationReference = this.library.createContinuationCallbackReference(this.decl.returnType)
const continuationConvertor = this.library.typeConvertor(continuationCallbackName, continuationReference)
const returnType = this.decl.returnType
const optionalReturnType = idl.createOptionalType(this.decl.returnType)
continuation = [
writer.language == Language.CJ ?
writer.makeAssign(continuationValueName, undefined, writer.makeString(`${writer.getNodeName(this.decl.returnType).replace(/[\<\>]/g, '')}Holder(None<${writer.getNodeName(this.decl.returnType)}>)`), true, true) :
writer.makeAssign(continuationValueName, optionalReturnType, writer.language == Language.KOTLIN ? writer.makeNull() : undefined, true, false),
writer.makeAssign(
continuationCallbackName,
continuationReference,
writer.makeLambda(new NamedMethodSignature(idl.IDLVoidType, [returnType], [`value`]), [
writer.language == Language.CJ ?
writer.makeAssign(`${continuationValueName}.value`, undefined, writer.makeString(`value`), false) :
writer.makeAssign(continuationValueName, undefined, writer.makeString(`value`), false)
]),
true,
),
new ProxyStatement(writer => {
writer.writeStatement(continuationConvertor.convertorSerialize(argsSerializer, continuationCallbackName, writer))
}),
]
}
const closure = writer.makeLambda(callbackSignature, [
writer.makeAssign(`${argsSerializer}Serializer`, idl.createReferenceType('idlize.internal.SerializerBase'), writer.makeMethodCall('SerializerBase', 'hold', []), true),
new ExpressionStatement(writer.makeMethodCall(`${argsSerializer}Serializer`, `writeInt32`,
[writer.makeString(`${resourceName}.resourceId`)])),
new ExpressionStatement(writer.makeMethodCall(`${argsSerializer}Serializer`, `writePointer`,
[writer.makeString(callName)])),
new ExpressionStatement(writer.makeMethodCall(`${argsSerializer}Serializer`, `writePointer`,
[writer.makeString(callSyncName)])),
...this.decl.parameters.map(it => {
const convertor = this.library.typeConvertor(it.name, it.type!, it.isOptional)
return new ProxyStatement((writer: LanguageWriter) => {
writer.writeStatement(convertor.convertorSerialize(argsSerializer, writer.escapeKeyword(it.name), writer))
})
}),
...continuation,
new ExpressionStatement(
useSyncVersion
? writer.makeNativeCall(this.interopModuleName, `_CallCallbackSync`, [
writer.makeString(generatorConfiguration().ApiKind.toString()),
writer.makeString(generateCallbackKindValue(this.decl).toString()),
writer.makeSerializedBufferGetter(`${argsSerializer}Serializer`),
writer.makeString(`${argsSerializer}Serializer.length()`),
])
: writer.makeNativeCall(this.interopModuleName, `_CallCallback`, [
writer.makeString(generatorConfiguration().ApiKind.toString()),
writer.makeString(generateCallbackKindValue(this.decl).toString()),
writer.makeSerializedBufferGetter(`${argsSerializer}Serializer`),
writer.makeString(`${argsSerializer}Serializer.length()`),
])
),
new ExpressionStatement(writer.makeMethodCall(`${argsSerializer}Serializer`, `release`, [])),
writer.makeLambdaReturn(hasContinuation
? writer.makeCast(
writer.language == Language.CJ ?
writer.makeString(`${continuationValueName}.value`) :
writer.makeString(continuationValueName),
this.decl.returnType)
: undefined),
])
writer.addFeature(idl.createReferenceType('idlize.internal.resourceFinalizerRegister'))
statements.push(
writer.makeAssign(`${bufferName}Closure`, undefined, closure, true),
writer.makeStatement(writer.makeFunctionCall(`resourceFinalizerRegister`, [
writer.makeString(`${bufferName}Closure`),
writer.makeString(resourceName)
])),
)
return writer.makeBlock([
...statements,
assigneer(writer.makeString(`${bufferName}Closure`))
], false)
}
nativeType(): idl.IDLType {
return idl.createReferenceType(this.decl)
}
isPointerType(): boolean {
return true
}
}
const customObjects = new Set<string>()
function warnCustomObject(type: string, msg?: string) {
if (!customObjects.has(type)) {
warn(`Use CustomObject for ${msg ? `${msg} ` : ``}type ${type}`)
customObjects.add(type)
}
}
export const CallbackKind = "CallbackKind"
export function generateCallbackKindName(callback: idl.IDLCallback) {
return `KIND_${callback.name.toUpperCase()}`
}
export function generateCallbackKindAccess(callback: idl.IDLCallback, language: Language) {
const name = generateCallbackKindName(callback)
if (language == Language.CPP)
return name
return `${CallbackKind}.${name}`
}
export function generateCallbackKindValue(callback: idl.IDLCallback): number {
const name = generateCallbackKindName(callback)
return hashCodeFromString(name)
}
export function generateCallbackAPIArguments(library: LibraryInterface, callback: idl.IDLCallback): string[] {
const nameConvertor = new CppConvertor(library)
const args: string[] = [`const ${PrimitiveTypesInstance.Int32.getText()} resourceId`]
args.push(...callback.parameters.map(it => {
const target = library.toDeclaration(it.type!)
const type = library.typeConvertor(it.name, it.type!, it.isOptional)
const constPrefix = !idl.isEnum(target) ? "const " : ""
return `${constPrefix}${nameConvertor.convert(type.nativeType())} ${type.param}`
}))
if (!idl.isVoidType(callback.returnType)) {
const type = library.typeConvertor(`continuation`,
library.createContinuationCallbackReference(callback.returnType)!, false)
args.push(`const ${nameConvertor.convert(type.nativeType())} ${type.param}`)
}
return args
}
export function maybeTransformManagedCallback(callback: idl.IDLCallback, library: ReferenceResolver): idl.IDLCallback | undefined {
if (callback.name === "CustomBuilder")
return library.resolveTypeReference(idl.createReferenceType("arkui.component.idlize.CustomNodeBuilder")) as idl.IDLCallback
return undefined
}
class PromiseOutArgConvertor extends BaseArgConvertor {
callbackConvertor: CallbackConvertor
callback: idl.IDLCallback
isOut: true = true
constructor(
private readonly library: PeerLibrary,
param: string,
readonly promise: idl.IDLContainerType)
{
super(library.createContinuationCallbackReference(promise), [RuntimeType.FUNCTION], false, true, param)
const type = this.idlType as idl.IDLReferenceType
const callbackEntry = library.resolveTypeReference(type)
if (!callbackEntry)
throw new Error(`Internal error: no callback for ${type.name} resolved`)
this.callback = callbackEntry as idl.IDLCallback
this.callbackConvertor = new CallbackConvertor(library, param, this.callback, this.library.interopNativeModule)
}
convertorArg(param: string, writer: LanguageWriter): string {
return this.callbackConvertor.convertorArg(param, writer)
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
if (writer.language == Language.CPP) {
return this.callbackConvertor.convertorSerialize(param, value, writer)
}
let serializeCallback: LanguageExpression
if (idl.isVoidType(this.promise.elementType[0])) {
serializeCallback = writer.makeMethodCall(`${param}Serializer`, `holdAndWriteCallbackForPromiseVoid`, [])
} else {
serializeCallback = writer.makeMethodCall(`${param}Serializer`, `holdAndWriteCallbackForPromise<${writer.getNodeName(this.promise.elementType[0])}>`, [])
}
return writer.makeAssign(value, undefined, writer.language == Language.CJ
? writer.makeString(serializeCallback.asString().concat('.promise'))
: writer.makeTupleAccess(serializeCallback.asString(), 0), true)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
return this.callbackConvertor.convertorDeserialize(bufferName, deserializerName, assigneer, writer)
}
nativeType(): idl.IDLType {
return this.idlType
}
isPointerType(): boolean {
return true
}
}
export class TransformOnSerializeConvertor extends BaseArgConvertor {
private targetConvertor: ArgConvertor
constructor(param: string, protected library: PeerLibrary, protected managedDeclaration: idl.IDLEntry, protected source: idl.IDLType, protected target: idl.IDLType) {
const targetConvertor = library.typeConvertor(param, target)
super(target, targetConvertor.runtimeTypes, false, targetConvertor.useArray, param)
this.targetConvertor = targetConvertor
}
getSourceType(): idl.IDLType {
return this.source
}
getTargetType(): idl.IDLType {
return this.target
}
isPointerType(): boolean {
return this.targetConvertor.isPointerType()
}
nativeType(): idl.IDLType {
return this.targetConvertor.nativeType()
}
convertorArg(param: string, writer: LanguageWriter): string {
throw new Error("Method not implemented.");
}
addImport(transformerInfo: { module: string, ns?: string, method: string }, writer: LanguageWriter) {
transformerInfo.ns
? writer.addFeature(transformerInfo.ns, transformerInfo.module)
: writer.addFeature(transformerInfo.method, transformerInfo.module)
}
convertorSerialize(param: string, value: string, writer: LanguageWriter): LanguageStatement {
if (writer.language === Language.CPP) {
return this.targetConvertor.convertorSerialize(param, value, writer)
}
if (idl.isReferenceType(this.target)) {
writer.addFeature(this.target)
}
const transformerInfo = getTransformer(this.library, this.managedDeclaration, this.target)
this.addImport(transformerInfo, writer)
const transformCallExpression = transformerInfo.ns
? writer.makeMethodCall(transformerInfo.ns, transformerInfo.method, [writer.makeString(value)])
: writer.makeFunctionCall(transformerInfo.method, [writer.makeString(value)])
const statements = [
writer.makeAssign(`${value}Transformed`, this.target, transformCallExpression, true),
this.targetConvertor.convertorSerialize(param, `${value}Transformed`, writer)
]
return writer.makeBlock(statements, false)
}
convertorDeserialize(bufferName: string, deserializerName: string, assigneer: ExpressionAssigner, writer: LanguageWriter): LanguageStatement {
if (writer.language === Language.CPP) {
return this.targetConvertor.convertorDeserialize(bufferName, deserializerName, assigneer, writer)
}
if (idl.isReferenceType(this.target)) {
writer.addFeature(this.target)
}
const targetDeserialize = this.targetConvertor.convertorDeserialize(
`${bufferName}D`,
deserializerName,
(expr) => writer.makeAssign(`${bufferName}Deserialized`, this.target, expr, true),
writer,
)
const transformerInfo = getTransformer(this.library, this.target, this.managedDeclaration)
this.addImport(transformerInfo, writer)
const transformCallExpression = transformerInfo.ns
? writer.makeMethodCall(transformerInfo.ns, transformerInfo.method, [writer.makeString(`${bufferName}Deserialized`)])
: writer.makeFunctionCall(transformerInfo.method, [writer.makeString(`${bufferName}Deserialized`)])
return writer.makeBlock([
targetDeserialize,
assigneer(transformCallExpression)
], false)
}
}
export function createOutArgConvertor(library: PeerLibrary, type: idl.IDLType|undefined, otherParams: string[]): ArgConvertor | undefined {
if (type && idl.isContainerType(type) && idl.IDLContainerUtils.isPromise(type)) {
const param = (entropy: number) => `outputArgumentForReturningPromise${entropy || ''}`
let paramEntropy = 0
while (otherParams?.includes(param(paramEntropy)))
++paramEntropy;
return new PromiseOutArgConvertor(library, param(paramEntropy), type)
}
return undefined
}
function withDiscriminatorCondition(
library: LibraryInterface,
convertors: ArgConvertor[],
value: string,
discriminator: LanguageExpression,
type: idl.IDLType,
writer: LanguageWriter,
): LanguageExpression {
if ([Language.CPP, Language.KOTLIN].includes(writer.language)) return discriminator
if (!idl.isReferenceType(type)) return discriminator
if (idl.isReferenceType(type)) {
const resolved = library.resolveTypeReference(type)!
const isTuple = idl.isInterface(resolved) && idl.getExtAttribute(resolved, idl.IDLExtendedAttributes.Entity) === idl.IDLEntity.Tuple
if (isTuple && writer.language == Language.ARKTS) {
return writer.makeAnd(
discriminator,
writer.makeEquals(
[writer.makeArrayLength(value), writer.makeString(`${resolved.properties.length}`)]
)
)
}
}
return discriminator
}
function getSourceType(convertor: ArgConvertor): idl.IDLType {
if (convertor instanceof TransformOnSerializeConvertor) {
return convertor.getSourceType()
}
return convertor.idlType
}