* Copyright (c) 2024-2025 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 { generatorConfiguration } from "../config"
import { toIdlType, toIdlTypeList } from "../from-idl/deserialize"
import * as idl from "../idl"
import { Language } from "../Language"
import { IdlNameConvertor } from "../LanguageWriters"
import { ArgConvertor } from "../LanguageWriters/ArgConvertors"
import { StructureNameConvertor } from "../LanguageWriters/convertors/CppConvertors"
import { LibraryInterface } from "../LibraryInterface"
import { generateSyntheticIdlNodeName } from "../peer-generation/idl/common"
import { isMaterialized } from "../peer-generation/isMaterialized"
import { LayoutManager } from "../peer-generation/LayoutManager"
import { createCachedReferenceResolver, ReferenceResolver } from "../peer-generation/ReferenceResolver"
import { toDeclaration } from "../peer-generation/toDeclaration"
import { IdlTransformer } from "./IdlTransformer"
type ProduceResult = {
anchor: string,
produced: idl.IDLEntry,
}
export function genericsTransformer(
files: idl.IDLFile[],
options: {
ignore: ((node: idl.IDLNode) => boolean)[],
ignoreGenerics: string[],
}
): idl.IDLFile[] {
const optionsResolver = createCachedReferenceResolver(files)
options.ignore.push(ignoreConfigRule(options!.ignoreGenerics!),
createIgnoreMaterializedRule(optionsResolver), createIgnoreResourceRule(optionsResolver))
const defaultsGenericsTransformer = new DefaultGenericsTransformer(createCachedReferenceResolver(files))
files = files.map(it => defaultsGenericsTransformer.visit(it)).map(idl.linkParentBack)
const resolver = createCachedReferenceResolver(files)
const resolverAdditionals = new Map<string, idl.IDLEntry>()
const library: LibraryInterface = {
language: Language.CPP,
files: files,
typeConvertor: function (param: string, type: idl.IDLType, isOptionalParam?: boolean): ArgConvertor {
throw new Error('Function not implemented.');
},
declarationConvertor: function (param: string, type: idl.IDLReferenceType, declaration: idl.IDLEntry | undefined): ArgConvertor {
throw new Error('Function not implemented.');
},
createTypeNameConvertor: function (language: Language): IdlNameConvertor {
throw new Error('Function not implemented.');
},
createContinuationCallbackReference: function (continuationType: idl.IDLType): idl.IDLReferenceType {
throw new Error('Function not implemented.');
},
getCurrentContext: function (): string | undefined {
throw new Error('Function not implemented.');
},
layout: LayoutManager.Empty(),
libraryPrefix: '',
resolveTypeReference: function (type: idl.IDLReferenceType, options?: { terminalImports?: boolean, unresolvedOk?: boolean }): idl.IDLEntry | undefined {
if (resolverAdditionals.has(type.name))
return resolverAdditionals.get(type.name)!
return resolver.resolveTypeReference(type, options)
},
toDeclaration: function (type: idl.IDLNode): idl.IDLNode {
if (!idl.isType(type) && !idl.isEntry(type))
throw new Error("toDeclaration can be performed only on types or entries!")
return toDeclaration(type, this)
}
}
const production = new Array<ProduceResult>()
const nameConvertor = new StructureNameConvertor(library)
const monomorphizeReferencesTransformer = new GenericsTransformer(library, produced => {
const anchorDecl = library.resolveTypeReference(idl.createReferenceType(produced.anchor))!
const producedFqn = produced.anchor.split('.').slice(0, -1).concat(produced.produced.name).join('.')
if (!resolverAdditionals.has(producedFqn)) {
resolverAdditionals.set(producedFqn, produced.produced)
production.push(produced)
produced.produced.parent = anchorDecl.parent
}
}, {
nameConvertor: (node) => nameConvertor.convert(node),
ignore: options.ignore,
ignoreGenerics: options.ignoreGenerics,
})
files = files.map(it => monomorphizeReferencesTransformer.visit(it)).map(idl.linkParentBack)
const debugInsertedEntries = new Set<string>()
const insertProductionTransformer = new InsertProductionTransformer(production, {
debugInserted: (node) => debugInsertedEntries.add(node),
})
files = files.map(it => insertProductionTransformer.visit(it)).map(idl.linkParentBack)
const debugMissedEntries = new Set(production.filter(it => !debugInsertedEntries.has(it.anchor)).map(it => it.anchor))
if (debugMissedEntries.size) {
throw new Error(`Failed to insert generated generics for entries ${Array.from(debugMissedEntries).join(', ')}`)
}
return files
}
class DefaultGenericsTransformer extends IdlTransformer {
constructor(private resolver: ReferenceResolver) {
super()
}
visit(node: idl.IDLFile): idl.IDLFile
visit(node: idl.IDLType): idl.IDLType
visit(node: idl.IDLNode): idl.IDLNode {
if (idl.isReferenceType(node)) {
const decl = this.resolver.resolveTypeReference(node)
if (!decl) {
console.error(`Can not resolve reference for inplacing default generics ${node.name} in file ${node.fileName ?? '<unknown>'}`)
return this.visitEachChild(node)
}
if (!idl.isTypedef(decl) && !idl.isInterface(decl) && !idl.isCallback(decl)) {
return this.visitEachChild(node)
}
if ((decl.typeParameters?.length ?? 0) > (node.typeArguments?.length ?? 0)) {
const defaults: (undefined | idl.IDLType)[] = decl.extendedAttributes
?.find(it => it.name === idl.IDLExtendedAttributes.TypeParametersDefaults)
?.typesValue?.slice()
?.map(it => this.visit(it)) ?? []
while (defaults.length < decl.typeParameters!.length) {
defaults.unshift(undefined)
}
const typeArguments = Array.from(node.typeArguments ?? [])
while (decl.typeParameters!.length > typeArguments.length) {
if (defaults[typeArguments.length] === undefined) {
const declarationDetails = `${idl.getFQName(decl)}<${(decl.typeParameters ?? [])
.map((it, index) => `${it}${defaults[index] ? '='+defaults[index] : ''}`)
.join(', ')}>`
const referenceDetails = `${node.name}<${typeArguments.map(it => idl.printType(it)).join(", ")}>`
throw new Error(`Can not validate reference to ${declarationDetails} declaration: reference ${referenceDetails} has not enough generic arguments or declaration does not have enough default generic values. Reference defined at: ${node.fileName}`)
}
typeArguments.push(defaults[typeArguments.length]!)
}
return idl.createReferenceType(
node.name,
typeArguments,
idl.cloneNodeInitializer(node),
)
}
}
return this.visitEachChild(node)
}
}
class GenericsTransformer extends IdlTransformer {
private meaninglessFieldsTransformer = new RemoveMeaninglessFieldsTransformer()
constructor(
private resolver: ReferenceResolver,
private producer: (result: ProduceResult) => void,
private options: {
ignore: ((node: idl.IDLNode) => boolean)[],
nameConvertor: (type: idl.IDLType) => string,
ignoreGenerics: string[],
}
) {
super()
}
visit(node: idl.IDLFile): idl.IDLFile
visit(node: idl.IDLReferenceType): idl.IDLReferenceType
visit(node: idl.IDLNode): idl.IDLNode
visit(node: idl.IDLNode): idl.IDLNode {
if (idl.isReferenceType(node)) {
const ref = this.visitEachChild(node) as idl.IDLReferenceType
if (!ref.typeArguments?.length || hasTypeParameterTypeChild(ref)) {
return ref
}
const resolved = this.resolver.resolveTypeReference(ref)
if (!resolved) {
throw new Error(`Can not resolve ${ref.name}`)
}
if (this.options.ignore.some(it =>
it(ref) || it(resolved)) ||
this.options.ignoreGenerics.includes(idl.getFQName(resolved))) {
return ref
}
if (!idl.isTypedef(resolved) && !idl.isInterface(resolved) && !idl.isCallback(resolved)) {
throw new Error(`Unsupported generics target ${resolved.kind}`)
}
const inplacedRef = idl.createReferenceType(
idl.deriveQualifiedNameFrom(monomorphisedEntryName(resolved, ref.typeArguments, this.options), resolved),
undefined,
idl.cloneNodeInitializer(ref)
)
if (!this.resolver.resolveTypeReference(inplacedRef)) {
const monomorphizedEntry = this.visit(this.monomorphizeEntry(resolved, ref.typeArguments)) as idl.IDLEntry
this.producer({ anchor: idl.getFQName(resolved), produced: monomorphizedEntry })
}
return inplacedRef
}
return this.visitEachChild(node)
}
monomorphizeEntry<T extends idl.IDLEntry>(typedEntry: T, typeArguments: idl.IDLType[]): T {
if (!idl.isTypedef(typedEntry) && !idl.isInterface(typedEntry) && !idl.isCallback(typedEntry))
throw new Error(`Can not monomorphize ${typedEntry.kind}`)
if (typedEntry.typeParameters?.length != typeArguments.length)
throw new Error(`Trying to monomorphize entry ${typedEntry.name} that accepts ${typedEntry.typeParameters?.length} type parameters with ${typeArguments.length} type arguments`)
const monomorphizedEntry = idl.clone(typedEntry)
monomorphizedEntry.name = monomorphisedEntryName(typedEntry, typeArguments, this.options)
monomorphizedEntry.typeParameters = undefined
const nameToType = new Map(typedEntry.typeParameters.map((name, index) => [name, typeArguments[index]]))
idl.updateEachChild(monomorphizedEntry, (node) => {
if (idl.isTypeParameterType(node)) {
if (!nameToType.has(node.name))
throw new Error(`Can not name ${node.name} in type parameters of ${typedEntry.name}: available are ${typedEntry.typeParameters?.join(", ")}`)
return idl.clone(nameToType.get(node.name)!)
}
return node
})
monomorphizedEntry.extendedAttributes ??= []
monomorphizedEntry.extendedAttributes.push({
name: idl.IDLExtendedAttributes.OriginalGenericName,
value: idl.getFQName(typedEntry),
typesValue: typeArguments,
})
this.correctTransformOnSerialize(monomorphizedEntry.extendedAttributes, typeArguments)
return this.meaninglessFieldsTransformer.visit(monomorphizedEntry)
}
correctTransformOnSerialize(
extendedAttributes: idl.IDLExtendedAttribute[],
typeArguments: idl.IDLType[],
) {
const transformOnSerializeAttribute = extendedAttributes.find(it => it.name === idl.IDLExtendedAttributes.TransformOnSerialize)
if (transformOnSerializeAttribute === undefined)
return
const targetType = toIdlType("", transformOnSerializeAttribute.value!)
if (idl.isReferenceType(targetType)) {
const monomorphizedReference = this.visit(idl.createReferenceType(
targetType.name,
typeArguments,
idl.cloneNodeInitializer(targetType),
))
transformOnSerializeAttribute.value = monomorphizedReference.name
}
}
}
class InsertProductionTransformer extends IdlTransformer {
private production: Map<string | undefined, idl.IDLEntry[]>
constructor(production: ProduceResult[], private options: { debugInserted: (node: string) => void }) {
super()
this.production = new Map()
for (const result of production) {
if (!this.production.has(result.anchor))
this.production.set(result.anchor, [])
this.production.get(result.anchor)!.push(result.produced)
}
}
visit<T extends idl.IDLNode>(node: T): T
visit(node: idl.IDLNode): idl.IDLNode {
if (idl.isFile(node)) {
if (node.entries.some(it => this.production.has(idl.getFQNameSafe(it)))) {
return idl.createFile(
node.entries.flatMap(it => {
const fqn = idl.getFQNameSafe(it)
if (this.production.has(fqn)) {
this.options.debugInserted(fqn!)
return this.production.get(fqn)!.concat(this.visit(it))
}
return this.visit(it)
}),
node.fileName,
node.packageClause,
idl.cloneNodeInitializer(node),
)
}
}
if (idl.isNamespace(node)) {
if (node.members.some(it => this.production.has(idl.getFQNameSafe(it)))) {
return idl.createNamespace(
node.name,
node.members.flatMap(it => {
const fqn = idl.getFQNameSafe(it)
if (this.production.has(fqn)) {
this.options.debugInserted(fqn!)
return this.production.get(fqn)!.concat(this.visit(it))
}
return this.visit(it)
}),
idl.cloneNodeInitializer(node)
)
}
}
return this.visitEachChild(node)
}
}
class RemoveMeaninglessFieldsTransformer extends IdlTransformer {
visit<T extends idl.IDLNode>(node: T): T
visit(node: idl.IDLNode): idl.IDLNode {
if (idl.isInterface(node) && node.properties.some(it => isMeaninglessFieldType(it.type))) {
return idl.createInterface(
node.name,
node.subkind,
node.inheritance.map(it => this.visit(it)),
node.constructors.map(it => this.visit(it)),
node.constants.map(it => this.visit(it)),
node.properties.filter(it => !isMeaninglessFieldType(it.type)).map(it => this.visit(it)),
node.methods.map(it => this.visit(it)),
node.callables.map(it => this.visit(it)),
node.typeParameters,
idl.cloneNodeInitializer(node),
)
}
if (idl.isMethod(node) && node.parameters.some(it => isMeaninglessFieldType(it.type))) {
return idl.createMethod(
node.name,
node.parameters.filter(it => !isMeaninglessFieldType(it.type)).map(it => this.visit(it)),
this.visit(node.returnType),
{
isAsync: node.isAsync,
isFree: node.isFree,
isOptional: node.isOptional,
isStatic: node.isStatic,
},
idl.cloneNodeInitializer(node),
node.typeParameters,
)
}
if (idl.isCallback(node) && node.parameters.some(it => isMeaninglessFieldType(it.type))) {
return idl.createCallback(
node.name,
node.parameters.filter(it => !isMeaninglessFieldType(it.type)).map(it => this.visit(it)),
this.visit(node.returnType),
idl.cloneNodeInitializer(node),
node.typeParameters,
)
}
return this.visitEachChild(node)
}
}
export function isInplacedGeneric(entry: idl.IDLEntry) {
return idl.hasExtAttribute(entry, idl.IDLExtendedAttributes.OriginalGenericName)
}
export function maybeRestoreGenerics(
maybeTransformedGeneric: idl.IDLReferenceType | idl.IDLEntry,
resolver: ReferenceResolver,
): idl.IDLReferenceType | undefined {
if (idl.isReferenceType(maybeTransformedGeneric)) {
const resolved = resolver.resolveTypeReference(maybeTransformedGeneric)
return resolved ? maybeRestoreGenerics(resolved, resolver) : undefined
}
if (maybeTransformedGeneric && idl.hasExtAttribute(maybeTransformedGeneric, idl.IDLExtendedAttributes.OriginalGenericName)) {
const originalName = idl.getExtAttribute(maybeTransformedGeneric, idl.IDLExtendedAttributes.OriginalGenericName)!
const typeArguments = idl.getExtAttributeTypesValue(maybeTransformedGeneric, idl.IDLExtendedAttributes.OriginalGenericName)
if (!typeArguments)
throw new Error(`Can not restore original generic type arguments for ${originalName}: no type arguments`)
return idl.createReferenceType(
originalName,
typeArguments,
)
}
return undefined
}
function ignoreConfigRule(ignoreGenerics: string[]) {
return (node: idl.IDLNode) => {
return idl.isEntry(node) && ignoreGenerics.includes(idl.getFQName(node))
}
}
function createIgnoreMaterializedRule(resolver: ReferenceResolver): (node: idl.IDLNode) => boolean {
return (node: idl.IDLNode) => idl.isInterface(node) && isMaterialized(node, resolver) && !idl.hasExtAttribute(node, idl.IDLExtendedAttributes.TransformOnSerialize)
}
function createIgnoreResourceRule(resolver: ReferenceResolver): (node: idl.IDLNode) => boolean {
return (node: idl.IDLNode) => {
if (!idl.isReferenceType(node)) {
return false
}
const declaration = resolver.resolveTypeReference(node)
if (!declaration) {
return false
}
return generatorConfiguration().forceResource.includes(declaration.name)
}
}
function monomorphisedEntryName(typedEntry: idl.IDLEntry, typeArguments: idl.IDLType[], options: { nameConvertor: (node: idl.IDLType) => string }): string {
return typedEntry.name + "_" + typeArguments.map(options.nameConvertor).join("_")
}
function hasTypeParameterTypeChild(node: idl.IDLNode): boolean {
let result = false
idl.forEachChild(node, (child) => {
if (idl.isTypeParameterType(child))
result = true
})
return result
}
function isMeaninglessFieldType(type: idl.IDLType): boolean {
return type === idl.IDLVoidType || type === idl.IDLUndefinedType ||
idl.isUnionType(type) && type.types.every(isMeaninglessFieldType) ||
idl.isOptionalType(type) && isMeaninglessFieldType(type.type)
}