diff --git a/doc/developer-guide/generic.md b/doc/developer-guide/generic.md new file mode 100644 index 00000000..c4760d0c --- /dev/null +++ b/doc/developer-guide/generic.md @@ -0,0 +1,159 @@ +# Generic Type Process + +## Generic Types +At present, the processing of generic types mainly focuses on generic functions and generic classes. + +### generic function +``` +function genericFunc(param: T): T { + return param; +} + +export function numberFunc() { + console.log(genericFunc(100)); +} +``` + +### generic class +``` +class GenericClass { + x: X; + + constructor(x: X) { + this.x = x; + } + echo(param: X) { + return param; + } +} + +const a = new GenericClass('string'); +console.log(a.echo('hello world')); +``` + +## Type Process +This compiler will perform multiple scans during the `syntax` processing stage, the processing of types runs through these scans. +After the `syntax` processing stage, basic types (such as ***number***, ***string***) will be passed directly to the next processing stage; complex types (such as ***class***, ***function***) will generate `Scope`s (such as ***ClassScope***, ***FunctionScope***) for processing in the `semantic` processing stage. +The creation of each `Scope` relies on the corresponding ***DeclarationNode*** in AST tree. Take the following case as an example to explain how the compiler processes generic types. + +- - - + +``` +// example.ts +function genericFunc(param: T): T { + return param; +} + +class GenericClass { + x: X; + + constructor(x: X) { + this.x = x; + } + echo(param: X) { + return param; + } +} + +const ret = genericFunc(100); +console.log(ret); + +const a = new GenericClass('string'); +const echoWord = a.echo('hello world'); +console.log(echoWord); +``` +- - - + +### Scope Scan +Starting from the root node of the syntax tree, traverse the entire AST: +- when a ***SourceFile*** node is scanned, a `GlobalScope` is created +- when a ***ClassDeclaration*** node is scanned, a `ClassScope` is created +- when a ***FunctionDeclaration*** node is scanned, a `FunctionScope` is created + +After this scan, the compiler will create a scope tree with `GlobalScope` as the root for each ts source file to organize the file content. + +
+ +
+ +- - - + +### Type Scan +In this scanning phase, the compiler mainly processes the following nodes in the AST: +- **VariableDeclarationNode**: create a type based on the initialization expression of the variable +- **Parameter**: create a type based on the type information of the parameter +- **ClassDeclarationNode**: create a `TSClass` type +- **InterfaceDeclarationNode**: create a `TSInterface` type +- **FunctionDeclarationNode**: create a `TSFunction` type + +After creating a new type, the type information will be stored in parent scope's ***namedTypeMap***. + +
+ +
+ +- - - + +### Variable scan +In the variable scan phase, the compiler will analyze and process the ***VariableDeclarationNode*** and ***Parameter*** starting from the root of the AST. At this time, the specialization of generic types may be performed. + +- - - + +#### Specialization of Generic Function +When processing the global variable `const ret = genericFunc(100);`, the specialization of the generic function will be performed. + +``` +The time for specializing a generic function is when the function is called. +``` + +Specialization of generic functions mainly performs the following two operations: +- generate a new `TSFunction` type based on the type arguments and the generic `TSFunction` type +- generate a new `FunctionScope` based on the type arguments and the generic `FunctionScope` + +
+ +
+ +The parameters and local variables in the new `FunctionScope` will not be processed at this time. We will process them uniformly when processing statements. + +- - - + +#### Specialization of Generic Class +When processing the global variable `const a = new GenericClass('string');`, the specialization of the generic class will be performed. + +``` +The time for specializing a generic class is when creating an class instance. +``` + +Specialization of generic classes mainly performs the following two operations: +- generate a new `TSClass` type based on the type arguments and the generic `TSClass` type +- generate a new `ClassScope` based on the type arguments and the generic `ClassScope` + +
+ +
+ +- - - + +### Statement Scan +In this scanning phase, the compiler generates the statements in `FunctionScope` based on the content of **FunctionDeclarationNode**. + +
+ +
+ +- - - + +Since the specialized function does not have a corresponding **FunctionDeclarationNode** on the AST tree, the generation of its statements relies on the generic `FunctionScope`: +- generate a specialized **ParameterArray** based on **ParameterArray** and type arguments of generic `FunctionScope` +- generate a specialized **VariableArray**(such as ***context***, ***this***) based on **VariableArray** and type arguments of generic `FunctionScope` +- generate new statements based on the **StatementArray** and type arguments of the generic `FunctionScope`. When processing a VariableStatement, after specializing this statement, add the local variable represented by the statement to **VariableArray**. + +
+ +
+ +- - - + +### specialized type generation +After these scanning phases, the specialization of a generic type is completed. \ No newline at end of file diff --git a/doc/developer-guide/img/generic_class_specialization.png b/doc/developer-guide/img/generic_class_specialization.png new file mode 100644 index 00000000..774fddd5 Binary files /dev/null and b/doc/developer-guide/img/generic_class_specialization.png differ diff --git a/doc/developer-guide/img/generic_function_specialization.png b/doc/developer-guide/img/generic_function_specialization.png new file mode 100644 index 00000000..e3245e15 Binary files /dev/null and b/doc/developer-guide/img/generic_function_specialization.png differ diff --git a/doc/developer-guide/img/scope_tree.png b/doc/developer-guide/img/scope_tree.png new file mode 100644 index 00000000..0614bad2 Binary files /dev/null and b/doc/developer-guide/img/scope_tree.png differ diff --git a/doc/developer-guide/img/specialized_statement_gen.png b/doc/developer-guide/img/specialized_statement_gen.png new file mode 100644 index 00000000..8b7bbebf Binary files /dev/null and b/doc/developer-guide/img/specialized_statement_gen.png differ diff --git a/doc/developer-guide/img/statement_scan.png b/doc/developer-guide/img/statement_scan.png new file mode 100644 index 00000000..acc4d4a4 Binary files /dev/null and b/doc/developer-guide/img/statement_scan.png differ diff --git a/doc/developer-guide/img/type_scan.png b/doc/developer-guide/img/type_scan.png new file mode 100644 index 00000000..fa5a7746 Binary files /dev/null and b/doc/developer-guide/img/type_scan.png differ diff --git a/src/expression.ts b/src/expression.ts index 2692955a..ae13f7fa 100644 --- a/src/expression.ts +++ b/src/expression.ts @@ -5,21 +5,30 @@ import ts from 'typescript'; import { ParserContext } from './frontend.js'; -import { ClosureEnvironment, FunctionScope } from './scope.js'; -import { Variable } from './variable.js'; +import { + ClassScope, + ClosureEnvironment, + FunctionScope, + Scope, + importSearchTypes, +} from './scope.js'; +import { Variable, Parameter } from './variable.js'; import { getCurScope, addSourceMapLoc, isTypeGeneric, processEscape, + processGenericType, + getTypeArgumentsFromParameters, + genericMethodSpecialization, } from './utils.js'; import { - TSArray, TSFunction, Type, TypeKind, - TypeResolver, builtinTypes, + TSTypeParameter, + TSClass, } from './type.js'; import { Logger } from './log.js'; import { SourceMapLoc } from './backend/binaryen/utils.js'; @@ -569,13 +578,15 @@ export default class ExpressionProcessor { } } } - /** in order to avoid there is narrowed type checking scope */ - let declNode = node; - const symbol = - this.parserCtx.typeChecker!.getSymbolAtLocation(node); - if (symbol && symbol.valueDeclaration) { - declNode = symbol.valueDeclaration; - exprType = this.typeResolver.generateNodeType(declNode); + if (exprType.kind == TypeKind.UNDEFINED) { + /** in order to avoid there is narrowed type checking scope */ + let declNode = node; + const symbol = + this.parserCtx.typeChecker!.getSymbolAtLocation(node); + if (symbol && symbol.valueDeclaration) { + declNode = symbol.valueDeclaration; + exprType = this.typeResolver.generateNodeType(declNode); + } } res.setExprType(exprType); break; @@ -660,145 +671,202 @@ export default class ExpressionProcessor { if ( callExprNode.expression.kind === ts.SyntaxKind.SuperKeyword ) { - res = new SuperExpression(args); - res.setExprType(this.typeResolver.generateNodeType(node)); + const newSuperExpression = new SuperExpression(args); + newSuperExpression.tsNode = ( + expr as SuperExpression + ).tsNode; + res = newSuperExpression; break; } - // iff a generic function is specialized and called - const origType = this.typeResolver.generateNodeType( - callExprNode.expression, - ) as TSFunction; - const originalFunctionScope = origType.belongedScope; - // without FunctionScope information, generic functions cannot be specialized - if (isTypeGeneric(origType) && originalFunctionScope) { - // the function name of the CallExpression is corrected to the specialized function name - let typeArguments: Type[] | undefined; - + // get the list of specialization types + const originalFuncType = expr.exprType as TSFunction; + let typeArguments: Type[] = []; + if (isTypeGeneric(originalFuncType)) { // explicitly declare specialization type typeArguments // e.g. // function genericFunc (v: T){...} // genericFunc(5); + + const typeParameters = originalFuncType.isMethod + ? originalFuncType.belongedClass!.typeArguments + ? originalFuncType.belongedClass!.typeArguments + : originalFuncType.typeArguments! + : originalFuncType.typeArguments!; if (callExprNode.typeArguments) { typeArguments = callExprNode.typeArguments.map((t) => { return this.typeResolver.generateNodeType(t); }); } // specialize by passing parameters - // e.g. - // function genericFunc (v: T){...} - // genericFunc('hello'); - if (!typeArguments) { - const _typeArguments: Type[] = []; + if (typeArguments.length == 0) { // argument type - const _arguments = callExprNode.arguments.map((t) => { + const argTypes = callExprNode.arguments.map((t) => { return this.typeResolver.generateNodeType(t); }); // paramter type - const _paramters = origType.getParamTypes(); - - // TODO: Handling optional parameters - for (let i = 0; i < _paramters.length; i++) { - if ( - isTypeGeneric(_paramters[i]) && - !isTypeGeneric(_arguments[i]) - ) { - if ( - _paramters[i].kind == - TypeKind.TYPE_PARAMETER - ) { - _typeArguments.push(_arguments[i]); - } else if ( - _paramters[i].kind == TypeKind.ARRAY - ) { - const elementType = ( - _arguments[i] as TSArray - ).elementType; - _typeArguments.push(elementType); - } - } - } - typeArguments = _typeArguments; + const formalParameters = + originalFuncType.getParamTypes(); + typeArguments = getTypeArgumentsFromParameters( + formalParameters, + typeParameters, + argTypes, + ); } - // there is a specialization types list + if (typeArguments.length > 0) { const typeNames = new Array(); typeArguments.forEach((v) => { - typeNames.push(`${v.kind}`); + if (v.kind !== TypeKind.TYPE_PARAMETER) { + typeNames.push(`${v.kind}`); + } }); - const typeSignature = '<' + typeNames.join(',') + '>'; - + const typeSignature = + typeNames.length > 0 + ? '<' + typeNames.join(',') + '>' + : ''; + const isUpdateTypeParameters = + typeArguments.filter((type) => isTypeGeneric(type)) + .length == typeArguments.length; if ( callExprNode.expression.kind === ts.SyntaxKind.Identifier ) { - let genericInheritance = false; - typeArguments.forEach((t) => { - if (isTypeGeneric(t)) { - genericInheritance = true; - } - }); - if (!genericInheritance) { + const newFuncType = processGenericType( + originalFuncType, + typeArguments, + typeParameters, + this.parserCtx, + ); + if (!isUpdateTypeParameters) { const newIdentifierName = (expr as IdentifierExpression) .identifierName + typeSignature; expr = new IdentifierExpression( newIdentifierName, ); - - // the function type of the CallExpression is corrected to the specialized function type const specializedType = this.parserCtx.currentScope!.findIdentifier( newIdentifierName, + true, + importSearchTypes.Type, ); if (specializedType) expr.setExprType(specializedType as Type); + } else { + expr = new IdentifierExpression( + ( + expr as IdentifierExpression + ).identifierName, + ); + expr.setExprType(newFuncType); } } else if ( callExprNode.expression.kind === ts.SyntaxKind.PropertyAccessExpression ) { - const classType = origType.belongedClass!; - // if a generic function in a generic class is called, it will be processed according to the logic for processing generic class - if (!classType.typeArguments) { - const propertyName = ( - (expr as PropertyAccessExpression) - .propertyExpr as IdentifierExpression + // process class method + const propertyAccess = + expr as PropertyAccessExpression; + if ( + propertyAccess.propertyAccessExpr instanceof + IdentifierExpression && + propertyAccess.propertyAccessExpr + .exprType instanceof TSClass + ) { + const identifierName = ( + propertyAccess.propertyAccessExpr as IdentifierExpression ).identifierName; - const newPropertyName = - propertyName + typeSignature; - const newPropertyIdentifier = - new IdentifierExpression(newPropertyName); - let res = classType.getMethod(newPropertyName); - if (!res.method) { - const origType = - classType.getMethod(propertyName); - TypeResolver.specializeClassMethod( - classType, - propertyName, - typeArguments, + const ownerVariable = + this.parserCtx.currentScope!.findIdentifier( + identifierName, + ) as Variable; + const classType = + ownerVariable.varType as TSClass; + const methodName = ( + propertyAccess.propertyExpr as IdentifierExpression + ).identifierName; + + /** + * class A { + * a: number; + * echo(param: T) {...}; + * } + * const a = new A(); + * this class type does not contain 'typeParameters', and newExpression does not contain 'typeArguments'. + */ + if ( + !classType.typeArguments && + originalFuncType.typeArguments + ) { + if ( + !isUpdateTypeParameters && + originalFuncType.belongedScope + ) { + const newMethodName = + methodName + typeSignature; + const newPropertyIdentifier = + new IdentifierExpression( + newMethodName, + ); + let res = + classType.getMethod(newMethodName); + if (!res.method) { + const origType = + classType.getMethod(methodName); + genericMethodSpecialization( + origType.method!.type, + typeArguments, + this.parserCtx, + ); + res = + classType.getMethod( + newMethodName, + ); + } + if (res.method) + newPropertyIdentifier.setExprType( + res.method.type, + ); + const tsNode = expr.tsNode; + expr = new PropertyAccessExpression( + ( + expr as PropertyAccessExpression + ).propertyAccessExpr, + newPropertyIdentifier, + ); + expr.tsNode = tsNode; + if (res.method) + expr.setExprType(res.method.type); + } + } else { + const propertyAccessExpr = + new IdentifierExpression( + identifierName, + ); + propertyAccessExpr.setExprType(classType); + + const propertyType = classType.getMethod( + methodName, + originalFuncType.funcKind, + )!.method!.type; + const propertyExpr = + new IdentifierExpression(methodName); + propertyExpr.setExprType(propertyType); + + const tsNode = expr.tsNode; + expr = new PropertyAccessExpression( + propertyAccessExpr, + propertyExpr, ); - res = classType.getMethod(newPropertyName); + expr.tsNode = tsNode; + expr.setExprType(propertyType); } - if (res.method) - newPropertyIdentifier.setExprType( - res.method.type, - ); - - const tsNode = expr.tsNode; - expr = new PropertyAccessExpression( - ( - expr as PropertyAccessExpression - ).propertyAccessExpr, - newPropertyIdentifier, - ); - expr.tsNode = tsNode; - if (res.method) - expr.setExprType(res.method.type); } } } } + // get the list of specialization types end const callExpr = new CallExpression( expr, @@ -831,76 +899,131 @@ export default class ExpressionProcessor { const newExprNode = node; const expr = this.visitNode(newExprNode.expression); const newExpr = new NewExpression(expr); - if ( - expr.expressionKind === ts.SyntaxKind.Identifier && - (expr).identifierName === 'Array' - ) { - if (!newExprNode.typeArguments) { - if (!this.typeResolver.arrayTypeCheck(node)) { - throw new ExpressionError( - 'new Array without declare element type', - ); - } + if (newExprNode.arguments !== undefined) { + const args = new Array(); + for (const arg of newExprNode.arguments) { + args.push(this.visitNode(arg)); } - let isLiteral = false; - if (newExprNode.arguments) { - /* Check if it's created from a literal */ - const argLen = newExprNode.arguments.length; - if (argLen > 1) { - isLiteral = true; - } else if (argLen === 1) { - const elem = newExprNode.arguments[0]; - const elemExpr = this.visitNode(elem); - if ( - elemExpr.exprType.kind !== TypeKind.NUMBER && - elemExpr.exprType.kind !== TypeKind.WASM_I32 && - elemExpr.exprType.kind !== TypeKind.WASM_I64 && - elemExpr.exprType.kind !== TypeKind.WASM_F32 && - elemExpr.exprType.kind !== TypeKind.WASM_F64 - ) { - isLiteral = true; + if (args.length > 0) + (newExpr as NewExpression).setArgs(args); + } + if (newExprNode.typeArguments) { + newExpr.setTypeArguments( + this.buildTypeArguments(newExprNode.typeArguments)!, + ); + } + + if (expr.expressionKind === ts.SyntaxKind.Identifier) { + if ( + (expr).identifierName === 'Array' + ) { + if (!newExprNode.typeArguments) { + if (!this.typeResolver.arrayTypeCheck(node)) { + throw new ExpressionError( + 'new Array without declare element type', + ); } } + let isLiteral = false; + if (newExprNode.arguments) { + /* Check if it's created from a literal */ + const argLen = newExprNode.arguments.length; + if (argLen > 1) { + isLiteral = true; + } else if (argLen === 1) { + const elem = newExprNode.arguments[0]; + const elemExpr = this.visitNode(elem); + if ( + elemExpr.exprType.kind !== + TypeKind.NUMBER && + elemExpr.exprType.kind !== + TypeKind.WASM_I32 && + elemExpr.exprType.kind !== + TypeKind.WASM_I64 && + elemExpr.exprType.kind !== + TypeKind.WASM_F32 && + elemExpr.exprType.kind !== TypeKind.WASM_F64 + ) { + isLiteral = true; + } + } - if (isLiteral) { - const elemExprs = newExprNode.arguments.map((a) => { - return this.visitNode(a); - }); - newExpr.setArrayLen(argLen); - newExpr.setArgs(elemExprs); - } else if (argLen === 1) { - newExpr.setLenExpr( - this.visitNode(newExprNode.arguments[0]), - ); + if (isLiteral) { + const elemExprs = newExprNode.arguments.map( + (a) => { + return this.visitNode(a); + }, + ); + newExpr.setArrayLen(argLen); + newExpr.setArgs(elemExprs); + } else if (argLen === 1) { + newExpr.setLenExpr( + this.visitNode(newExprNode.arguments[0]), + ); + } + /* else no arguments */ + } else { + newExpr.setLenExpr(new NumberLiteralExpression(0)); } - /* else no arguments */ - } else { - newExpr.setLenExpr(new NumberLiteralExpression(0)); - } - - if (newExprNode.typeArguments) { - newExpr.setTypeArguments( - this.buildTypeArguments(newExprNode.typeArguments)!, + newExpr.setExprType( + this.typeResolver.generateNodeType(node), ); - } - - newExpr.setExprType( - this.typeResolver.generateNodeType(node), - ); - } else { - if (newExprNode.arguments !== undefined) { - const args = new Array(); - for (const arg of newExprNode.arguments) { - args.push(this.visitNode(arg)); + } else { + // handling generic types chain + if ( + expr.exprType instanceof TSClass && + isTypeGeneric(expr.exprType) + ) { + const genericClassType = expr.exprType; + const typeParameters = + genericClassType.typeArguments; + + if (typeParameters) { + let typeArguments = new Array(); + if (newExpr.newArgs) { + // argument type + const argTypes: Type[] = []; + for (const arg of newExpr.newArgs) { + argTypes.push(arg.exprType); + } + // paramter type + const formalParameters = + genericClassType.ctorType.getParamTypes(); + typeArguments = + getTypeArgumentsFromParameters( + formalParameters, + typeParameters, + argTypes, + ); + } else if (newExpr.typeArguments) { + typeArguments = newExpr.typeArguments; + } + if (typeArguments.length > 0) { + const newClassType = processGenericType( + genericClassType, + typeArguments, + typeParameters, + this.parserCtx, + ); + const newIdentifierExpression = + new IdentifierExpression( + (newClassType as TSClass).className, + ); + newIdentifierExpression.setExprType( + newClassType, + ); + const newNewExpr = new NewExpression( + newIdentifierExpression, + newExpr.newArgs, + ); + newNewExpr.setExprType(newClassType); + res = newNewExpr; + break; + } + } } - (newExpr as NewExpression).setArgs(args); } } - if (newExprNode.typeArguments) { - newExpr.setTypeArguments( - this.buildTypeArguments(newExprNode.typeArguments)!, - ); - } newExpr.setExprType(this.typeResolver.generateNodeType(node)); res = newExpr; @@ -1002,7 +1125,6 @@ export default class ExpressionProcessor { } case ts.SyntaxKind.SuperKeyword: { res = new SuperExpression(); - res.setExprType(this.typeResolver.generateNodeType(node)); break; } case ts.SyntaxKind.SpreadElement: { @@ -1067,4 +1189,698 @@ export default class ExpressionProcessor { } return types; } + + specializeExpression( + expr: Expression, + typeArguments: Type[], + typeParameters: TSTypeParameter[], + currentFuncScope: Scope, + ): Expression { + let res = expr; + const exprType = expr.exprType; + if (typeArguments.length == 0 || typeParameters.length == 0) return res; + switch (expr.expressionKind) { + case ts.SyntaxKind.Identifier: { + const identifierExpression = expr as IdentifierExpression; + let identifierName = identifierExpression.identifierName; + if (identifierName == 'undefined') { + return expr; + } + if (identifierName == 'this') { + const newIdentifierExpression = new IdentifierExpression( + identifierName, + ); + const thisVarType = processGenericType( + identifierExpression.exprType, + typeArguments, + typeParameters, + this.parserCtx, + ); + newIdentifierExpression.setExprType(thisVarType); + res = newIdentifierExpression; + return res; + } + + const typeArgumentsSignature = new Array(); + const ret = currentFuncScope.findIdentifier(identifierName); + if (ret) { + if (ret instanceof TSClass || ret instanceof ClassScope) { + if ( + isTypeGeneric(exprType) && + (exprType as TSClass).typeArguments + ) { + const types = (exprType as TSClass).typeArguments!; + types.forEach((type) => { + const index = typeParameters.findIndex((t) => { + return t.name === type.name; + }); + if (index == -1) { + throw new ExpressionError( + `${type.name} not found in typeParameters`, + ); + } + if ( + typeArguments[index].kind !== + TypeKind.TYPE_PARAMETER + ) + typeArgumentsSignature.push( + `${typeArguments[index].kind}`, + ); + }); + } + } + if (ret instanceof FunctionScope) { + if (isTypeGeneric(exprType)) { + const types = (exprType as TSFunction) + .typeArguments!; + types.forEach((type) => { + const index = typeParameters.findIndex((t) => { + return t.name === type.name; + }); + if (index == -1) { + throw new ExpressionError( + `${type.name} not found in typeParameters`, + ); + } + if ( + typeArguments[index].kind !== + TypeKind.TYPE_PARAMETER + ) + typeArgumentsSignature.push( + `${typeArguments[index].kind}`, + ); + }); + } + } + const typeSignature = + typeArgumentsSignature.length > 0 + ? '<' + typeArgumentsSignature.join(',') + '>' + : ''; + identifierName = identifierName + typeSignature; + } + + const newIdentifierExpression = new IdentifierExpression( + identifierName, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newIdentifierExpression.setExprType(newExprType); + res = newIdentifierExpression; + break; + } + case ts.SyntaxKind.BinaryExpression: { + const binaryBinaryExpression = expr as BinaryExpression; + const leftExpr = this.specializeExpression( + binaryBinaryExpression.leftOperand, + typeArguments, + typeParameters, + currentFuncScope, + ); + const rightExpr = this.specializeExpression( + binaryBinaryExpression.rightOperand, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newBinaryExpression = new BinaryExpression( + binaryBinaryExpression.operatorKind, + leftExpr, + rightExpr, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newBinaryExpression.setExprType(newExprType); + res = newBinaryExpression; + break; + } + case ts.SyntaxKind.PrefixUnaryExpression: { + const prefixUnaryExpression = expr as UnaryExpression; + const newOperand = this.specializeExpression( + prefixUnaryExpression.operand, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newprefixUnaryExpression = new UnaryExpression( + ts.SyntaxKind.PrefixUnaryExpression, + prefixUnaryExpression.operatorKind, + newOperand, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newprefixUnaryExpression.setExprType(newExprType); + res = newprefixUnaryExpression; + break; + } + case ts.SyntaxKind.PostfixUnaryExpression: { + const postfixUnaryExpression = expr as UnaryExpression; + const newOperand = this.specializeExpression( + postfixUnaryExpression.operand, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newUnaryExpression = new UnaryExpression( + ts.SyntaxKind.PostfixUnaryExpression, + postfixUnaryExpression.operatorKind, + newOperand, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newUnaryExpression.setExprType(newExprType); + res = newUnaryExpression; + break; + } + case ts.SyntaxKind.ConditionalExpression: { + const conditionalExpression = expr as ConditionalExpression; + const newCondition = this.specializeExpression( + conditionalExpression.condtion, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newTrueExpr = this.specializeExpression( + conditionalExpression.whenTrue, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newFalseExpr = this.specializeExpression( + conditionalExpression.whenFalse, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newConditionalExpression = new ConditionalExpression( + newCondition, + newTrueExpr, + newFalseExpr, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newConditionalExpression.setExprType(newExprType); + res = newConditionalExpression; + break; + } + case ts.SyntaxKind.CallExpression: { + const callExpression = expr as CallExpression; + let callExpr = callExpression.callExpr; + const args = new Array( + callExpression.callArgs.length, + ); + for (let i = 0; i != args.length; ++i) { + args[i] = this.specializeExpression( + callExpression.callArgs[i], + typeArguments, + typeParameters, + currentFuncScope, + ); + } + + if (callExpr.expressionKind === ts.SyntaxKind.Identifier) { + const identifierExpression = + callExpr as IdentifierExpression; + const exprType = + identifierExpression.exprType as TSFunction; + if (isTypeGeneric(exprType)) { + const typeArguments: Type[] = []; + for (let idx = 0; idx < args.length; idx++) { + typeArguments.push(args[idx].exprType); + } + callExpr = this.specializeExpression( + identifierExpression, + typeArguments, + exprType.typeArguments!, + currentFuncScope, + ); + } + } else if ( + callExpr.expressionKind === + ts.SyntaxKind.PropertyAccessExpression + ) { + const propertyAccessExpression = + callExpr as PropertyAccessExpression; + callExpr = this.specializeExpression( + propertyAccessExpression, + typeArguments, + typeParameters, + currentFuncScope, + ); + } + const newCallExpression = new CallExpression(callExpr, args); + if (callExpr instanceof PropertyAccessExpression) + callExpr.parent = newCallExpression; + + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newCallExpression.setExprType(newExprType); + res = newCallExpression; + break; + } + case ts.SyntaxKind.PropertyAccessExpression: { + const propertyAccessExpression = + expr as PropertyAccessExpression; + let propertyAccessExpr = + propertyAccessExpression.propertyAccessExpr; + let propertyExpr = propertyAccessExpression.propertyExpr; + + if ( + propertyAccessExpr.exprType instanceof TSClass && + isTypeGeneric(propertyAccessExpr.exprType) + ) { + const ownerType = propertyAccessExpr.exprType; + let propertyName = (propertyExpr as IdentifierExpression) + .identifierName; + let propertyType: Type = propertyExpr.exprType; + + propertyAccessExpr = this.specializeExpression( + propertyAccessExpr, + typeArguments, + typeParameters, + currentFuncScope, + ); + // method call + if (propertyExpr.exprType instanceof TSFunction) { + const funcKind = propertyExpr.exprType.funcKind; + if (ownerType.typeArguments) { + propertyType = ( + propertyAccessExpr.exprType as TSClass + ).getMethod(propertyName, funcKind)!.method!.type; + } else { + const typeArgumentsSignature: Array = []; + const _typeParameters = + propertyExpr.exprType.typeArguments; + if (_typeParameters) { + typeArguments.forEach((t) => { + if (t.kind !== TypeKind.TYPE_PARAMETER) + typeArgumentsSignature.push( + `${t.kind}`, + ); + }); + } + const typeSignature = + typeArgumentsSignature.length > 0 + ? '<' + + typeArgumentsSignature.join(',') + + '>' + : ''; + propertyName = propertyName + typeSignature; + propertyType = ( + propertyAccessExpr.exprType as TSClass + ).getMethod(propertyName, funcKind)!.method!.type; + } + } else { + // field access + //member field + (propertyAccessExpr.exprType as TSClass).fields.forEach( + (f) => { + if (f.name == propertyName) { + propertyType = f.type; + } + }, + ); + //static field + ( + propertyAccessExpr.exprType as TSClass + ).staticFields.forEach((f) => { + if (f.name == propertyName) { + propertyType = f.type; + } + }); + } + propertyExpr = new IdentifierExpression(propertyName); + propertyExpr.setExprType(propertyType); + } else { + propertyAccessExpr = this.specializeExpression( + propertyAccessExpr, + typeArguments, + typeParameters, + currentFuncScope, + ); + propertyExpr = this.specializeExpression( + propertyExpr, + typeArguments, + typeParameters, + currentFuncScope, + ); + } + + const newPropertyAccessExpression = + new PropertyAccessExpression( + propertyAccessExpr, + propertyExpr, + ); + newPropertyAccessExpression.setExprType(propertyExpr.exprType); + res = newPropertyAccessExpression; + break; + } + case ts.SyntaxKind.ParenthesizedExpression: { + const parenthesizedExpression = expr as ParenthesizedExpression; + const newParentesizedExpr = this.specializeExpression( + parenthesizedExpression.parentesizedExpr, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newParenthesizedExpression = new ParenthesizedExpression( + newParentesizedExpr, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newParenthesizedExpression.setExprType(newExprType); + res = newParenthesizedExpression; + break; + } + case ts.SyntaxKind.NewExpression: { + const newExpression = expr as NewExpression; + if (!newExpression.newArgs && !newExpression.typeArguments) + return newExpression; + + const args: Array = []; + if (newExpression.newArgs) { + for (let i = 0; i != newExpression.newArgs.length; ++i) { + const argExpr = this.specializeExpression( + newExpression.newArgs[i], + typeArguments, + typeParameters, + currentFuncScope, + ); + args.push(argExpr); + } + } + if ( + newExpression.newExpr.expressionKind === + ts.SyntaxKind.Identifier + ) { + const identifierExpression = + newExpression.newExpr as IdentifierExpression; + const newIdentifierExpression = this.specializeExpression( + identifierExpression, + typeArguments, + typeParameters, + currentFuncScope, + ); + res = new NewExpression(newIdentifierExpression, args); + res.setExprType(newIdentifierExpression.exprType); + } + break; + } + case ts.SyntaxKind.ObjectLiteralExpression: { + const objectLiteralExpression = expr as ObjectLiteralExpression; + const fields = new Array(); + const values = new Array(); + for (const f of objectLiteralExpression.objectFields) { + fields.push( + this.specializeExpression( + f, + typeArguments, + typeParameters, + currentFuncScope, + ) as IdentifierExpression, + ); + } + for (const v of objectLiteralExpression.objectValues) { + values.push( + this.specializeExpression( + v, + typeArguments, + typeParameters, + currentFuncScope, + ), + ); + } + + const newObjectLiteralExpression = new ObjectLiteralExpression( + fields, + values, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newObjectLiteralExpression.setExprType(newExprType); + res = newObjectLiteralExpression; + break; + } + case ts.SyntaxKind.ArrayLiteralExpression: { + const arrayLiteralExpression = expr as ArrayLiteralExpression; + const elements = new Array(); + for (const elem of arrayLiteralExpression.arrayValues) { + elements.push( + this.specializeExpression( + elem, + typeArguments, + typeParameters, + currentFuncScope, + ), + ); + } + const newArrayLiteralExpression = new ArrayLiteralExpression( + elements, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newArrayLiteralExpression.setExprType(newExprType); + res = newArrayLiteralExpression; + break; + } + case ts.SyntaxKind.AsExpression: { + const asExpression = expr as AsExpression; + const newExpr = this.specializeExpression( + asExpression.expression, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newAsExpression = new AsExpression(newExpr); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newAsExpression.setExprType(newExprType); + res = newAsExpression; + break; + } + case ts.SyntaxKind.ElementAccessExpression: { + const elementAccessExprNode = expr as ElementAccessExpression; + const newAccessExpr = this.specializeExpression( + elementAccessExprNode.accessExpr, + typeArguments, + typeParameters, + currentFuncScope, + ); + const newArgExpr = this.specializeExpression( + elementAccessExprNode.argExpr, + typeArguments, + typeParameters, + currentFuncScope, + ); + + const newElementAccessExpression = new ElementAccessExpression( + newAccessExpr, + newArgExpr, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newElementAccessExpression.setExprType(newExprType); + res = newElementAccessExpression; + break; + } + case ts.SyntaxKind.FunctionDeclaration: + case ts.SyntaxKind.FunctionExpression: + case ts.SyntaxKind.ArrowFunction: + case ts.SyntaxKind.MethodDeclaration: { + const functionExpression = expr as FunctionExpression; + const funcScope = functionExpression.funcScope; + const newFuncScope = new FunctionScope(currentFuncScope); + funcScope.specialize(newFuncScope); + // specialize this new FunctionScope + newFuncScope.setClassName(funcScope.className); + newFuncScope.debugFilePath = funcScope.debugFilePath; + newFuncScope.setFuncName(funcScope.funcName); + const newFuncType = isTypeGeneric(funcScope.funcType) + ? processGenericType( + funcScope.funcType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : funcScope.funcType; + newFuncScope.setFuncType(newFuncType as TSFunction); + newFuncScope.setGenericOwner(funcScope); + funcScope.addSpecializedScope(funcScope.funcName, newFuncScope); + newFuncScope.hasFreeVar = funcScope.hasFreeVar; + newFuncScope.mangledName = + newFuncScope.parent!.mangledName + + '|' + + newFuncScope.getName(); + funcScope.paramArray.forEach((v) => { + let varType = v.varType; + let initExpression = v.initExpression; + if (typeArguments) { + varType = processGenericType( + v.varType, + typeArguments, + typeParameters, + this.parserCtx, + ); + initExpression = initExpression + ? this.specializeExpression( + initExpression, + typeArguments, + typeParameters, + newFuncScope, + ) + : initExpression; + } + const new_parameter = new Parameter( + v.varName, + varType, + v.varModifiers, + v.varIndex, + v.isOptional, + v.destructuring, + initExpression, + v.isLocalVar(), + ); + newFuncScope.addParameter(new_parameter); + }); + funcScope.varArray.forEach((v) => { + if (v.varName == '@context') { + const contextVar = new Variable( + '@context', + v.varType, + v.varModifiers, + v.varIndex, + v.isLocalVar(), + v.initExpression, + ); + contextVar.scope = newFuncScope; + newFuncScope.contextVariable = contextVar; + newFuncScope.addVariable(contextVar); + } + }); + + funcScope.statements.forEach((s) => { + const stmt = + this.parserCtx.statementSpecializationProcessor.processStatement( + s, + typeArguments, + typeParameters, + currentFuncScope, + ); + newFuncScope.addStatement(stmt); + }); + + const newFunctionExpression = new FunctionExpression( + newFuncScope, + ); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newFunctionExpression.setExprType(newExprType); + res = newFunctionExpression; + break; + } + case ts.SyntaxKind.SuperKeyword: { + const superExpression = expr as SuperExpression; + const args: Array = []; + if (superExpression.callArgs) { + for (let i = 0; i != superExpression.callArgs.length; ++i) { + args.push( + this.specializeExpression( + superExpression.callArgs[i], + typeArguments, + typeParameters, + currentFuncScope, + ), + ); + } + } + const newSuperExpression = new SuperExpression(args); + const newExprType = isTypeGeneric(exprType) + ? processGenericType( + exprType, + typeArguments, + typeParameters, + this.parserCtx, + ) + : exprType; + newSuperExpression.setExprType(newExprType); + newSuperExpression.tsNode = superExpression.tsNode; + res = newSuperExpression; + break; + } + default: + res = expr; + } + return res; + } } diff --git a/src/frontend.ts b/src/frontend.ts index 109dae76..b5d09ccd 100644 --- a/src/frontend.ts +++ b/src/frontend.ts @@ -16,7 +16,10 @@ import { } from './scope.js'; import { VariableScanner, VariableInit } from './variable.js'; import ExpressionProcessor from './expression.js'; -import StatementProcessor from './statement.js'; +import { + StatementProcessor, + StatementSpecializationProcessor, +} from './statement.js'; import path from 'path'; import { Logger } from './log.js'; import { SyntaxError } from './error.js'; @@ -45,6 +48,7 @@ export class ParserContext { private _customTypeResolver; private _exprProcessor; private _stmtProcessor; + private _stmtSpecializationProcessor; private _sematicChecker; private _errorMessage: ts.Diagnostic[] | null = null; /** These types form a circular reference and need to be created as wasm types using rec. */ @@ -79,6 +83,8 @@ export class ParserContext { this._customTypeResolver = new CustomTypeResolver(this); this._exprProcessor = new ExpressionProcessor(this); this._stmtProcessor = new StatementProcessor(this); + this._stmtSpecializationProcessor = + new StatementSpecializationProcessor(this); this._sematicChecker = new SemanticChecker(this); } @@ -128,6 +134,7 @@ export class ParserContext { mangling(this.globalScopes); /* Step6: Add statements to scopes */ this._stmtProcessor.visit(); + this._stmtSpecializationProcessor.visit(); /* Step7: Resolve context type and this type */ this._customTypeResolver.visit(); /* Step8: Additional semantic check */ @@ -165,6 +172,10 @@ export class ParserContext { return this._stmtProcessor; } + get statementSpecializationProcessor(): StatementSpecializationProcessor { + return this._stmtSpecializationProcessor; + } + get semanticChecker(): SemanticChecker { return this._sematicChecker; } diff --git a/src/scope.ts b/src/scope.ts index ba97deac..4a788502 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -51,6 +51,7 @@ export enum importSearchTypes { Type = 'type', Function = 'function', Namespace = 'namespace', + Class = 'class', All = 'all', } @@ -72,8 +73,10 @@ export class Scope { private localIndex = -1; public mangledName = ''; private modifiers: ts.Node[] = []; - // iff this Scope is specialized + // iff this Scope is specialized scope private _genericOwner?: Scope; + // iff this Scope is a generic scope + private _specializedScopes?: Map; constructor(parent: Scope | null) { this.parent = parent; @@ -188,7 +191,7 @@ export class Scope { this.modifiers.push(modifier); } - setGenericOwner(genericOwner: Scope) { + setGenericOwner(genericOwner: Scope | undefined) { this._genericOwner = genericOwner; } @@ -196,6 +199,15 @@ export class Scope { return this._genericOwner; } + addSpecializedScope(typeSignature: string, s: Scope) { + if (!this._specializedScopes) this._specializedScopes = new Map(); + this._specializedScopes.set(typeSignature, s); + } + + get specializedScopes() { + return this._specializedScopes; + } + protected _nestFindScopeItem( name: string, searchFunc: (scope: Scope) => T | undefined, @@ -303,6 +315,23 @@ export class Scope { ); } + public findClassScope( + className: string, + nested = true, + ): ClassScope | undefined { + return this._nestFindScopeItem( + className, + (scope) => { + return scope.children.find((c) => { + return ( + c instanceof ClassScope && c.className === className // not mangled + ); + }) as ClassScope; + }, + nested, + ); + } + public findNamespaceScope( name: string, nested = true, @@ -388,6 +417,9 @@ export class Scope { /* Step4: Find namespace */ (matchStep(importSearchTypes.Namespace) && scope.findNamespaceScope(oriName, false)) || + /* Step5: Find class in current scope */ + (matchStep(importSearchTypes.Class) && + scope.findClassScope(oriName, false)) || undefined; if (res) { return res; @@ -542,7 +574,6 @@ export class Scope { scope.kind = this.kind; scope.name = this.name; scope.children = new Array(); - scope.parent = this.parent; scope.namedTypeMap = new Map(); scope.debugFilePath = this.debugFilePath; scope.tempVarArray = new Array(); @@ -551,7 +582,6 @@ export class Scope { scope.localIndex = this.localIndex; scope.mangledName = this.mangledName; scope.modifiers = this.modifiers; - if (this.genericOwner) scope.setGenericOwner(this.genericOwner); } } @@ -576,7 +606,6 @@ export class ClosureEnvironment extends Scope { super.specialize(scope); scope.kind = this.kind; scope.hasFreeVar = this.hasFreeVar; - scope.contextVariable = this.contextVariable; } } @@ -737,6 +766,7 @@ export class FunctionScope extends ClosureEnvironment { funcScope.functionType = this.functionType; funcScope._className = this._className; funcScope.realParamCtxType = this.realParamCtxType; + funcScope.mangledName = this.mangledName; funcScope.oriFuncName = this.oriFuncName; funcScope.debugLocations = new Array(); } @@ -813,6 +843,7 @@ export class ScopeScanner { /* block index to represent current block's count */ blockIndex = 0; static literal_obj_count = 0; + static specializedScopeCache = new Map[]>(); constructor(private parserCtx: ParserContext) { this.globalScopes = this.parserCtx.globalScopes; diff --git a/src/semantics/builder_context.ts b/src/semantics/builder_context.ts index 4eccda3b..f7fcbd50 100644 --- a/src/semantics/builder_context.ts +++ b/src/semantics/builder_context.ts @@ -260,6 +260,10 @@ export class BuildContext { return this.findSymbol(id, importSearchTypes.Function); } + findClass(id: string): SymbolValue | undefined { + return this.findSymbol(id, importSearchTypes.Class); + } + findNamespace(id: string): SymbolValue | undefined { return this.findSymbol(id, importSearchTypes.Namespace); } diff --git a/src/semantics/expression_builder.ts b/src/semantics/expression_builder.ts index 48752b7f..d24d8f89 100644 --- a/src/semantics/expression_builder.ts +++ b/src/semantics/expression_builder.ts @@ -788,7 +788,6 @@ function buildIdentiferExpression( context: BuildContext, ): SemanticsValue { const name = expr.identifierName; - if (name == 'undefined') { return new LiteralValue(Primitive.Undefined, undefined); } @@ -806,6 +805,10 @@ function buildIdentiferExpression( } if (!ret) { Logger.debug(`=== try find identifer "${name}" as Function Faield`); + ret = context.findClass(name); + } + if (!ret) { + Logger.debug(`=== try find identifer "${name}" as Class Faield`); ret = builtinTypes.get(name) ? builtinTypes.get(name) : context.findType(name); diff --git a/src/semantics/index.ts b/src/semantics/index.ts index e32b4303..b981323c 100644 --- a/src/semantics/index.ts +++ b/src/semantics/index.ts @@ -30,7 +30,11 @@ import { ObjectType, EnumType, } from './value_types.js'; -import { PredefinedTypeId, isNativeSignatureComment } from '../utils.js'; +import { + PredefinedTypeId, + isNativeSignatureComment, + processGenericType, +} from '../utils.js'; import { GetPredefinedType } from './predefined_types.js'; import { flattenFunction } from './flatten.js'; import { BuildContext, SymbolKey, SymbolValue } from './builder_context.js'; @@ -212,41 +216,10 @@ function createFunctionDeclareNode( reverse[0] = '@' + reverse[0]; name = reverse.reverse().join('|'); } - /* maybe can be replace to context.findSymbolKey(f.funcType) as FunctionType */ const func_type = createType(context, f.funcType) as FunctionType; const this_type = getMethodClassType(f, context); const parameters: VarDeclareNode[] = []; - if (f.genericOwner) { - const genericOwner = f.genericOwner as FunctionScope; - const specializedArgs = f.funcType.specializedArguments!; - genericOwner.paramArray.forEach((v) => { - const specializedType = TypeResolver.createSpecializedType( - v.varType, - specializedArgs, - (f.genericOwner as FunctionScope).funcType, - ); - const newParam = new Parameter( - v.varName, - specializedType, - v.varModifiers, - v.varIndex, - v.isOptional, - v.destructuring, - ); - if (v.initExpression) newParam.setInitExpr(v.initExpression); - newParam.setIsLocalVar(v.isLocalVar()); - newParam.needReBinding = v.needReBinding; - newParam.tsNode = v.tsNode; - f.addParameter(newParam); - }); - /* at this time, we set parameters for the specialized FunctionScope, - * so we need to initialize their index once - */ - f.resetLocalIndex(); - f.initVariableIndex(); - f.initParamIndex(); - } const paramArray = f.paramArray; for (let i = 0; i < paramArray.length; i++) { @@ -537,11 +510,6 @@ function generateFunctionScopeNodes( generateChildrenFunctionScope(context, scope); - if (scope.genericOwner) { - scope.genericOwner.statements.forEach((s) => { - scope.addStatement(s); - }); - } const statements = buildStatements(context, scope.statements); func.body.statements = statements; diff --git a/src/semantics/statement_builder.ts b/src/semantics/statement_builder.ts index 805111c9..ef6121de 100644 --- a/src/semantics/statement_builder.ts +++ b/src/semantics/statement_builder.ts @@ -38,9 +38,7 @@ import { } from './semantics_nodes.js'; import { Variable } from '../variable.js'; -import { TypeResolver } from '../type.js'; -import { isTypeGeneric } from '../utils.js'; -import { ClassScope, FunctionScope, Scope, ScopeKind } from '../scope.js'; +import { Scope, ScopeKind } from '../scope.js'; import { Statement, @@ -111,7 +109,6 @@ export function createFromVariable( v.scope ? v.scope!.mangledName : '' })" type: ${type}`, ); - return new VarDeclareNode( storageType, type!, @@ -136,62 +133,6 @@ export function createLocalSymbols( ): [VarDeclareNode[] | undefined, Map | undefined] { let varList: VarDeclareNode[] | undefined = undefined; let symbols: Map | undefined = undefined; - - if (scope.genericOwner) { - const origVarArr = scope.genericOwner.varArray; - if (scope instanceof FunctionScope) { - const specializedArgs = scope.funcType.specializedArguments!; - origVarArr.forEach((v) => { - if (v.varName == '@context' || v.varName == 'this') return; - if (!isTypeGeneric(v.varType)) { - scope.addVariable(v); - } else { - const newType = TypeResolver.createSpecializedType( - v.varType, - specializedArgs, - (scope.genericOwner as FunctionScope).funcType, - ); - const newVar = new Variable( - v.varName, - newType, - v.varModifiers, - v.varIndex, - v.isLocalVar(), - ); - if (v.initExpression) newVar.setInitExpr(v.initExpression); - newVar.needReBinding = v.needReBinding; - newVar.tsNode = v.tsNode; - scope.addVariable(newVar); - } - }); - } else if (scope instanceof ClassScope) { - const specializedArgs = scope.classType.specializedArguments!; - origVarArr.forEach((v) => { - if (v.varName == '@context' || v.varName == 'this') return; - if (!isTypeGeneric(v.varType)) { - scope.addVariable(v); - } else { - const newType = TypeResolver.createSpecializedType( - v.varType, - specializedArgs, - (scope.genericOwner as ClassScope).classType, - ); - const newVar = new Variable( - v.varName, - newType, - v.varModifiers, - v.varIndex, - v.isLocalVar(), - ); - if (v.initExpression) newVar.setInitExpr(v.initExpression); - newVar.needReBinding = v.needReBinding; - newVar.tsNode = v.tsNode; - scope.addVariable(newVar); - } - }); - } - } - const vararr = scope!.varArray; if (vararr.length > 0) { symbols = new Map(); diff --git a/src/semantics/type_creator.ts b/src/semantics/type_creator.ts index 36d04c34..b2a43aa7 100644 --- a/src/semantics/type_creator.ts +++ b/src/semantics/type_creator.ts @@ -6,8 +6,6 @@ import { Type, TSClass, - TsClassField, - TsClassFunc, FunctionKind, TypeKind, TSFunction, @@ -456,6 +454,14 @@ export function createObjectType( if (objectType) { return objectType as ObjectType; } + // filter out useless class types + if ( + clazz.typeKind == TypeKind.CLASS && + !clazz.isLiteral && + !clazz.belongedScope + ) { + return undefined; + } if (clazz.mangledName.includes(BuiltinNames.builtinTypeManglePrefix)) { if (IsBuiltinObject(clazz.className)) { return createBuiltinObjectType(clazz, context); diff --git a/src/semantics/value_types.ts b/src/semantics/value_types.ts index 088de955..2de32310 100644 --- a/src/semantics/value_types.ts +++ b/src/semantics/value_types.ts @@ -406,43 +406,6 @@ export class ObjectType extends ValueTypeWithArguments { } if (this.meta === other_type.meta) return true; - - if ( - !this.typeArguments && - !other_type.typeArguments && - (this.genericOwner || other_type.genericOwner) - ) { - const self_generic = this.genericOwner - ? (this.genericOwner as ObjectType) - : this; - const other_generic = other_type.genericOwner - ? (other_type.genericOwner as ObjectType) - : other_type; - return self_generic.meta === other_generic.meta; - } - - if (!(this.genericOwner && other_type.genericOwner)) return false; - - // is the same specialized object? - if (!this.genericOwner!.equals(other_type.genericOwner!)) return false; - // compare the specialTypeArguments - if ( - this.specialTypeArguments && - other_type.specialTypeArguments && - this.specialTypeArguments.length == - other_type.specialTypeArguments.length - ) { - for (let i = 0; i < this.specialTypeArguments.length; i++) { - if ( - !this.specialTypeArguments[i].equals( - other_type.specialTypeArguments[i], - ) - ) { - return false; - } - } - return true; - } return false; } diff --git a/src/statement.ts b/src/statement.ts index 1439e87e..b325c8a9 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -21,7 +21,7 @@ import { StringLiteralExpression, EnumerateKeysExpression, } from './expression.js'; -import { Scope, ScopeKind, FunctionScope } from './scope.js'; +import { Scope, ScopeKind, FunctionScope, BlockScope } from './scope.js'; import { parentIsFunctionLike, Stack, @@ -30,15 +30,16 @@ import { SourceLocation, addSourceMapLoc, getCurScope, + processGenericType, } from './utils.js'; -import { ModifierKind, Variable } from './variable.js'; +import { ModifierKind, Variable, Parameter } from './variable.js'; import { TSArray, TSClass, - TSFunction, Type, TypeKind, builtinTypes, + TSTypeParameter, } from './type.js'; import { Logger } from './log.js'; import { StatementError, UnimplementError } from './error.js'; @@ -64,6 +65,11 @@ export class Statement { getScope(): Scope | null { return this._scope; } + + clone() { + const stmt = new Statement(this.statementKind); + return stmt; + } } /** in order to keep order of namespace in parent level scope, creat a corresponding statement @@ -73,6 +79,11 @@ export class ModDeclStatement extends Statement { constructor(public scope: Scope) { super(ts.SyntaxKind.ModuleDeclaration); } + + clone(): ModDeclStatement { + const stmt = new ModDeclStatement(this.scope); + return stmt; + } } export class IfStatement extends Statement { @@ -95,12 +106,28 @@ export class IfStatement extends Statement { get ifIfFalse(): Statement | null { return this.ifFalse; } + + clone(): IfStatement { + const stmt = new IfStatement( + this.ifCondition, + this.ifIfTrue, + this.ifIfFalse, + ); + return stmt; + } } export class BlockStatement extends Statement { constructor() { super(ts.SyntaxKind.Block); } + + clone(): BlockStatement { + const stmt = new BlockStatement(); + const scope = this.getScope(); + if (scope !== null) stmt.setScope(scope); + return stmt; + } } export class ReturnStatement extends Statement { @@ -111,6 +138,11 @@ export class ReturnStatement extends Statement { get returnExpression(): Expression | null { return this.expr; } + + clone(): ReturnStatement { + const stmt = new ReturnStatement(this.returnExpression); + return stmt; + } } // create 'while' or 'do...while' loop @@ -145,6 +177,18 @@ export class BaseLoopStatement extends Statement { get loopBody(): Statement { return this.body; } + + clone(): BaseLoopStatement { + const stmt = new BaseLoopStatement( + this.statementKind, + this.loopLabel, + this.loopBlockLabel, + this.loopContinueLable, + this.loopCondtion, + this.loopBody, + ); + return stmt; + } } export class ForStatement extends Statement { @@ -188,6 +232,19 @@ export class ForStatement extends Statement { get forLoopIncrementor(): Expression | null { return this.incrementor; } + + clone(): ForStatement { + const stmt = new ForStatement( + this.forLoopLabel, + this.forLoopBlockLabel, + this.forContinueLable, + this.forLoopCondtion, + this.forLoopBody, + this.forLoopInitializer, + this.forLoopIncrementor, + ); + return stmt; + } } export class ExpressionStatement extends Statement { @@ -198,12 +255,22 @@ export class ExpressionStatement extends Statement { get expression(): Expression { return this.expr; } + + clone(): ExpressionStatement { + const stmt = new ExpressionStatement(this.expression); + return stmt; + } } export class EmptyStatement extends Statement { constructor() { super(ts.SyntaxKind.EmptyStatement); } + + clone(): EmptyStatement { + const stmt = new EmptyStatement(); + return stmt; + } } export class CaseClause extends Statement { @@ -218,6 +285,11 @@ export class CaseClause extends Statement { get caseStatements(): Statement[] { return this.statements; } + + clone(): CaseClause { + const stmt = new CaseClause(this.caseExpr, this.caseStatements); + return stmt; + } } export class DefaultClause extends Statement { @@ -228,6 +300,11 @@ export class DefaultClause extends Statement { get caseStatements(): Statement[] { return this.statements; } + + clone(): DefaultClause { + const stmt = new DefaultClause(this.caseStatements); + return stmt; + } } export class CaseBlock extends Statement { @@ -250,6 +327,15 @@ export class CaseBlock extends Statement { get caseCauses(): Statement[] { return this.causes; } + + clone(): CaseBlock { + const stmt = new CaseBlock( + this.switchLabel, + this.breakLabel, + this.caseCauses, + ); + return stmt; + } } export class SwitchStatement extends Statement { constructor(private cond: Expression, private caseBlock: Statement) { @@ -263,6 +349,14 @@ export class SwitchStatement extends Statement { get switchCaseBlock(): Statement { return this.caseBlock; } + + clone(): SwitchStatement { + const stmt = new SwitchStatement( + this.switchCondition, + this.switchCaseBlock, + ); + return stmt; + } } export class BreakStatement extends Statement { @@ -273,6 +367,11 @@ export class BreakStatement extends Statement { get breakLabel(): string { return this.label; } + + clone(): BreakStatement { + const stmt = new BreakStatement(this.breakLabel); + return stmt; + } } export class ContinueStatement extends Statement { @@ -283,6 +382,11 @@ export class ContinueStatement extends Statement { get continueLabel(): string { return this.label; } + + clone(): ContinueStatement { + const stmt = new ContinueStatement(this.continueLabel); + return stmt; + } } export class FunctionDeclarationStatement extends Statement { @@ -295,6 +399,11 @@ export class FunctionDeclarationStatement extends Statement { get funcScope(): FunctionScope { return this._funcScope; } + + clone(): FunctionDeclarationStatement { + const stmt = new FunctionDeclarationStatement(this.funcScope); + return stmt; + } } export class VariableStatement extends Statement { @@ -311,6 +420,14 @@ export class VariableStatement extends Statement { get varArray(): Variable[] { return this.variableArray; } + + clone(): VariableStatement { + const stmt = new VariableStatement(); + this.varArray.forEach((v) => { + stmt.addVariable(v); + }); + return stmt; + } } export class ImportDeclaration extends Statement { @@ -319,6 +436,11 @@ export class ImportDeclaration extends Statement { constructor() { super(ts.SyntaxKind.ImportDeclaration); } + + clone(): ImportDeclaration { + const stmt = new ImportDeclaration(); + return stmt; + } } export class ThrowStatement extends Statement { @@ -328,6 +450,11 @@ export class ThrowStatement extends Statement { super(ts.SyntaxKind.ThrowStatement); this.expr = expr; } + + clone(): ThrowStatement { + const stmt = new ThrowStatement(this.expr); + return stmt; + } } export class CatchClauseStatement extends Statement { @@ -338,6 +465,12 @@ export class CatchClauseStatement extends Statement { super(ts.SyntaxKind.CatchClause); this.catchBlockStmt = catchBlock; } + + clone(): CatchClauseStatement { + const stmt = new CatchClauseStatement(this.catchBlockStmt); + stmt.catchVar = this.catchVar; + return stmt; + } } export class TryStatement extends Statement { @@ -351,9 +484,16 @@ export class TryStatement extends Statement { this.label = tryLable; this.tryBlockStmt = tryBlock; } + + clone(): TryStatement { + const stmt = new TryStatement(this.label, this.tryBlockStmt); + stmt.catchClauseStmt = this.catchClauseStmt; + stmt.finallyBlockStmt = this.finallyBlockStmt; + return stmt; + } } -export default class StatementProcessor { +export class StatementProcessor { private loopLabelStack = new Stack(); private breakLabelsStack = new Stack(); // mark if continue statement in a loop @@ -368,6 +508,7 @@ export default class StatementProcessor { visit() { this.emitSourceMap = getConfig().sourceMap; this.parserCtx.nodeScopeMap.forEach((scope, node) => { + this.parserCtx.currentScope = scope; this.currentScope = scope; /** arrow function body is a ts.expression */ if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) { @@ -1394,3 +1535,441 @@ export default class StatementProcessor { return loopLabel + '_continue'; } } + +export class StatementSpecializationProcessor { + currentScope: Scope | null = null; + constructor(private parserCtx: ParserContext) {} + + visit() { + for (const g of this.parserCtx.globalScopes) { + this.currentScope = g; + this.visitScope(g); + } + } + + visitScope(scope: Scope) { + switch (scope.kind) { + case ScopeKind.FunctionScope: + this.currentScope = scope as FunctionScope; + if (scope.genericOwner) { + const originalFuncType = ( + scope.genericOwner as FunctionScope + ).funcType; + const typeParameters = originalFuncType.isMethod + ? originalFuncType.belongedClass!.typeArguments + ? originalFuncType.belongedClass!.typeArguments + : originalFuncType.typeArguments! + : originalFuncType.typeArguments!; + const specializedFuncType = (scope as FunctionScope) + .funcType; + const typeArguments = specializedFuncType.isMethod + ? specializedFuncType.belongedClass! + .specializedArguments + ? specializedFuncType.belongedClass! + .specializedArguments + : specializedFuncType.specializedArguments! + : specializedFuncType.specializedArguments!; + + const genericFunctionScope = + scope.genericOwner as FunctionScope; + //prcocess parameters and variables + genericFunctionScope.paramArray.forEach((v) => { + let varType = v.varType; + let initExpression = v.initExpression; + if (typeArguments) { + varType = processGenericType( + v.varType, + typeArguments, + typeParameters, + this.parserCtx, + ); + initExpression = initExpression + ? this.parserCtx.expressionProcessor.specializeExpression( + initExpression, + typeArguments, + typeParameters, + scope, + ) + : initExpression; + } + const new_parameter = new Parameter( + v.varName, + varType, + v.varModifiers, + v.varIndex, + v.isOptional, + v.destructuring, + initExpression, + v.isLocalVar(), + ); + + if (v.varIsClosure) new_parameter.setVarIsClosure(); + (scope as FunctionScope).addParameter(new_parameter); + }); + genericFunctionScope.varArray.forEach((v, index) => { + if (v.varName == '@context') { + const contextVar = new Variable( + '@context', + v.varType, + v.varModifiers, + v.varIndex, + v.isLocalVar(), + v.initExpression, + ); + contextVar.scope = scope; + (scope as FunctionScope).contextVariable = + contextVar; + scope.addVariable(contextVar); + } else if (v.varName == 'this') { + const thisVar = new Variable( + 'this', + processGenericType( + v.varType, + typeArguments, + typeParameters, + this.parserCtx, + ), + v.varModifiers, + v.varIndex, + v.isLocalVar(), + v.initExpression, + ); + thisVar.setVarIsClosure(); + thisVar.scope = scope; + scope.addVariable(thisVar); + } + }); + + scope.genericOwner.statements.forEach((s) => { + const stmt = this.processStatement( + s, + typeArguments, + typeParameters, + this.currentScope!, + ); + scope.addStatement(stmt); + }); + } + break; + default: + this.foreachScopeChildren(scope); + break; + } + } + + foreachScopeChildren(scope: Scope) { + for (const c of scope.children) { + this.currentScope = c; + this.visitScope(c); + } + } + + processStatement( + s: Statement, + typeArguments: Type[], + typeParameters: TSTypeParameter[], + currentScope: Scope, + ): Statement { + const stmt = s.clone(); + switch (stmt.statementKind) { + case ts.SyntaxKind.VariableStatement: { + const variableStatement = stmt as VariableStatement; + const newVariableStatement = new VariableStatement(); + variableStatement.varArray.forEach((v) => { + const initExpression = v.initExpression; + if (!initExpression) { + newVariableStatement.addVariable(v); + } else { + const newInitExpression = + this.parserCtx.expressionProcessor.specializeExpression( + initExpression, + typeArguments, + typeParameters, + currentScope, + ); + const newVar = new Variable( + v.varName, + newInitExpression.exprType, + v.varModifiers, + v.varIndex, + v.isLocalVar(), + newInitExpression, + ); + if (v.varIsClosure) newVar.setVarIsClosure(); + newVariableStatement.addVariable(newVar); + currentScope.addVariable(newVar); + newVar.scope = currentScope; + } + }); + return newVariableStatement; + } + case ts.SyntaxKind.IfStatement: { + const ifStatement = stmt as IfStatement; + const newIfCondition = + this.parserCtx.expressionProcessor.specializeExpression( + ifStatement.ifCondition, + typeArguments, + typeParameters, + this.currentScope! as FunctionScope, + ); + const newIfTrue = this.processStatement( + ifStatement.ifIfTrue, + typeArguments, + typeParameters, + currentScope, + ); + let newIfFalse: Statement | null = null; + if (ifStatement.ifIfFalse) { + newIfFalse = this.processStatement( + ifStatement.ifIfFalse, + typeArguments, + typeParameters, + currentScope, + ); + } + const newIfStatement = new IfStatement( + newIfCondition, + newIfTrue, + newIfFalse, + ); + return newIfStatement; + } + case ts.SyntaxKind.Block: { + const blockStatement = stmt as BlockStatement; + const newBlockStatement = new BlockStatement(); + if (blockStatement.getScope() !== null) { + const genericBlockScope = + blockStatement.getScope() as BlockScope; + const newBlockScope = new BlockScope( + currentScope, + genericBlockScope.getName(), + this.currentScope as FunctionScope, + ); + genericBlockScope.specialize(newBlockScope); + // initialize the properties of BlockScope; + newBlockScope.setGenericOwner(genericBlockScope); + genericBlockScope.addSpecializedScope( + genericBlockScope.getName(), + newBlockScope, + ); + if (genericBlockScope.mangledName !== '') { + newBlockScope.mangledName = + currentScope.mangledName !== '' + ? currentScope.mangledName + + '|' + + newBlockScope.getName() + : newBlockScope.getName(); + } + + //process variable '@context' + genericBlockScope.varArray.forEach((v) => { + if (v.varName == '@context') { + const contextVar = new Variable( + '@context', + v.varType, + v.varModifiers, + v.varIndex, + v.isLocalVar(), + v.initExpression, + ); + contextVar.scope = newBlockScope; + newBlockScope.addVariable(contextVar); + } + }); + + // processing statement + genericBlockScope.statements.forEach((s) => { + const newStmt = this.processStatement( + s, + typeArguments, + typeParameters, + newBlockScope, + ); + newBlockScope.addStatement(newStmt); + }); + newBlockStatement.setScope(newBlockScope); + } + + return newBlockStatement; + } + case ts.SyntaxKind.ReturnStatement: { + const returnStatement = stmt as ReturnStatement; + if (!returnStatement.returnExpression) return returnStatement; + const returnExpression = + this.parserCtx.expressionProcessor.specializeExpression( + returnStatement.returnExpression, + typeArguments, + typeParameters, + this.currentScope! as FunctionScope, + ); + const newReturnStatement = new ReturnStatement( + returnExpression, + ); + return newReturnStatement; + } + case ts.SyntaxKind.WhileStatement: { + const baseLoopStatement = stmt as BaseLoopStatement; + const newLoopLabel = baseLoopStatement.loopLabel; + const newBlockLabel = baseLoopStatement.loopBlockLabel; + const newContinueLable = baseLoopStatement.loopContinueLable; + const newLoopCondtion = + this.parserCtx.expressionProcessor.specializeExpression( + baseLoopStatement.loopCondtion, + typeArguments, + typeParameters, + currentScope, + ); + const newLoopBody = this.processStatement( + baseLoopStatement.loopBody, + typeArguments, + typeParameters, + currentScope, + ); + const newBaseLoopStatement = new BaseLoopStatement( + baseLoopStatement.statementKind, + newLoopLabel, + newBlockLabel, + newContinueLable, + newLoopCondtion, + newLoopBody, + ); + return newBaseLoopStatement; + } + case ts.SyntaxKind.ExpressionStatement: { + const expressionStatement = stmt as ExpressionStatement; + const expression = + this.parserCtx.expressionProcessor.specializeExpression( + expressionStatement.expression, + typeArguments, + typeParameters, + currentScope, + ); + const newExpressionStatement = new ExpressionStatement( + expression, + ); + return newExpressionStatement; + } + case ts.SyntaxKind.SwitchStatement: { + const switchStatement = stmt as SwitchStatement; + const newSwitchCondition = + this.parserCtx.expressionProcessor.specializeExpression( + switchStatement.switchCondition, + typeArguments, + typeParameters, + currentScope, + ); + const newSwitchCaseBlock = this.processStatement( + switchStatement.switchCaseBlock, + typeArguments, + typeParameters, + currentScope, + ); + const newSwitchStatement = new SwitchStatement( + newSwitchCondition, + newSwitchCaseBlock, + ); + return newSwitchStatement; + } + case ts.SyntaxKind.CaseBlock: { + const caseBlock = stmt as CaseBlock; + const newSwitchLabel = caseBlock.switchLabel; + const newBreakLabel = caseBlock.breakLabel; + const stmtArray: Statement[] = []; + caseBlock.caseCauses.forEach((s) => { + const newStmt = this.processStatement( + s, + typeArguments, + typeParameters, + currentScope, + ); + stmtArray.push(newStmt); + }); + const newCaseBlock = new CaseBlock( + newSwitchLabel, + newBreakLabel, + stmtArray, + ); + return newCaseBlock; + } + case ts.SyntaxKind.CaseClause: { + const caseClause = stmt as CaseClause; + const newCaseExpr = + this.parserCtx.expressionProcessor.specializeExpression( + caseClause.caseExpr, + typeArguments, + typeParameters, + currentScope, + ); + const stmtArray: Statement[] = []; + caseClause.caseStatements.forEach((s) => { + const newStmt = this.processStatement( + s, + typeArguments, + typeParameters, + currentScope, + ); + stmtArray.push(newStmt); + }); + const newCaseClause = new CaseClause(newCaseExpr, stmtArray); + return newCaseClause; + } + case ts.SyntaxKind.DefaultClause: { + const defaultClause = stmt as DefaultClause; + const stmtArray: Statement[] = []; + defaultClause.caseStatements.forEach((s) => { + const newStmt = this.processStatement( + s, + typeArguments, + typeParameters, + currentScope, + ); + stmtArray.push(newStmt); + }); + const newDefaultClause = new DefaultClause(stmtArray); + return newDefaultClause; + } + case ts.SyntaxKind.ThrowStatement: { + const throwStatement = stmt as ThrowStatement; + const newExpr = + this.parserCtx.expressionProcessor.specializeExpression( + throwStatement.expr, + typeArguments, + typeParameters, + currentScope, + ); + const newThrowStatement = new ThrowStatement(newExpr); + return newThrowStatement; + } + case ts.SyntaxKind.CatchClause: { + const catchClauseStatement = stmt as CatchClauseStatement; + const newCatchBlockStmt = this.processStatement( + catchClauseStatement.catchBlockStmt, + typeArguments, + typeParameters, + currentScope, + ) as BlockStatement; + const newCatchClauseStatement = new CatchClauseStatement( + newCatchBlockStmt, + ); + return newCatchClauseStatement; + } + case ts.SyntaxKind.TryStatement: { + const tryStatement = stmt as TryStatement; + const newLable = tryStatement.label; + const newTryBlockStmt = this.processStatement( + tryStatement.tryBlockStmt, + typeArguments, + typeParameters, + currentScope, + ) as BlockStatement; + const newTryStatement = new TryStatement( + newLable, + newTryBlockStmt, + ); + return newTryStatement; + } + default: + return stmt; + } + } +} diff --git a/src/type.ts b/src/type.ts index be3e839c..418acf0c 100644 --- a/src/type.ts +++ b/src/type.ts @@ -14,18 +14,21 @@ import { NamespaceScope, Scope, ScopeKind, + importSearchTypes, } from './scope.js'; import { Parameter, Variable } from './variable.js'; -import { Expression, IdentifierExpression } from './expression.js'; +import { Expression } from './expression.js'; import { Logger } from './log.js'; import { DefaultTypeId, - createClassScopeByClassType, - createFunctionScopeByFunctionType, + processGenericType, isTypeGeneric, + createScopeBySpecializedType, + genericClassTransformation, } from './utils.js'; -import { TypeError, UnimplementError } from './error.js'; +import { TypeError } from './error.js'; import { BuiltinNames } from '../lib/builtin/builtin_name.js'; +import { INSPECT_MAX_BYTES } from 'buffer'; export const enum TypeKind { VOID = 'void', @@ -833,7 +836,7 @@ export class TypeResolver { builtInTsTypeMap = new Map(); private symbolTypeMap = new Map(); nodeTypeCache = new Map(); - specializedTypeCache = new Map[]>(); + static specializedTypeCache = new Map[]>(); static typeParameterIndex = 0; private loopEntry: TSClass | null = null; @@ -938,12 +941,12 @@ export class TypeResolver { return this.tsTypeToType(t); }, ); - const specificType = TypeResolver.createSpecializedType( + const specificType = processGenericType( type, aliasTypes, - type as TSClass, + (type as TSClass).typeArguments!, + this.parserCtx, ) as TSClass; - specificType.setSpecializedArguments(aliasTypes); specificType.isLiteral = true; // TODO: in this case, specificType can't be recursive this.typeIdAllocate(specificType); @@ -953,57 +956,38 @@ export class TypeResolver { } } else { type = this.generateNodeType(symbolNode); - // specialization of generic types when using type alias - if ( - tsType.aliasTypeArguments && - this.noTypeParmeters(tsType.aliasTypeArguments) && - type instanceof TSTypeWithArguments - ) { - const specializedTypes = tsType.aliasTypeArguments.map( - (t) => { - return this.tsTypeToType(t); - }, - ); - // generate a name for the new specialized type - const typeNames = new Array(); - specializedTypes.forEach((v) => { - typeNames.push(`${v.kind}`); - }); - const typeSignature = '<' + typeNames.join(',') + '>'; - - // if a specialized type already exists - const cache = this.specializedTypeCache.get(type); - let found: Type | undefined; - if (cache) { - cache.forEach((v) => { - if (v.has(typeSignature)) { - found = v.get(typeSignature); - } - }); - } - if (found) break; - - // perform specialization operation - type = TypeResolver.createSpecializedType( - type, - specializedTypes, - type, - ); - if (type instanceof TSTypeWithArguments) - type.setSpecializedArguments(specializedTypes); - const origType = this.generateNodeType(symbolNode); - - // update specializedTypeCache - if (this.specializedTypeCache.has(origType)) { - const value = new Map(); - value.set(typeSignature, type); - this.specializedTypeCache - .get(origType)! - .push(value); - } else { - const value = new Map(); - value.set(typeSignature, type); - this.specializedTypeCache.set(origType, [value]); + // if type is generic, may need to deal with the generic chain + if (isTypeGeneric(type)) { + if (type instanceof TSTypeWithArguments) { + let typeArguments: Type[] = []; + const typeParameters = type.typeArguments; + // e.g. + // type ItemGenerator = (item: T, index: U) => void + // function test_func(func: ItemGenerator, a: T, b: U) {...} + if (tsType.aliasTypeArguments) { + typeArguments = tsType.aliasTypeArguments!.map( + (t) => { + return this.tsTypeToType(t); + }, + ); + } else if (this.isTypeReference(tsType)) { + // e.g. + // class Foo {...}; + // function bar(a: Foo, data: Y[]) {...} + typeArguments = + this.typechecker!.getTypeArguments( + tsType as ts.TypeReference, + ).map((t) => { + return this.tsTypeToType(t); + }); + } + if (typeParameters) + type = processGenericType( + type, + typeArguments, + typeParameters, + this.parserCtx, + ); } } } @@ -1080,166 +1064,6 @@ export class TypeResolver { this.currentScope!.addType(typeName, type); break; } - case ts.SyntaxKind.CallExpression: { - // When a generic function is called, it first needs to be specialized - const callExprNode = node; - const origType = this.generateNodeType( - callExprNode.expression, - ) as TSFunction; - const originalFunctionScope = origType.belongedScope; - // without FunctionScope information, generic functions cannot be specialized - if (isTypeGeneric(origType) && originalFunctionScope) { - let typeArguments: Type[] | undefined; - - // explicitly declare specialization type typeArguments - // e.g. - // function genericFunc (v: T){...} - // genericFunc(5); - if (callExprNode.typeArguments) { - typeArguments = callExprNode.typeArguments.map((t) => { - return this.generateNodeType(t); - }); - } - // specialize by passing parameters - // e.g. - // function genericFunc (v: T){...} - // genericFunc('hello'); - if (!typeArguments) { - const _typeArguments: Type[] = []; - // argument type - const _arguments = callExprNode.arguments.map((t) => { - return this.generateNodeType(t); - }); - // paramter type - const _paramters = origType.getParamTypes(); - - // TODO: Handling optional parameters - for (let i = 0; i < _paramters.length; i++) { - if ( - isTypeGeneric(_paramters[i]) && - !isTypeGeneric(_arguments[i]) - ) { - if ( - _paramters[i].kind == - TypeKind.TYPE_PARAMETER - ) { - _typeArguments.push(_arguments[i]); - } else if ( - _paramters[i].kind == TypeKind.ARRAY - ) { - const elementType = ( - _arguments[i] as TSArray - ).elementType; - _typeArguments.push(elementType); - } - } - } - typeArguments = _typeArguments; - } - - // there is a specialization types list - if (typeArguments.length > 0) { - const typeNames = new Array(); - typeArguments.forEach((v) => { - typeNames.push(`${v.kind}`); - }); - const typeSignature = '<' + typeNames.join(',') + '>'; - // if a specialized type already exists - const cache = this.specializedTypeCache.get(origType); - let found; - if (cache) { - cache.forEach((v) => { - if (v.has(typeSignature)) { - found = v.get(typeSignature); - } - }); - } - if (found) break; - - // default function call - if ( - callExprNode.expression.kind === - ts.SyntaxKind.Identifier - ) { - // create a new specialized function type - const specializedType = - TypeResolver.createSpecializedType( - origType, - typeArguments, - origType, - ) as TSFunction; - specializedType.setSpecializedArguments( - typeArguments, - ); - - // generate a name for the new specialized FunctionScope - const newScopeName = - originalFunctionScope.getName() + typeSignature; - - createFunctionScopeByFunctionType( - originalFunctionScope as FunctionScope, - originalFunctionScope.parent!, - specializedType, - newScopeName, - ); - originalFunctionScope.parent?.addType( - newScopeName, - specializedType, - ); - - if (this.specializedTypeCache.has(origType)) { - const value = new Map(); - value.set(typeSignature, specializedType); - this.specializedTypeCache - .get(origType)! - .push(value); - } else { - const value = new Map(); - value.set(typeSignature, specializedType); - this.specializedTypeCache.set(origType, [ - value, - ]); - } - } else if ( - callExprNode.expression.kind === - ts.SyntaxKind.PropertyAccessExpression - ) { - // method function call - /** - * e.g. - * class A { - * x: number; - * constructor(x: number) { - * this.x = x; - * } - * - * func(param: T) { - * return param; - * } - * } - * const a: A = new A(1); - * const ret = a.func(2); - */ - const classType = origType.belongedClass!; - // generate a name for the new specialized method FunctionScope - const origScopeName = - originalFunctionScope.getName(); - const newScopeName = origScopeName + typeSignature; - // whether the specialized generic method already exists - const existMethod = - classType.getMethod(newScopeName); - if (existMethod.method) return; - - TypeResolver.specializeClassMethod( - classType, - origScopeName, - typeArguments, - ); - } - } - } - break; - } case ts.SyntaxKind.AsExpression: { const asExprNode = node; const typeNode = asExprNode.type; @@ -1289,7 +1113,6 @@ export class TypeResolver { private addTypeToTypeMap(type: Type, node: ts.Node) { const tsTypeString = this.getTsTypeName(node); - if ( this.currentScope!.kind === ScopeKind.FunctionScope && type.kind === TypeKind.FUNCTION && @@ -1646,41 +1469,12 @@ export class TypeResolver { return this.tsTypeToType(t); }); - // generate a string signature of typeArguments to facilitate information storage - const typeNames = new Array(); - typeArguments.forEach((v) => { - typeNames.push(`${v.kind}`); - }); - const typeSignature = '<' + typeNames.join(',') + '>'; - - // Determine whether a specialized type already exists - // If it exists, return it directly - const cache = this.specializedTypeCache.get(tsType); - if (cache) { - let found: Type | undefined; - cache.forEach((v) => { - if (v.has(typeSignature)) { - found = v.get(typeSignature); - } - }); - if (found) return found; - } - - res = TypeResolver.createSpecializedType( + res = processGenericType( tsType, typeArguments, - tsType as TSClass, + (tsType as TSClass).typeArguments!, + this.parserCtx, ); - (res as TSClass).setSpecializedArguments(typeArguments); - if (this.specializedTypeCache.has(tsType)) { - const value = new Map(); - value.set(typeSignature, res); - this.specializedTypeCache.get(tsType)!.push(value); - } else { - const value = new Map(); - value.set(typeSignature, res); - this.specializedTypeCache.set(tsType, [value]); - } } else { res = tsType; } @@ -1962,24 +1756,37 @@ export class TypeResolver { } const tsType = this.typechecker!.getTypeAtLocation(valueDecl); let customType = this.tsTypeToType(tsType); - // e.g. - // type ItemGenerator = (item: T, index: U) => void - // function test_func(func: ItemGenerator, a: T, b: U) {...} - if ( - customType instanceof TSTypeWithArguments && - isTypeGeneric(customType) - ) { - const param = valueDecl as ts.ParameterDeclaration; - const type = this.typechecker!.getTypeAtLocation(param.type!); - if (type.aliasTypeArguments) { - const typeArguments = type.aliasTypeArguments!.map((t) => { - return this.tsTypeToType(t); - }); - customType = TypeResolver.createSpecializedType( - customType, - typeArguments, - customType, - ); + // if this parameter type is generic, may need to deal with the generic chain + if (isTypeGeneric(customType)) { + //function and class + if (customType instanceof TSTypeWithArguments) { + let typeArguments: Type[] = []; + // e.g. + // type ItemGenerator = (item: T, index: U) => void + // function test_func(func: ItemGenerator, a: T, b: U) {...} + if (tsType.aliasTypeArguments) { + typeArguments = tsType.aliasTypeArguments!.map((t) => { + return this.tsTypeToType(t); + }); + } else if (this.isTypeReference(tsType)) { + // e.g. + // class Foo {...}; + // function bar(a: Foo, data: Y[]) {...} + typeArguments = this.typechecker!.getTypeArguments( + tsType as ts.TypeReference, + ).map((t) => { + return this.tsTypeToType(t); + }); + } + if (customType.typeArguments) + customType = processGenericType( + customType, + typeArguments, + customType.typeArguments, + this.parserCtx, + ); + } else { + //TODO } } @@ -2073,7 +1880,7 @@ export class TypeResolver { const heritages = node.heritageClauses; const baseClassType: TSClass | null = null; let baseInfcType: TSInterface | null = null; - const effectTypeArguments = new Array(); + const typeArguments = new Array(); /** if extends more than two classes, an error will be thrown, * if extends a class, implements some interface, the subclass is subtype of supclass, * but do not guarantee that it will be a subtype of the interface either. @@ -2092,7 +1899,7 @@ export class TypeResolver { const baseType = this.symbolTypeMap.get(baseDecl); if (type.typeArguments) { type.typeArguments.forEach((t) => { - effectTypeArguments.push( + typeArguments.push( this.generateNodeType(t) as TSTypeParameter, ); }); @@ -2131,29 +1938,27 @@ export class TypeResolver { } } - if (baseInfcType && isTypeGeneric(baseInfcType)) { - const _infc = infc; - const _effectTypeArguments = effectTypeArguments; - + if ( + baseInfcType && + isTypeGeneric(baseInfcType) && + typeArguments.length > 0 + ) { // For the same interface base on different inheritance chains, we will regenerate a new base infc type. // The purpose is to prevent these base types from affecting each other. // e.g. // interface IGeneric {...}; // interface IGenericBase1 extends IGeneric {...}; // interface IGenericBase2 extends IGeneric {...}; - const newName = infc.className + '_' + baseInfcType.className; - baseInfcType = TypeResolver.createSpecializedType( - baseInfcType, - _effectTypeArguments, + baseInfcType = processGenericType( baseInfcType, - newName, + typeArguments, + baseInfcType.typeArguments!, + this.parserCtx, ) as TSInterface; infc.setBase(baseInfcType); - baseInfcType.setSpecializedArguments(_effectTypeArguments); baseInfcType.overrideOrOwnMethods.forEach((t) => { infc.overrideOrOwnMethods.add(t); }); - this.currentScope!.addType(newName, baseInfcType); } if (baseInfcType) { @@ -2251,7 +2056,6 @@ export class TypeResolver { this.nodeTypeCache.set(node, classType); classType.setClassName(node.name!.getText()); - const scope = this.parserCtx.nodeScopeMap.get(node)!; this.parseTypeParameters(classType, node, scope); @@ -2260,7 +2064,7 @@ export class TypeResolver { const heritages = node.heritageClauses; let baseClassType: TSClass | null = null; let baseInfcType: TSInterface | null = null; - const effectTypeArguments = new Array(); + const typeArguments = new Array(); /** if extends more than two classes, an error will be thrown, * if extends a class, implements some interface, the subclass is subtype of supclass, * but do not guarantee that it will be a subtype of the interface either. @@ -2278,7 +2082,7 @@ export class TypeResolver { const baseType = this.symbolTypeMap.get(baseDecl); if (type.typeArguments) { type.typeArguments.forEach((t) => { - effectTypeArguments.push( + typeArguments.push( this.generateNodeType(t) as TSTypeParameter, ); }); @@ -2320,16 +2124,17 @@ export class TypeResolver { } } - if (baseInfcType && isTypeGeneric(baseInfcType)) { - const _effectTypeArguments = effectTypeArguments; - const newName = classType.className + '_' + baseInfcType.className; - baseInfcType = TypeResolver.createSpecializedType( - baseInfcType, - _effectTypeArguments, + if ( + baseInfcType && + isTypeGeneric(baseInfcType) && + typeArguments.length > 0 + ) { + baseInfcType = processGenericType( baseInfcType, - newName, + typeArguments, + baseInfcType.typeArguments!, + this.parserCtx, ) as TSInterface; - baseInfcType.setSpecializedArguments(_effectTypeArguments); classType.setImplInfc(baseInfcType); let _baseInfcType: TSClass | null = baseInfcType; while (_baseInfcType) { @@ -2349,27 +2154,36 @@ export class TypeResolver { // On the inheritance chain, the type parameter of Generic is Y, // and in its class definition, the type parameter is X. // So it needs to be corrected based on the actual type parameters on the inheritance chain. - if (baseClassType && isTypeGeneric(baseClassType)) { - const _effectTypeArguments = effectTypeArguments; - + if ( + baseClassType && + isTypeGeneric(baseClassType) && + typeArguments.length > 0 + ) { // For the same class base on different inheritance chains, we will regenerate a new base class type. // The purpose is to prevent these base types from affecting each other. // e.g. // class Generic {...}; // class GenericBase1 extends Generic {...}; // class GenericBase2 extends Generic {...}; - if (_effectTypeArguments.length > 0) { - const newName = - classType.className + '_' + baseClassType.className; - baseClassType = TypeResolver.createSpecializedType( - baseClassType, - _effectTypeArguments, + baseClassType = genericClassTransformation( + baseClassType, + typeArguments, + baseClassType.typeArguments!, + this.parserCtx, + classType.className, + ) as TSClass; + classType.setBase(baseClassType); + baseClassType.setDrivedClass(classType); + // create base generic class scope + const geneicOwner = baseClassType.genericOwner + ? baseClassType.genericOwner + : baseClassType; + if (geneicOwner.belongedScope) { + createScopeBySpecializedType( baseClassType, - newName, - ) as TSClass; - baseClassType.setSpecializedArguments(_effectTypeArguments); - classType.setBase(baseClassType); - baseClassType.setDrivedClass(classType); + geneicOwner.belongedScope.parent!, + this.parserCtx, + ); } } @@ -2421,17 +2235,15 @@ export class TypeResolver { const paramTypes = baseCtorType.getParamTypes(); for (let i = 0; i < paramTypes.length; i++) { const paramType = paramTypes[i]; - const newType = TypeResolver.createSpecializedType( + const newType = processGenericType( paramType, - effectTypeArguments, - classType, + typeArguments, + classType.typeArguments!, + this.parserCtx, ); if (!newType) throw new TypeError('unimpl TSTypeParameter exchange'); - if (newType instanceof TSTypeWithArguments) - newType.setSpecializedArguments(effectTypeArguments); - ctorType.addParamType(paramTypes[i]); ctorScope.addParameter( new Parameter(`@anonymous${i}`, paramTypes[i]), @@ -2531,6 +2343,7 @@ export class TypeResolver { if (ts.isConstructorDeclaration(member)) { const ctorType = this.parseConstructor(member); ctorScope.setFuncType(ctorType); + ctorType.setBelongedScope(ctorScope); classType.ctorType = ctorType; ctorType.belongedClass = classType; } @@ -2925,378 +2738,6 @@ export class TypeResolver { } } - public static createSpecializedType( - type: Type, - specializedArgs: Type[], - containType: TSTypeWithArguments, - newName?: string, - ): Type { - // the type that need to be specialized must be generic - if (!isTypeGeneric(type)) return type; - - // SpecializedArgs MAY contain generic type. - // This situation currently occurs in the generic inheritance chain. - // e.g. - // class Generic {...}; - // class GenericBase extends Generic {...}; - // - // At this time, we need to change the type parameter of the generic Generic on the inheritance chain from X to Y. - let genericInheritance = false; - specializedArgs.forEach((t) => { - if (isTypeGeneric(t)) { - genericInheritance = true; - } - }); - - switch (type.kind) { - case TypeKind.VOID: - case TypeKind.BOOLEAN: - case TypeKind.NUMBER: - case TypeKind.ANY: - case TypeKind.UNDEFINED: - case TypeKind.STRING: - case TypeKind.UNKNOWN: - case TypeKind.NULL: - case TypeKind.WASM_I32: - case TypeKind.WASM_I64: - case TypeKind.WASM_F32: - case TypeKind.WASM_F64: - case TypeKind.WASM_ANYREF: { - return type; - } - case TypeKind.ARRAY: { - return new TSArray( - this.createSpecializedType( - (type as TSArray).elementType, - specializedArgs, - containType as TSTypeWithArguments, - ), - ); - } - case TypeKind.UNION: { - const unionType = type as TSUnion; - const newUnion = new TSUnion(); - unionType.types.forEach((t) => { - if (t.kind == TypeKind.UNDEFINED) { - newUnion.addType(t); - } else { - const newType = this.createSpecializedType( - t, - specializedArgs, - containType as TSTypeWithArguments, - ); - newUnion.addType(newType); - } - }); - return newUnion; - } - case TypeKind.FUNCTION: { - const funcType = type as TSFunction; - const newFuncType = funcType.clone(); - newFuncType.setTypeParameters(undefined); - // specialized function does not have '_typeArguments' property - if (genericInheritance && funcType.typeArguments) { - funcType.typeArguments.forEach((t) => { - const newTypeArg = this.createSpecializedType( - t, - specializedArgs, - containType as TSTypeWithArguments, - ) as TSTypeParameter; - newFuncType.addTypeParameter(newTypeArg); - }); - } - // specialized function type need to reset belongedClass and belongedScope - newFuncType.belongedClass = undefined; - newFuncType.setBelongedScope(undefined); - - // regenerate the parameter list - newFuncType.setParamTypes([]); - funcType.getParamTypes().forEach((paramType) => { - const newParamType = this.createSpecializedType( - paramType, - specializedArgs, - containType as TSTypeWithArguments, - ); - if (newParamType instanceof TSTypeWithArguments) - newParamType.setSpecializedArguments(specializedArgs); - newFuncType.addParamType(newParamType); - }); - - // prevent infinite recursive call - if ( - funcType.isMethod && - funcType.returnType instanceof TSClass && - funcType.returnType.typeId == funcType.belongedClass?.typeId - ) - return newFuncType; - - newFuncType.returnType = this.createSpecializedType( - funcType.returnType, - specializedArgs, - containType as TSTypeWithArguments, - ); - if (newFuncType.returnType instanceof TSTypeWithArguments) - newFuncType.returnType.setSpecializedArguments( - specializedArgs, - ); - return newFuncType; - } - case TypeKind.CLASS: - case TypeKind.INTERFACE: { - const classType = type as TSClass; - if (classType.typeArguments) { - let newType: TSClass; - if (type.kind === TypeKind.CLASS) { - newType = new TSClass(); - } else { - newType = new TSInterface(); - } - - // generate the class name of the new class - const typeNames = new Array(); - specializedArgs.forEach((v) => { - typeNames.push(`${v.kind}`); - }); - if (genericInheritance) { - // e.g. - // In this generic inheritance chain case: class GenericBase extends Generic {...} - // The name of the new generic class will be changed from a to Generic to Generic_GenericBase - const specializedName = newName - ? newName - : classType.className; - newType.setClassName(specializedName); - specializedArgs.forEach((v) => { - newType.addTypeParameter(v as TSTypeParameter); - }); - } else { - // e.g. - // Generic -> Generic - const specializedName = - classType.className + - '<' + - typeNames.join(',') + - '>'; - newType.setClassName(specializedName); - newType.setGenericOwner(classType); - } - - // set the property value of the basic property - if (classType.getBase()) { - const base = classType.getBase(); - const base_typeArguments = base!.typeArguments; - if (base_typeArguments) { - const baseSpecializedArgs = new Array(); - if (specializedArgs) { - for ( - let i = 0; - i < base_typeArguments.length; - i++ - ) { - const genericType = base_typeArguments[ - i - ] as TSTypeParameter; - const type = - TypeResolver.createSpecializedType( - genericType, - specializedArgs, - classType, - ); - if (type instanceof TSTypeWithArguments) - type.setSpecializedArguments( - specializedArgs, - ); - baseSpecializedArgs.push(type); - } - } - const newName = - newType.className + - '_' + - base?.className.split('_').reverse()[0]; - const newBaseType = - TypeResolver.createSpecializedType( - base!, - baseSpecializedArgs, - base!, - newName, - ) as TSClass; - newBaseType.setSpecializedArguments( - baseSpecializedArgs, - ); - newType.setBase(newBaseType); - newBaseType.setDrivedClass(newType); - } else { - newType.setBase(base!); - base!.setDrivedClass(newType); - } - } - - newType.hasDeclareCtor = classType.hasDeclareCtor; - const implInfc = classType.getImplInfc(); - if (implInfc && isTypeGeneric(implInfc)) { - const infc_typeArguments = implInfc.typeArguments; - if (infc_typeArguments) { - const infcSpecializedArgs = new Array(); - if (specializedArgs) { - for ( - let i = 0; - i < infc_typeArguments.length; - i++ - ) { - const genericType = infc_typeArguments[ - i - ] as TSTypeParameter; - const type = - TypeResolver.createSpecializedType( - genericType, - specializedArgs, - classType, - ); - if (type instanceof TSTypeWithArguments) - type.setSpecializedArguments( - specializedArgs, - ); - infcSpecializedArgs.push(type); - } - } - const infc = TypeResolver.createSpecializedType( - implInfc, - infcSpecializedArgs, - implInfc, - ) as TSInterface; - infc.setSpecializedArguments(infcSpecializedArgs); - newType.setImplInfc(infc); - } - } else { - newType.setImplInfc(implInfc); - } - newType.isDeclare = classType.isDeclare; - newType.isLiteral = classType.isLiteral; - newType.overrideOrOwnMethods = - classType.overrideOrOwnMethods; - newType.traverseStatus = classType.traverseStatus; - - // specialized member variables - classType.fields.forEach((field) => { - const newFieldType = this.createSpecializedType( - field.type, - specializedArgs, - containType as TSTypeWithArguments, - ); - if (newFieldType instanceof TSTypeWithArguments) - newFieldType.setSpecializedArguments( - specializedArgs, - ); - newType.addMemberField({ - name: field.name, - type: newFieldType, - }); - }); - // specialized member functions - classType.memberFuncs.forEach((func) => { - const newFuncType = this.createSpecializedType( - func.type, - specializedArgs, - containType as TSTypeWithArguments, - ) as TSFunction; - newFuncType.setSpecializedArguments(specializedArgs); - newType.addMethod({ - name: func.name, - type: newFuncType, - }); - }); - classType.staticFields.forEach((field) => { - const newStaticFieldType = this.createSpecializedType( - field.type, - specializedArgs, - containType as TSTypeWithArguments, - ); - if (newStaticFieldType instanceof TSTypeWithArguments) - newStaticFieldType.setSpecializedArguments( - specializedArgs, - ); - newType.addStaticMemberField({ - name: field.name, - type: newStaticFieldType, - }); - }); - // specialized constructor - if (classType.ctorType) { - newType.ctorType = this.createSpecializedType( - classType.ctorType, - specializedArgs, - containType as TSTypeWithArguments, - ) as TSFunction; - newType.ctorType.setSpecializedArguments( - specializedArgs, - ); - newType.ctorType.returnType = newType; - newType.ctorType.belongedClass = newType; - } - - // create the classScope to which the new class type belongs, but it doesn't always exist - const originalClassScope = classType.belongedScope; - if (!originalClassScope) return newType; - originalClassScope.parent?.addType( - newType.className, - newType, - ); - const newClassScope = createClassScopeByClassType( - originalClassScope as ClassScope, - originalClassScope.parent!, - newType, - ); - newType.setBelongedScope(newClassScope); - return newType; - } else { - classType.memberFuncs.forEach((func) => { - if (isTypeGeneric(func.type)) { - const origType = func.type; - const originalFunctionScope = - origType.belongedScope!; - // generate the class name of the new class - const typeNames = new Array(); - specializedArgs.forEach((v) => { - typeNames.push(`${v.kind}`); - }); - const typeSignature = - '<' + typeNames.join(',') + '>'; - const origScopeName = - originalFunctionScope.getName(); - const newScopeName = origScopeName + typeSignature; - // whether the specialized generic method already exists - const existMethod = - classType.getMethod(newScopeName); - if (existMethod.method) return classType; - - TypeResolver.specializeClassMethod( - classType, - origScopeName, - specializedArgs, - ); - } - }); - return classType; - } - } - case TypeKind.TYPE_PARAMETER: { - const genericType = type as TSTypeParameter; - const typeArgs = containType.typeArguments; - if (specializedArgs && typeArgs) { - for (let i = 0; i < typeArgs.length; i++) { - if (typeArgs[i].name === genericType.name) { - return specializedArgs[i]; - } - } - } - - return builtinTypes.get('any')!; - } - default: { - throw new UnimplementError('Not implemented type: ${type}'); - } - } - } - public arrayTypeCheck(node: ts.Node): boolean { const parentNode = node.parent; if ( @@ -3311,60 +2752,6 @@ export class TypeResolver { } return false; } - - /** - * @describe specialize a generic member function on the inheritance chain - * @param classType the base class where the generic member function is located - * @param funcName the name of function - * @param specializedArgs list of specialization types - * @returns void - */ - public static specializeClassMethod( - classType: TSClass, - funcName: string, - specializedArgs: Type[], - ) { - const origType = classType.getMethod(funcName).method?.type; - if (!origType) return; - - const typeNames = new Array(); - specializedArgs.forEach((v) => { - typeNames.push(`${v.kind}`); - }); - const newFuncName = funcName + '<' + typeNames.join(',') + '>'; - - const originalFunctionScope = origType.belongedScope; - if (!originalFunctionScope) return; - - const specializedType = TypeResolver.createSpecializedType( - origType, - specializedArgs, - origType, - ) as TSFunction; - specializedType.setSpecializedArguments(specializedArgs); - - createFunctionScopeByFunctionType( - originalFunctionScope as FunctionScope, - originalFunctionScope.parent!, - specializedType, - newFuncName, - ); - - const optional = classType.getMethod(funcName).method!.optional; - classType.addMethod({ - name: newFuncName, - type: specializedType, - optional: optional, - }); - classType.overrideOrOwnMethods.add(newFuncName); - - // specialize a generic member function on the inheritance chain - const drivedClasses = classType.getDrivedClasses(); - if (!drivedClasses) return; - drivedClasses.forEach((c) => { - this.specializeClassMethod(c, funcName, specializedArgs); - }); - } } export class CustomTypeResolver { diff --git a/src/utils.ts b/src/utils.ts index f36bba6f..b01dd4f2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,6 +19,7 @@ import ExpressionProcessor, { IdentifierExpression, } from './expression.js'; import { BuiltinNames } from '../lib/builtin/builtin_name.js'; +import { ParserContext } from './frontend.js'; import { FunctionKind, getMethodPrefix, @@ -32,10 +33,11 @@ import { TSUnion, builtinTypes, builtinWasmTypes, + TypeResolver, + TSTypeWithArguments, } from './type.js'; import { UnimplementError } from './error.js'; import { Statement } from './statement.js'; -import { Variable, Parameter } from './variable.js'; import { Logger } from './log.js'; export interface importGlobalInfo { @@ -476,111 +478,1346 @@ function decimalizationInternal(value: string, systemNumeration: number) { return decimal.toString(); } +export function genericFunctionSpecialization( + genericFuncType: TSFunction, + typeArguments: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, +): TSFunction { + if ( + !genericFuncType.typeArguments || + typeArguments.length == 0 || + typeParameters.length == 0 + ) + return genericFuncType; + + const typeArgumentsSignature: Array = []; + const _typeParameters = genericFuncType.typeArguments!; + _typeParameters.forEach((type) => { + const index = typeParameters.findIndex((t) => { + return t.name === type.name; + }); + if (index == -1) { + throw new UnimplementError( + `${type.name} not found in typeParameters`, + ); + } + typeArgumentsSignature.push(`${typeArguments[index].kind}`); + }); + + const genericOwner = genericFuncType.genericOwner + ? genericFuncType.genericOwner + : genericFuncType; + const typeSignature = + typeArgumentsSignature.length > 0 + ? '<' + typeArgumentsSignature.join(',') + '>' + : ''; + const cache = TypeResolver.specializedTypeCache.get(genericOwner); + let found: Type | undefined; + if (cache) { + cache.forEach((v) => { + if (v.has(typeSignature)) { + found = v.get(typeSignature); + } + }); + } + if (found) return found as TSFunction; + + const funcType = genericFuncType as TSFunction; + const newFuncType = funcType.clone(); + // specialized function does not have typeArguments + newFuncType.setTypeParameters(undefined); + // specialized function type need to reset belongedClass and belongedScope + newFuncType.belongedClass = undefined; + newFuncType.setBelongedScope(undefined); + + // regenerate the parameter list + newFuncType.setParamTypes([]); + funcType.getParamTypes().forEach((paramType) => { + const newParamType = genericTypeSpecialization( + paramType, + typeArguments, + typeParameters, + context, + ); + newFuncType.addParamType(newParamType); + }); + newFuncType.setGenericOwner(genericOwner); + + // update specializedTypeCache + newFuncType.setSpecializedArguments(typeArguments); + if (TypeResolver.specializedTypeCache.has(genericOwner)) { + const value = new Map(); + value.set(typeSignature, newFuncType); + TypeResolver.specializedTypeCache.get(genericOwner)!.push(value); + } else { + const value = new Map(); + value.set(typeSignature, newFuncType); + TypeResolver.specializedTypeCache.set(genericOwner, [value]); + } + + newFuncType.returnType = genericTypeSpecialization( + funcType.returnType, + typeArguments, + typeParameters, + context, + ); + return newFuncType; +} + +export function genericClassMethodSpecialization( + genericMethodType: TSFunction, + typeArguments: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, +): TSFunction { + if (typeArguments.length == 0 || typeParameters.length == 0) + return genericMethodType; + + const typeArgumentsSignature: Array = []; + const _typeParameters = genericMethodType.typeArguments + ? genericMethodType.typeArguments + : genericMethodType.belongedClass!.typeArguments!; + _typeParameters.forEach((type) => { + const index = typeParameters.findIndex((t) => { + return t.name === type.name; + }); + if (index == -1) { + throw new UnimplementError( + `${type.name} not found in typeParameters`, + ); + } + typeArgumentsSignature.push(`${typeArguments[index].kind}`); + }); + + const genericOwner = genericMethodType.genericOwner + ? genericMethodType.genericOwner + : genericMethodType; + const newMethodType = genericMethodType.clone(); + // specialized function does not have typeArguments + newMethodType.setTypeParameters(undefined); + // specialized function type need to reset belongedClass and belongedScope + newMethodType.belongedClass = undefined; + newMethodType.setBelongedScope(undefined); + + // regenerate the parameter list + newMethodType.setParamTypes([]); + genericMethodType.getParamTypes().forEach((paramType) => { + const newParamType = genericTypeSpecialization( + paramType, + typeArguments, + typeParameters, + context, + ); + newMethodType.addParamType(newParamType); + }); + newMethodType.setGenericOwner(genericOwner); + + // update specializedTypeCache + if (genericMethodType.typeArguments) { + newMethodType.setSpecializedArguments(typeArguments); + } + + // prevent infinite recursive call + if ( + genericMethodType.funcKind == FunctionKind.CONSTRUCTOR || + (genericMethodType.returnType instanceof TSClass && + genericMethodType.returnType.toString === + genericMethodType.belongedClass!.toString) + ) + return newMethodType; + + newMethodType.returnType = genericTypeSpecialization( + genericMethodType.returnType, + typeArguments, + typeParameters, + context, + ); + return newMethodType; +} + /** - * @describe create a new classScope based on classType information - * @param originalClassScope the original ClassScope to be specialized - * @param parent the parent of the original ClassScope - * @param classType the new class type corresponding to specialized ClassScope: TSClass => ClassScope - * @param newName the name of new ClassScope - * @returns a new specialized ClassScope + * class A { + * a: number; + * echo(param: T) {...}; + * } + * const a = new A(); + * this class type does not contain 'typeParameters'. */ -export function createClassScopeByClassType( - originalClassScope: ClassScope, - parent: Scope, - classType: TSClass, - newName?: string, +export function genericMethodSpecialization( + genericMethodType: TSFunction, + typeArguments: Type[], + context: ParserContext, ) { - const newClassScope = new ClassScope(parent); - originalClassScope.specialize(newClassScope); - newClassScope.setGenericOwner(originalClassScope); - const name = newName ? newName : classType.className; - newClassScope.setName(name); - newClassScope.setClassType(classType); - - originalClassScope.children.forEach((s) => { - if (s.kind == ScopeKind.FunctionScope) { - const functionScope = s as FunctionScope; - const funcName = functionScope.getName(); - const funcKind = functionScope.funcType.funcKind; - // constructor is not in the memberFuncs - if (funcKind == FunctionKind.CONSTRUCTOR) { - createFunctionScopeByFunctionType( - functionScope, - newClassScope, - classType.ctorType, + const classType = genericMethodType.belongedClass; + const originalFunctionScope = genericMethodType.belongedScope; + const typeParameters = genericMethodType.typeArguments; + + // insufficient information used for specialization + if (!classType || !typeParameters || !originalFunctionScope) return; + + const typeNames = new Array(); + typeArguments.forEach((v) => { + typeNames.push(`${v.kind}`); + }); + const typeSignature = '<' + typeNames.join(',') + '>'; + const newFuncName = originalFunctionScope.getName() + typeSignature; + const specializedFunctionType = genericClassMethodSpecialization( + genericMethodType, + typeArguments, + typeParameters, + context, + ) as TSFunction; + specializedFunctionType.belongedClass = classType; + + // create new function scope begin + const newFuncScope = new FunctionScope(originalFunctionScope.parent!); + originalFunctionScope.specialize(newFuncScope); + newFuncScope.setClassName(classType.className); + newFuncScope.setFuncName(newFuncName); + newFuncScope.setFuncType(specializedFunctionType); + specializedFunctionType.setBelongedScope(newFuncScope); + if (originalFunctionScope.mangledName !== '') { + const genericMangledName = originalFunctionScope.mangledName; + const reverse = genericMangledName.split('|').reverse(); + // class name + reverse[0] = newFuncName; + newFuncScope.mangledName = reverse.reverse().join('|'); + } + newFuncScope.setGenericOwner(originalFunctionScope); + originalFunctionScope.addSpecializedScope(typeSignature, newFuncScope); + const optional = classType.getMethod(originalFunctionScope.getName()) + .method!.optional; + classType.addMethod({ + name: newFuncName, + type: specializedFunctionType, + optional: optional, + }); + classType.overrideOrOwnMethods.add(newFuncName); + + // specialize a generic member function on the inheritance chain + const drivedClasses = classType.getDrivedClasses(); + if (!drivedClasses) return; + drivedClasses.forEach((c) => { + if (c.memberFuncs.length > 0) { + c.memberFuncs.forEach((m) => { + genericMethodSpecialization(m.type, typeArguments, context); + }); + } + }); +} + +export function genericClassSpecialization( + genericClassType: TSClass, + typeArguments: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, +): TSClass { + if (typeArguments.length == 0 || typeParameters.length == 0) + return genericClassType; + + const typeArgumentsSignature: Array = []; + const _typeParameters = genericClassType.typeArguments; + if (_typeParameters) { + _typeParameters.forEach((type) => { + const index = typeParameters.findIndex((t) => { + return t.name === type.name; + }); + if (index == -1) { + throw new UnimplementError( + `${type.name} not found in typeParameters`, ); + } + typeArgumentsSignature.push(`${typeArguments[index].kind}`); + }); + } + + const genericOwner = genericClassType.genericOwner + ? genericClassType.genericOwner + : genericClassType; + const typeSignature = + typeArgumentsSignature.length > 0 + ? '<' + typeArgumentsSignature.join(',') + '>' + : ''; + const cache = TypeResolver.specializedTypeCache.get(genericOwner); + let found: Type | undefined; + if (cache) { + cache.forEach((v) => { + if (v.has(genericClassType.className + typeSignature)) { + found = v.get(genericClassType.className + typeSignature); + } + }); + } + if (found) return found as TSClass; + + /** + * class Base { + * x: T; + * action(param: T) {....} + * } + */ + if (genericClassType.typeArguments) { + let newClassType: TSClass; + if (genericClassType.kind === TypeKind.CLASS) { + newClassType = new TSClass(); + } else { + newClassType = new TSInterface(); + } + const specializedName = genericClassType.className + typeSignature; + newClassType.setClassName(specializedName); + if (genericClassType.mangledName !== '') { + const genericMangledName = genericClassType.mangledName; + const reverse = genericMangledName.split('|').reverse(); + reverse[0] = specializedName; + newClassType.mangledName = reverse.reverse().join('|'); + } + newClassType.setGenericOwner(genericOwner as TSTypeWithArguments); + newClassType.setSpecializedArguments(typeArguments); + newClassType.hasDeclareCtor = genericClassType.hasDeclareCtor; + newClassType.isDeclare = genericClassType.isDeclare; + newClassType.isLiteral = genericClassType.isLiteral; + newClassType.overrideOrOwnMethods = + genericClassType.overrideOrOwnMethods; + newClassType.traverseStatus = genericClassType.traverseStatus; + + // update specializedTypeCache + if (TypeResolver.specializedTypeCache.has(genericOwner)) { + const value = new Map(); + value.set(specializedName, newClassType); + TypeResolver.specializedTypeCache.get(genericOwner)!.push(value); + } else { + const value = new Map(); + value.set(specializedName, newClassType); + TypeResolver.specializedTypeCache.set(genericOwner, [value]); + } + + // base class type + if (genericClassType.getBase()) { + let baseType = genericClassType.getBase(); + if (baseType) { + if (isTypeGeneric(baseType)) { + baseType = genericTypeSpecialization( + baseType, + typeArguments, + typeParameters, + context, + ) as TSClass; + } + newClassType.setBase(baseType); + } + } + + const implInfc = genericClassType.getImplInfc(); + if (implInfc && isTypeGeneric(implInfc)) { + const newInfcType = genericTypeSpecialization( + implInfc, + typeArguments, + typeParameters, + context, + ) as TSInterface; + newClassType.setImplInfc(newInfcType); + } else { + newClassType.setImplInfc(implInfc); + } + + // specialized member variables + genericClassType.fields.forEach((field) => { + const newFieldType = genericTypeSpecialization( + field.type, + typeArguments, + typeParameters, + context, + ); + newClassType.addMemberField({ + name: field.name, + type: newFieldType, + }); + }); + // specialized member functions + genericClassType.memberFuncs.forEach((func) => { + const funcName = func.name; + const funcKind = func.type.funcKind; + let realFuncName = funcName; + if (funcKind == FunctionKind.GETTER) { + realFuncName = 'get_' + funcName; + } else if (funcKind == FunctionKind.SETTER) { + realFuncName = 'set_' + funcName; + } + const isOwnMethod = + newClassType.overrideOrOwnMethods.has(realFuncName); + if (isOwnMethod) { + const newFuncType = genericClassMethodSpecialization( + func.type, + typeArguments, + typeParameters, + context, + ) as TSFunction; + newFuncType.belongedClass = newClassType; + newClassType.addMethod({ + name: funcName, + type: newFuncType, + }); } else { - let prefix = ''; - // the function names of the getter and setter contain 'get_' and 'set_' prefix strings. - if ( - funcKind == FunctionKind.GETTER || - funcKind == FunctionKind.SETTER - ) { - prefix = getMethodPrefix(funcKind); + let base = newClassType.getBase(); + let found = func; + while (base) { + const isOwnMethod = + base.overrideOrOwnMethods.has(realFuncName); + if (isOwnMethod) { + base.memberFuncs.forEach((f) => { + if ( + f.name == funcName && + f.type.funcKind == funcKind + ) { + found = f; + return; + } + }); + break; + } + base = base.getBase(); } - const res = classType.memberFuncs.findIndex((f) => { - return ( - funcName === prefix + f.name && - functionScope.funcType.funcKind === f.type.funcKind + newClassType.addMethod(found); + } + }); + genericClassType.staticFields.forEach((field) => { + const newStaticFieldType = genericTypeSpecialization( + field.type, + typeArguments, + typeParameters, + context, + ); + if (newStaticFieldType instanceof TSTypeWithArguments) + newClassType.addStaticMemberField({ + name: field.name, + type: newStaticFieldType, + }); + }); + // specialized constructor + if (genericClassType.ctorType) { + const newCtor = genericClassMethodSpecialization( + genericClassType.ctorType, + typeArguments, + typeParameters, + context, + ) as TSFunction; + newCtor.belongedClass = newClassType; + newClassType.ctorType = newCtor; + newClassType.ctorType.returnType = newClassType; + } + + if (genericClassType.belongedScope) + genericClassType.belongedScope.parent?.addType( + newClassType.className, + newClassType, + ); + return newClassType; + } else { + /** + * class Base { + * x: number; + * action(param: T) {....} + * } + */ + genericClassType.memberFuncs.forEach((func) => { + if (isTypeGeneric(func.type)) { + const genericFuncType = func.type; + const genericFunctionScope = genericFuncType.belongedScope!; + // generate the name of the specialized function name + const typeNames = new Array(); + typeArguments.forEach((v) => { + if (v.kind !== TypeKind.TYPE_PARAMETER) + typeNames.push(`${v.kind}`); + }); + const typeSignature = + typeNames.length > 0 ? '<' + typeNames.join(',') + '>' : ''; + const genericFunctionScopeName = genericFunctionScope.getName(); + const specializedFunctionScopeName = + genericFunctionScopeName + typeSignature; + // whether the specialized generic method already exists + const existMethod = genericClassType.getMethod( + specializedFunctionScopeName, + ); + if (existMethod.method) return genericClassType; + + genericMethodSpecialization( + genericFuncType, + typeArguments, + context, + ); + } + }); + return genericClassType; + } +} + +/** + * @describe specialize a generic type + * @param genericType the generic type that need to be specialized + * @param typeArguments specialized type arguments list + * @param typeParameters generic parameter type list + * @returns a new specialized type,if the same specialization is executed for the second time, + * generictype is returned to prevent the scope from being created again. + */ +export function genericTypeSpecialization( + genericType: Type, + typeArguments: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, +): Type { + // the type that need to be specialized must be generic + if (!isTypeGeneric(genericType)) return genericType; + + switch (genericType.kind) { + case TypeKind.VOID: + case TypeKind.BOOLEAN: + case TypeKind.NUMBER: + case TypeKind.ANY: + case TypeKind.UNDEFINED: + case TypeKind.STRING: + case TypeKind.UNKNOWN: + case TypeKind.NULL: + case TypeKind.WASM_I32: + case TypeKind.WASM_I64: + case TypeKind.WASM_F32: + case TypeKind.WASM_F64: + case TypeKind.WASM_ANYREF: { + return genericType; + } + case TypeKind.ARRAY: { + return new TSArray( + genericTypeSpecialization( + (genericType as TSArray).elementType, + typeArguments, + typeParameters, + context, + ), + ); + } + case TypeKind.UNION: { + const unionType = genericType as TSUnion; + const newUnion = new TSUnion(); + unionType.types.forEach((t) => { + if (t.kind == TypeKind.UNDEFINED) { + newUnion.addType(t); + } else { + const newType = genericTypeSpecialization( + t, + typeArguments, + typeParameters, + context, ); + newUnion.addType(newType); + } + }); + return newUnion; + } + case TypeKind.FUNCTION: { + const newFuncType = genericFunctionSpecialization( + genericType as TSFunction, + typeArguments, + typeParameters, + context, + ); + return newFuncType; + } + case TypeKind.CLASS: + case TypeKind.INTERFACE: { + const newClassType = genericClassSpecialization( + genericType as TSClass, + typeArguments, + typeParameters, + context, + ); + return newClassType; + } + case TypeKind.TYPE_PARAMETER: { + const gType = genericType as TSTypeParameter; + if (typeArguments && typeParameters) { + for (let i = 0; i < typeParameters.length; i++) { + if (typeParameters[i].name === gType.name) { + return typeArguments[i]; + } + } + } + return builtinTypes.get('any')!; + } + default: { + throw new UnimplementError( + `Not implemented type: ${genericType.kind}`, + ); + } + } +} + +export function genericFuncTransformation( + genericFuncType: TSFunction, + newTypeParameters: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, +): TSFunction { + if ( + !genericFuncType.typeArguments || + newTypeParameters.length == 0 || + typeParameters.length == 0 + ) + return genericFuncType; + + const genericOwner = genericFuncType.genericOwner + ? genericFuncType.genericOwner + : genericFuncType; + const newFuncType = genericFuncType.clone(); + newFuncType.setBelongedScope(undefined); + newFuncType.setGenericOwner(genericOwner); + newFuncType.setSpecializedArguments(newTypeParameters); + + // regenerate typeParameters + newFuncType.setTypeParameters([]); + genericFuncType.typeArguments.forEach((t) => { + newFuncType.addTypeParameter( + genericTypeTransformation( + t, + newTypeParameters, + typeParameters, + context, + ) as TSTypeParameter, + ); + }); + // regenerate the parameter list + newFuncType.setParamTypes([]); + genericFuncType.getParamTypes().forEach((paramType) => { + const newParamType = genericTypeTransformation( + paramType, + newTypeParameters, + typeParameters, + context, + ); + newFuncType.addParamType(newParamType); + }); + newFuncType.returnType = genericTypeTransformation( + genericFuncType.returnType, + newTypeParameters, + typeParameters, + context, + ); + return newFuncType; +} + +export function genericMethodTransformation( + genericMethodType: TSFunction, + newTypeParameters: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, +): TSFunction { + if (newTypeParameters.length == 0 || typeParameters.length == 0) + return genericMethodType; + + const genericOwner = genericMethodType.genericOwner + ? genericMethodType.genericOwner + : genericMethodType; + const newMethodType = genericMethodType.clone(); + newMethodType.setBelongedScope(undefined); + newMethodType.setGenericOwner(genericOwner); + + // regenerate the parameter list + newMethodType.setParamTypes([]); + genericMethodType.getParamTypes().forEach((paramType) => { + const newParamType = genericTypeTransformation( + paramType, + newTypeParameters, + typeParameters, + context, + ); + newMethodType.addParamType(newParamType); + }); + + // prevent infinite recursive call + if ( + genericMethodType.funcKind == FunctionKind.CONSTRUCTOR || + (genericMethodType.returnType instanceof TSClass && + genericMethodType.returnType.toString === + genericMethodType.belongedClass!.toString) + ) + return newMethodType; + + newMethodType.returnType = genericTypeTransformation( + genericMethodType.returnType, + newTypeParameters, + typeParameters, + context, + ); + return newMethodType; +} + +export function genericClassTransformation( + genericClassType: TSClass, + newTypeParameters: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, + namePrefix?: string, +): TSClass { + if ( + !genericClassType.typeArguments || + newTypeParameters.length == 0 || + typeParameters.length == 0 + ) + return genericClassType; + + const genericOwner = genericClassType.genericOwner + ? genericClassType.genericOwner + : genericClassType; + if (genericClassType.typeArguments) { + let newClassType: TSClass; + if (genericClassType.kind === TypeKind.CLASS) { + newClassType = new TSClass(); + } else { + newClassType = new TSInterface(); + } + newClassType.hasDeclareCtor = genericClassType.hasDeclareCtor; + newClassType.isDeclare = genericClassType.isDeclare; + newClassType.isLiteral = genericClassType.isLiteral; + newClassType.overrideOrOwnMethods = + genericClassType.overrideOrOwnMethods; + newClassType.traverseStatus = genericClassType.traverseStatus; + const newClassName = namePrefix + ? namePrefix + '_' + genericClassType.className + : genericClassType.className; + newClassType.setClassName(newClassName); + newClassType.setGenericOwner(genericOwner as TSClass); + newClassType.setSpecializedArguments(newTypeParameters); + + // regenerate typeParameters + newClassType.setTypeParameters([]); + genericClassType.typeArguments!.forEach((t) => { + newClassType.addTypeParameter( + genericTypeTransformation( + t, + newTypeParameters, + typeParameters, + context, + ) as TSTypeParameter, + ); + }); + + // base class type + if (genericClassType.getBase()) { + let baseType = genericClassType.getBase(); + if (baseType) { + if (isTypeGeneric(baseType)) { + baseType = genericClassTransformation( + baseType, + newTypeParameters, + typeParameters, + context, + namePrefix ? namePrefix : undefined, + ) as TSClass; + } + newClassType.setBase(baseType); + } + } + + const implInfc = genericClassType.getImplInfc(); + if (implInfc && isTypeGeneric(implInfc)) { + const newInfcType = genericTypeTransformation( + implInfc, + newTypeParameters, + typeParameters, + context, + ) as TSInterface; + newClassType.setImplInfc(newInfcType); + } else { + newClassType.setImplInfc(implInfc); + } + + genericClassType.fields.forEach((field) => { + const newFieldType = genericTypeTransformation( + field.type, + newTypeParameters, + typeParameters, + context, + ); + newClassType.addMemberField({ + name: field.name, + type: newFieldType, + }); + }); + genericClassType.memberFuncs.forEach((func) => { + const funcName = func.name; + const funcKind = func.type.funcKind; + let realFuncName = funcName; + if (funcKind == FunctionKind.GETTER) { + realFuncName = 'get_' + funcName; + } else if (funcKind == FunctionKind.SETTER) { + realFuncName = 'set_' + funcName; + } + const isOwnMethod = + newClassType.overrideOrOwnMethods.has(realFuncName); + if (isOwnMethod) { + const newFuncType = genericMethodTransformation( + func.type, + newTypeParameters, + typeParameters, + context, + ) as TSFunction; + newFuncType.belongedClass = newClassType; + newClassType.addMethod({ + name: funcName, + type: newFuncType, + }); + } else { + let base = newClassType.getBase(); + let found = func; + while (base) { + const isOwnMethod = + base.overrideOrOwnMethods.has(realFuncName); + if (isOwnMethod) { + base.memberFuncs.forEach((f) => { + if ( + f.name == funcName && + f.type.funcKind == funcKind + ) { + found = f; + return; + } + }); + break; + } + base = base.getBase(); + } + newClassType.addMethod(found); + } + }); + genericClassType.staticFields.forEach((field) => { + const newStaticFieldType = genericTypeTransformation( + field.type, + newTypeParameters, + typeParameters, + context, + ); + if (newStaticFieldType instanceof TSTypeWithArguments) + newClassType.addStaticMemberField({ + name: field.name, + type: newStaticFieldType, }); - if (res !== -1) { - const functionType = classType.memberFuncs[res].type; - createFunctionScopeByFunctionType( - functionScope, - newClassScope, - functionType, + }); + if (genericClassType.ctorType) { + const newCtor = genericMethodTransformation( + genericClassType.ctorType, + newTypeParameters, + typeParameters, + context, + ) as TSFunction; + newCtor.belongedClass = newClassType; + newClassType.ctorType = newCtor; + newClassType.ctorType.returnType = newClassType; + } + + if (genericClassType.belongedScope) { + const typeNames = new Array(); + newTypeParameters.forEach((v) => { + typeNames.push(`${(v as TSTypeParameter).name}`); + }); + const typeSignature = '<' + typeNames.join(',') + '>'; + genericClassType.belongedScope.parent?.addType( + newClassType.className + typeSignature, + newClassType, + ); + } + return newClassType; + } + return genericClassType; +} + +/** + * @describe update the type parameters of the generic type, and return a new generic type + * @param genericType the generic type that need to update type parameter list + * @param newTypeParameters target parameter type list + * @param typeParameters generic parameter type list + * @param context the parser context + * @returns a new generic type + */ +export function genericTypeTransformation( + genericType: Type, + newTypeParameters: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, +): Type { + // the type that need to be update must be generic + if (!isTypeGeneric(genericType)) return genericType; + + switch (genericType.kind) { + case TypeKind.VOID: + case TypeKind.BOOLEAN: + case TypeKind.NUMBER: + case TypeKind.ANY: + case TypeKind.UNDEFINED: + case TypeKind.STRING: + case TypeKind.UNKNOWN: + case TypeKind.NULL: + case TypeKind.WASM_I32: + case TypeKind.WASM_I64: + case TypeKind.WASM_F32: + case TypeKind.WASM_F64: + case TypeKind.WASM_ANYREF: { + return genericType; + } + case TypeKind.ARRAY: { + return new TSArray( + genericTypeTransformation( + (genericType as TSArray).elementType, + newTypeParameters, + typeParameters, + context, + ), + ); + } + case TypeKind.UNION: { + const unionType = genericType as TSUnion; + const newUnion = new TSUnion(); + unionType.types.forEach((t) => { + if (t.kind == TypeKind.UNDEFINED) { + newUnion.addType(t); + } else { + const newType = genericTypeTransformation( + t, + newTypeParameters, + typeParameters, + context, ); + newUnion.addType(newType); + } + }); + return newUnion; + } + case TypeKind.FUNCTION: { + const newFuncType = genericFuncTransformation( + genericType as TSFunction, + newTypeParameters, + typeParameters, + context, + ); + return newFuncType; + } + case TypeKind.CLASS: + case TypeKind.INTERFACE: { + const newClassType = genericClassTransformation( + genericType as TSClass, + newTypeParameters, + typeParameters, + context, + ); + return newClassType; + } + case TypeKind.TYPE_PARAMETER: { + const gType = genericType as TSTypeParameter; + if (newTypeParameters && typeParameters) { + for (let i = 0; i < typeParameters.length; i++) { + if (typeParameters[i].name === gType.name) { + return newTypeParameters[i]; + } } } + return builtinTypes.get('any')!; + } + default: { + throw new UnimplementError( + `Not implemented type: ${genericType.kind}`, + ); + } + } +} + +/** + * @describe specialize a generic type, or update the parameter type list of this generic type + * @param genericType the generic type that need to be processed + * @param typeArguments specialized type arguments list + * @param typeParameters generic parameter type list + * @param context the parser context + * @returns a new type + */ +export function processGenericType( + genericType: Type, + typeArguments: Type[], + typeParameters: TSTypeParameter[], + context: ParserContext, +): Type { + if ( + !isTypeGeneric(genericType) || + typeArguments.length == 0 || + typeParameters.length == 0 + ) + return genericType; + + if (genericType instanceof TSUnion) { + const newUnionType = new TSUnion(); + genericType.types.forEach((t) => { + newUnionType.addType( + processGenericType(t, typeArguments, typeParameters, context), + ); + }); + return newUnionType; + } + if (genericType instanceof TSArray) { + const newArrayType = new TSArray( + processGenericType( + genericType.elementType, + typeArguments, + typeParameters, + context, + ), + ); + return newArrayType; + } + + let newType: Type = genericType; + // if updating the parameter type list + const isUpdateTypeParameters = + typeArguments.filter((type) => isTypeGeneric(type)).length == + typeArguments.length; + if (isUpdateTypeParameters) { + // determine whether typeArguments is equal to typeParameters + let isSame = true; + for (let i = 0; i < typeParameters.length; i++) { + if ( + typeParameters[i].name !== + (typeArguments[i] as TSTypeParameter).name + ) { + isSame = false; + } + } + if (!isSame) + newType = genericTypeTransformation( + genericType, + typeArguments, + typeParameters, + context, + ); + } else { + if (genericType instanceof TSTypeWithArguments) { + const typeArgumentsSignature: Array = []; + const _typeParameters = genericType.typeArguments; + if (_typeParameters) { + _typeParameters.forEach((type) => { + const index = typeParameters.findIndex((t) => { + return t.name === type.name; + }); + if (index == -1) { + throw new UnimplementError( + `${type.name} not found in typeParameters`, + ); + } + typeArgumentsSignature.push(`${typeArguments[index].kind}`); + }); + } + + const genericOwner = genericType.genericOwner + ? genericType.genericOwner + : genericType; + const typeSignature = + typeArgumentsSignature.length > 0 + ? '<' + typeArgumentsSignature.join(',') + '>' + : ''; + const cache = TypeResolver.specializedTypeCache.get(genericOwner); + let found: Type | undefined; + if (cache) { + cache.forEach((v) => { + if (v.has(typeSignature)) { + found = v.get(typeSignature); + } + }); + } + if (found) return found; + } + newType = genericTypeSpecialization( + genericType, + typeArguments, + typeParameters, + context, + ); + if (genericType instanceof TSTypeWithArguments) { + const genericOwner = genericType.genericOwner + ? genericType.genericOwner + : genericType; + if (genericOwner.belongedScope) + createScopeBySpecializedType( + newType as TSTypeWithArguments, + genericOwner.belongedScope.parent!, + context, + ); + } + } + return newType; +} + +export function createScopeBySpecializedType( + specializeType: TSTypeWithArguments, + parentScope: Scope, + context: ParserContext, +) { + const genericOwner = specializeType.genericOwner; + if (!genericOwner) return; + + const genericScope = genericOwner.belongedScope; + if (!genericScope) return; + + switch (genericScope.kind) { + case ScopeKind.ClassScope: { + createClassScope(specializeType as TSClass, parentScope, context); + break; + } + case ScopeKind.FunctionScope: { + createFunctionScope( + specializeType as TSFunction, + parentScope, + context, + ); + break; + } + default: + return; + } +} + +/** + * @describe create a new specialized classScope + * @param specializedClassType the specialized class type + * @param parentScope the parent scope + * @param context the parser context + * @returns a new specialized ClassScope + */ +function createClassScope( + specializedClassType: TSClass, + parentScope: Scope, + context: ParserContext, +) { + const genericClassType = specializedClassType.genericOwner; + if ( + !genericClassType || + (genericClassType && !genericClassType.belongedScope) || + !specializedClassType.specializedArguments + ) + return; + + const genericClassScope = genericClassType.belongedScope! as ClassScope; + const name = specializedClassType.className; + // check if a specialized scope already exists + if ( + genericClassScope.specializedScopes && + genericClassScope.specializedScopes.has(name) + ) + return; + + const newClassScope = new ClassScope(parentScope); + genericClassScope.specialize(newClassScope); + newClassScope.setName(name); + + newClassScope.setGenericOwner(genericClassScope); + genericClassScope.addSpecializedScope(name, newClassScope); + newClassScope.setClassType(specializedClassType); + specializedClassType.setBelongedScope(newClassScope); + + if (genericClassScope.mangledName !== '') { + const genericMangledName = genericClassScope.mangledName; + const reverse = genericMangledName.split('|').reverse(); + reverse[0] = name; + newClassScope.mangledName = reverse.reverse().join('|'); + specializedClassType.mangledName = newClassScope.mangledName; + } + genericClassScope.children.forEach((s) => { + const genericFuncScope = s as FunctionScope; + const funcKind = genericFuncScope.funcType.funcKind; + // constructor is not in the memberFuncs + if (funcKind == FunctionKind.CONSTRUCTOR) { + createMethodScope( + specializedClassType.ctorType, + newClassScope, + context, + ); + } else { + let funcName = genericFuncScope.getName(); + // the function names of the getter and setter contain 'get_' and 'set_' prefix strings + if ( + funcKind == FunctionKind.GETTER || + funcKind == FunctionKind.SETTER + ) { + funcName = funcName.substring(4); + } + const res = specializedClassType.memberFuncs.findIndex((f) => { + return funcName === f.name && funcKind === f.type.funcKind; + }); + if (res !== -1) { + const specializedFunctionType = + specializedClassType.memberFuncs[res].type; + createMethodScope( + specializedFunctionType, + newClassScope, + context, + ); + } } }); + const genericBaseClassType = genericClassScope.classType.getBase(); + const specializedBaseClassType = specializedClassType.getBase(); + if (genericBaseClassType && specializedBaseClassType) { + const genericBaseClassScope = genericBaseClassType.belongedScope; + if (isTypeGeneric(genericBaseClassType) && genericBaseClassScope) { + createScopeBySpecializedType( + specializedBaseClassType, + genericBaseClassScope.parent!, + context, + ); + } + } return newClassScope; } /** - * @describe create a new FunctionScope based on functionType information - * @param originalFunctionScope the original FunctionScope to be specialized - * @param parent the parent of the original FunctionScope - * @param functionType the new function type corresponding to specialized FunctionScope: TSFunction => FunctionScope - * @param newName the name of new FunctionScope + * @describe create a new specialize method FunctionScope + * @param specializedMethodType the new specialized method type + * @param parentScope the parent class scope + * @param context the parser context + * @returns a new specialized method FunctionScope + */ +function createMethodScope( + specializedMethodType: TSFunction, + parentScope: ClassScope, + context: ParserContext, +) { + const classType = parentScope.classType; + const typeArguments = classType.specializedArguments + ? classType.specializedArguments + : specializedMethodType.specializedArguments; + + if ( + !specializedMethodType.genericOwner || + (specializedMethodType.genericOwner && + !specializedMethodType.genericOwner.belongedScope) || + !typeArguments + ) + return; + + const genericMethodType = specializedMethodType.genericOwner as TSFunction; + const genericMethodScope = + genericMethodType.belongedScope! as FunctionScope; + const newMethodScope = new FunctionScope(parentScope); + genericMethodScope.specialize(newMethodScope); + newMethodScope.setClassName(classType.className); + newMethodScope.setFuncName(genericMethodScope.getName()); + + newMethodScope.setGenericOwner(genericMethodScope); + genericMethodScope.addSpecializedScope(classType.className, newMethodScope); + + newMethodScope.setFuncType(specializedMethodType); + specializedMethodType.setBelongedScope(newMethodScope); + + const typeParameters = genericMethodType.belongedClass!.typeArguments!; + if (!typeArguments) return; + + // process funcName, mangledName and className of FunctionScope + if (genericMethodScope.mangledName !== '') { + const genericMangledName = genericMethodScope.mangledName; + const reverse = genericMangledName.split('|').reverse(); + // class name + reverse[1] = classType.className; + newMethodScope.mangledName = reverse.reverse().join('|'); + } + + // process function parameters and create scope + for (let idx = 0; idx < genericMethodType.getParamTypes().length; idx++) { + const genericParamType = genericMethodType.getParamTypes()[idx]; + if ( + genericParamType instanceof TSTypeWithArguments && + genericParamType.belongedScope + ) + createScopeBySpecializedType( + specializedMethodType.getParamTypes()[ + idx + ] as TSTypeWithArguments, + genericParamType.belongedScope.parent!, + context, + ); + } + return newMethodScope; +} + +/** + * @describe create a new specialize FunctionScope + * @param specializedFunctionType the new specialized function type + * @param parentScope the parent scope + * @param context the parser context * @returns a new specialized FunctionScope */ -export function createFunctionScopeByFunctionType( - originalFunctionScope: FunctionScope, - parent: Scope, - functionType: TSFunction, - newName?: string, +function createFunctionScope( + specializedFunctionType: TSFunction, + parentScope: Scope, + context: ParserContext, ) { - const newFuncScope = new FunctionScope(parent); - const className = parent instanceof ClassScope ? parent.className : ''; - newFuncScope.setClassName(className); - const name = newName ? newName : originalFunctionScope.funcName; - newFuncScope.setFuncName(name); - newFuncScope.setGenericOwner(originalFunctionScope); + const typeArguments = specializedFunctionType.specializedArguments; + if ( + !specializedFunctionType.genericOwner || + (specializedFunctionType.genericOwner && + !specializedFunctionType.genericOwner.belongedScope) || + !typeArguments + ) + return; - // specialize local variables inside functions - originalFunctionScope.varArray.forEach((v, index) => { - if (v.varName == '@context') { - const context = newFuncScope.findVariable('@context') as Variable; - context.setVarIndex(v.varIndex); - context.scope = newFuncScope; + const genericFuncType = specializedFunctionType.genericOwner as TSFunction; + const genericFunctionScope = + genericFuncType.belongedScope! as FunctionScope; + // check if a specialized scope already exists + const typeArgumentsSignature: Array = []; + typeArguments.forEach((t) => { + if (t.kind !== TypeKind.TYPE_PARAMETER) { + typeArgumentsSignature.push(`${t.kind}`); } else { - const varType = v.varType; - const new_var = new Variable( - v.varName, - varType, - [], - v.varIndex, - v.isLocalVar(), - v.initExpression, - ); - new_var.scope = newFuncScope; - newFuncScope.addVariable(new_var); + typeArgumentsSignature.push(`${(t as TSTypeParameter).name}`); } }); - newFuncScope.setFuncType(functionType); - functionType.setBelongedScope(newFuncScope); + const typeSignature = + typeArgumentsSignature.length > 0 + ? '<' + typeArgumentsSignature.join(',') + '>' + : ''; + const newFuncName = genericFunctionScope.getName() + typeSignature; + if ( + genericFunctionScope.specializedScopes && + genericFunctionScope.specializedScopes.has(newFuncName) + ) + return; + + const newFuncScope = new FunctionScope(parentScope); + genericFunctionScope.specialize(newFuncScope); + newFuncScope.setClassName(''); + newFuncScope.setFuncName(newFuncName); + newFuncScope.setGenericOwner(genericFunctionScope); + genericFunctionScope.addSpecializedScope(newFuncName, newFuncScope); + newFuncScope.setFuncType(specializedFunctionType); + specializedFunctionType.setBelongedScope(newFuncScope); + + const typeParameters = genericFuncType.typeArguments; + if (!typeParameters) return; + + if (genericFunctionScope.mangledName !== '') { + const genericMangledName = genericFunctionScope.mangledName; + const reverse = genericMangledName.split('|').reverse(); + reverse[0] = newFuncName; + newFuncScope.mangledName = reverse.reverse().join('|'); + } + + // Process function parameters and create scope + for (let idx = 0; idx < genericFuncType.getParamTypes().length; idx++) { + const genericParamType = genericFuncType.getParamTypes()[idx]; + if ( + genericParamType instanceof TSTypeWithArguments && + genericParamType.belongedScope + ) + createScopeBySpecializedType( + specializedFunctionType.getParamTypes()[ + idx + ] as TSTypeWithArguments, + genericParamType.belongedScope.parent!, + context, + ); + } return newFuncScope; } @@ -593,6 +1830,7 @@ export function isTypeGeneric(type: Type): boolean { case TypeKind.ANY: case TypeKind.UNDEFINED: case TypeKind.STRING: + case TypeKind.ENUM: case TypeKind.UNKNOWN: case TypeKind.NULL: case TypeKind.WASM_I32: @@ -613,21 +1851,6 @@ export function isTypeGeneric(type: Type): boolean { } case TypeKind.FUNCTION: { const funcType = type as TSFunction; - /* Member functions do not have 'typeArguments' property. - * So when a function is a member function of a class, - * it is determined whether the function is a generic type by judging whether the class it belongs to is a generic type. - * The result is not always correct, but it does not affect the logic of specialization. - * - * e.g. - * class A { - * a: T; - * echo() { - * console.log('hello world'); - * } - * } - * - * 'A' is a generic class type, and at this time we will treat 'echo' as a generic function. - */ if ( (funcType.isMethod && funcType.belongedClass?.typeArguments) || funcType.typeArguments @@ -638,45 +1861,144 @@ export function isTypeGeneric(type: Type): boolean { case TypeKind.CLASS: case TypeKind.INTERFACE: { const classType = type as TSClass; - /** - * e.g. - * class A { - * x: T; - * constructor(x: T) { - * this.x = x; - * } - * - * func(param: T) { - * return param; - * } - * } - */ - if (classType.typeArguments) return true; - /** - * e.g. - * class A { - * x: number; - * constructor(x: number) { - * this.x = x; - * } - * - * func(param: T) { - * return param; - * } - * } - */ - return classType.memberFuncs.some((func) => { - return isTypeGeneric(func.type); - }); + if (classType.typeArguments) { + /* When the class type carries type parameters, its method member does not contain type parameter information. + * At this time, it is determined whether the function is a generic type by judging whether the class it belongs to is a generic type. + * e.g. + * class A { + * a: T; + * echo(param: T) {...}; + * } + */ + return true; + } else { + /** + * class A { + * a: number; + * echo(param: T) {...}; + * } + * const a = new A(); + * this class type does not contain 'typeParameters', and newExpression does not contain 'typeArguments'. + */ + return classType.memberFuncs.some((func) => { + return isTypeGeneric(func.type); + }); + } } case TypeKind.TYPE_PARAMETER: { return true; } default: { - throw new UnimplementError('Not implemented type: ${type}'); + throw new UnimplementError(`Not implemented type: ${type.kind}`); } } - return false; +} + +/** + * @describe calculate 'typeArguments' from the formal parameters and actual parameters + * @param formalParameters the formal parameters of this generic function + * @param typeParameters the typeParameter list of this generic function + * @param actualParameters the actual parameters of this generic function + * @returns typeArguments + */ +// Currently only some basic types can be processed +export function getTypeArgumentsFromParameters( + formalParameters: Type[], + typeParameters: TSTypeParameter[], + actualParameters: Type[], +) { + if ( + formalParameters.length == 0 || + typeParameters.length == 0 || + actualParameters.length == 0 + ) + return []; + + if (formalParameters.length > actualParameters.length) { + formalParameters = formalParameters.slice(0, actualParameters.length); + } + const typeArguments = new Array(typeParameters.length); + // argument type + const argTypes = actualParameters; + // TODO: Handling optional parameters + for (let i = 0; i < formalParameters.length; i++) { + const pType = formalParameters[i]; + if (!isTypeGeneric(pType)) continue; + + const aType = argTypes[i]; + if (pType instanceof TSTypeParameter) { + const index = typeParameters.findIndex((t) => { + return t.name === pType.name; + }); + if (index == -1) { + throw new UnimplementError( + `${pType.name} not found in typeParameters`, + ); + } + typeArguments[index] = aType; + } else if (pType instanceof TSTypeWithArguments) { + const genericTypeList = pType.typeArguments!; + const specializedTypeList = (aType as TSTypeWithArguments) + .specializedArguments; + if (specializedTypeList) { + let idx = 0; + genericTypeList.forEach((g) => { + const index = typeParameters.findIndex((t) => { + return t.name === g.name; + }); + if (index == -1) { + throw new UnimplementError( + `${g.name} not found in typeParameters`, + ); + } + typeArguments[index] = specializedTypeList[idx]; + idx++; + }); + } + } else if (pType instanceof TSArray) { + // workaround + const genericElemType = pType.elementType; + if (aType.kind !== TypeKind.ARRAY) { + typeArguments[i] = aType; + } else { + if (genericElemType instanceof TSTypeParameter) { + const specializedElemType = (aType as TSArray).elementType; + const index = typeParameters.findIndex((t) => { + return t.name === genericElemType.name; + }); + if (index == -1) { + throw new UnimplementError( + `${genericElemType.name} not found in typeParameters`, + ); + } + typeArguments[index] = specializedElemType; + } else { + throw new UnimplementError( + `generic Array specialization operation for complex elementType is not implemented`, + ); + } + } + } else if (pType instanceof TSUnion) { + const types = pType.types; + let i = 0; + types.forEach((t) => { + if (t.kind == TypeKind.TYPE_PARAMETER) return; + i++; + }); + const index = typeParameters.findIndex((t) => { + return t.name === (types[i] as TSTypeParameter).name; + }); + if (index == -1) { + throw new UnimplementError( + `${ + (types[i] as TSTypeParameter).name + } not found in typeParameters`, + ); + } + typeArguments[index] = aType; + } + } + return typeArguments; } export enum PredefinedTypeId { diff --git a/src/variable.ts b/src/variable.ts index 52edd0b6..61f562bc 100644 --- a/src/variable.ts +++ b/src/variable.ts @@ -5,14 +5,14 @@ import ts from 'typescript'; import { Expression, IdentifierExpression } from './expression.js'; -import { TypeResolver, Type, TypeKind, TSFunction, TSEnum } from './type.js'; +import { TypeResolver, Type, TypeKind, TSEnum } from './type.js'; import { ParserContext } from './frontend.js'; import { addSourceMapLoc, generateNodeExpression, isScopeNode, } from './utils.js'; -import { ClassScope, FunctionScope, GlobalScope, Scope } from './scope.js'; +import { FunctionScope, GlobalScope, Scope } from './scope.js'; import { getConfig } from '../config/config_mgr.js'; export enum ModifierKind { diff --git a/tests/samples/nest_generic.ts b/tests/samples/nest_generic.ts new file mode 100644 index 00000000..0321f8e5 --- /dev/null +++ b/tests/samples/nest_generic.ts @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 Xiaomi Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +// case 1: +// Test when the type of typealias is generic +type ItemGenerator = (item: T, index?: U) => void +function func1(func: ItemGenerator, a: T, b: U) { + func(b, a); +} + +export function test1() { + const itemGenerator1: ItemGenerator = (index: number, item?: boolean) => {console.log(index); console.log(item)}; + func1(itemGenerator1, true, 1); + + const itemGenerator2: ItemGenerator = (item: string, index?: number) => {console.log(index); console.log(item)}; + func1(itemGenerator2, 2, 'hello'); +} + +// case 2 +// Test when function parameters are generic +class Foo { + foo(data: T[]) { + const a = data[0]; + console.log(a); + } +} +class Bar { + bar(a: Foo, data: T[]) { + a.foo(data); + } +} +class Foo1 { + _x: T; + + constructor(x: T) { + this._x = x; + } + set x(x: T) { + this._x = x; + } + get x() { + return this._x; + } + foo(data: T[]) { + console.log(this._x); + console.log(data[0]); + } +} +class Bar1 { + _y: U; + + constructor(y: U) { + this._y = y; + } + bar(a: Foo1, data: U[]) { + console.log(this._y); + a.foo(data); + } +} +function func2(a: Foo, b: Bar, data: U[]) { + b.bar(a, data); +} +function func3(a: Foo1, b: Bar1, data: U[]) { + b.bar(a, data); +} + +export function test2() { + const foo = new Foo(); + const bar = new Bar(); + bar.bar(foo, [1, 2]); + bar.bar(foo, ['hello', 'world']); +} + +export function test3() { + const foo1 = new Foo1(1); + foo1.x = 3; + console.log(foo1.x); + const bar1 = new Bar1(2); + bar1.bar(foo1, [1, 2]); + + const foo2 = new Foo1('11'); + foo2.x = '33'; + console.log(foo2.x); + const bar2 = new Bar1('22'); + bar2.bar(foo2, ['hello', 'world']); +} + +export function test4() { + const a = new Foo(); + const b = new Bar(); + func2(a, b, [1, 2]) + func2(a, b, ['hello', 'world']); + + const c = new Foo1(1); + const d = new Bar1(2); + const e = new Foo1('hello'); + const f = new Bar1('world'); + func3(c, d, [3, 4]); + func3(e, f, ['111', '222']); +} + +// case 3 +// Test when generic functions are nested +function func5(a: X, b: Y) { + const foo = new Foo1(a); + const bar = new Bar1(b); + console.log(foo.x); + console.log(bar._y); +} +function func6(a: U, b: T) { + func5(a, b); +} + +export function test5() { + func6(1, 'hello'); + func6(false, 2); +} + +// case 4 +type funcType = (key: string, value: V) => void +type AttrValue = undefined | string | number | boolean +function echo(key: string, value: string) { + console.log(key, value); +} + +class ArrayMap2 { + readonly keys: string[] = []; + readonly values: V[] = []; + + get size() { + return this.keys.length; + } + get(key: string): V | undefined { + const idx = this.keys.indexOf(key) + if (idx !== -1) { + return this.values[idx]; + } + return undefined; + } + set(key: string, value: V) { + const idx = this.keys.indexOf(key); + if (idx !== -1) { + this.values[idx] = value; + } else { + this.keys.push(key); + this.values.push(value); + } + } + delete(key: string) { + const idx = this.keys.indexOf(key); + if (idx !== -1) { + this.keys.splice(idx, 1); + this.values.splice(idx, 1); + } + } + clear() { + this.keys.splice(0, this.keys.length); + this.values.splice(0, this.values.length); + } + forEach2(fn: funcType) { + this.keys.forEach((key: string, index: number) => { + fn(key, this.values[index]); + }) + } +} + +export function test6() { + const a = new ArrayMap2(); + a.set('n1', 1); + a.set('n2', 2); + a.set('n3', 3); + console.log(a.get('n2')); + a.delete('n1'); + console.log(a.size); + + const b = new ArrayMap2(); + b.set('s1', '11'); + b.set('s2', '22'); + b.set('s3', '33'); + b.forEach2(echo); + console.log(b.get('s3')); + b.clear(); + console.log(b.size); + + const c = new ArrayMap2(); + c.set('u1', '11'); + c.set('u2', 22); + c.set('u3', true); + console.log(c.get('u1')); + console.log(c.get('u2')); + console.log(c.get('u3')); + c.clear(); + console.log(c.size); +} \ No newline at end of file diff --git a/tools/validate/wamr/validation.json b/tools/validate/wamr/validation.json index 4d667613..fecdb4cc 100644 --- a/tools/validate/wamr/validation.json +++ b/tools/validate/wamr/validation.json @@ -3174,6 +3174,41 @@ } ] }, + { + "module": "nest_generic", + "entries": [ + { + "name": "test1", + "args": [], + "result": "1\ntrue\n2\nhello" + }, + { + "name": "test2", + "args": [], + "result": "1\nhello" + }, + { + "name": "test3", + "args": [], + "result": "3\n2\n3\n1\n33\n22\n33\nhello" + }, + { + "name": "test4", + "args": [], + "result": "1\nhello\n2\n1\n3\nworld\nhello\n111" + }, + { + "name": "test5", + "args": [], + "result": "1\nhello\nfalse\n2" + }, + { + "name": "test6", + "args": [], + "result": "2\n2\ns1 11\ns2 22\ns3 33\n33\n0\n11\n22\ntrue\n0" + } + ] + }, { "module": "null_type_case1", "entries": [