diff --git a/ets2panda/linter/src/lib/TypeScriptLinter.ts b/ets2panda/linter/src/lib/TypeScriptLinter.ts index 2ba0b44631f3953bfa413deeb00e38fd87fb9191..04d39a0d90045bc5804ca395c782449caf89ded2 100644 --- a/ets2panda/linter/src/lib/TypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/TypeScriptLinter.ts @@ -157,16 +157,6 @@ import { D_ETS, D_TS } from './utils/consts/TsSuffix'; import { arkTsBuiltInTypeName } from './utils/consts/ArkuiImportList'; import { ERROR_TASKPOOL_PROP_LIST } from './utils/consts/ErrorProp'; -interface InterfaceSymbolTypeResult { - propNames: string[]; - typeNames: string[]; - allProps: Map; -} -interface InterfaceSymbolTypePropertyNames { - propertyNames: string[]; - typeNames: string[]; -} - export class TypeScriptLinter extends BaseTypeScriptLinter { supportedStdCallApiChecker: SupportedStdCallApiChecker; @@ -2495,7 +2485,6 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { this.handleInvalidIdentifier(tsVarDecl); this.checkAssignmentNumericSemanticsly(tsVarDecl); this.checkTypeFromSdk(tsVarDecl.type); - this.handleNoStructuralTyping(tsVarDecl); this.handleObjectLiteralforUnionTypeInterop(tsVarDecl); this.handleObjectLiteralAssignmentToClass(tsVarDecl); this.handleObjectLiteralAssignment(tsVarDecl); @@ -2523,194 +2512,6 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } } - private static extractUsedObjectType(tsVarDecl: ts.VariableDeclaration): InterfaceSymbolTypePropertyNames | null { - const result = { - propertyNames: [] as string[], - typeNames: [] as string[] - }; - - if (!this.isObjectLiteralWithProperties(tsVarDecl)) { - return null; - } - - this.processObjectLiteralProperties(tsVarDecl.initializer as ts.ObjectLiteralExpression, result); - return result.propertyNames.length > 0 ? result : null; - } - - private static isObjectLiteralWithProperties(tsVarDecl: ts.VariableDeclaration): boolean { - return ( - tsVarDecl.initializer !== undefined && - ts.isObjectLiteralExpression(tsVarDecl.initializer) && - tsVarDecl.initializer.properties.length > 0 - ); - } - - private static processObjectLiteralProperties( - objectLiteral: ts.ObjectLiteralExpression, - result: { propertyNames: string[]; typeNames: string[] } - ): void { - objectLiteral.properties.forEach((property) => { - if (!ts.isPropertyAssignment(property)) { - return; - } - - const propertyName = property.name.getText(); - result.propertyNames.push(propertyName); - - if (ts.isNewExpression(property.initializer)) { - const typeName = property.initializer.expression.getText(); - result.typeNames.push(typeName); - } - }); - } - - private interfaceSymbolType(tsVarDecl: ts.VariableDeclaration): InterfaceSymbolTypeResult | null { - if (!tsVarDecl.type) { - return null; - } - - const typeSymbol = this.getTypeSymbol(tsVarDecl); - if (!typeSymbol) { - return null; - } - - const interfaceType = this.getInterfaceType(tsVarDecl); - if (!interfaceType) { - return null; - } - - return this.collectInterfaceProperties(interfaceType, tsVarDecl); - } - - private getTypeSymbol(tsVarDecl: ts.VariableDeclaration): ts.Symbol | null { - const typeNode = ts.isTypeReferenceNode(tsVarDecl.type!) ? tsVarDecl.type.typeName : tsVarDecl.type; - return this.tsTypeChecker.getSymbolAtLocation(typeNode!) ?? null; - } - - private getInterfaceType(tsVarDecl: ts.VariableDeclaration): ts.InterfaceType | null { - const type = this.tsTypeChecker.getTypeAtLocation(tsVarDecl.type!); - return type && (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Interface ? (type as ts.InterfaceType) : null; - } - - private collectInterfaceProperties( - interfaceType: ts.InterfaceType, - tsVarDecl: ts.VariableDeclaration - ): InterfaceSymbolTypeResult { - const result = { - propNames: [] as string[], - typeNames: [] as string[], - allProps: new Map() - }; - - this.collectPropertiesRecursive(interfaceType, result, tsVarDecl); - return result; - } - - private collectPropertiesRecursive( - type: ts.Type, - result: { - propNames: string[]; - typeNames: string[]; - allProps: Map; - }, - tsVarDecl: ts.VariableDeclaration - ): void { - type.getProperties().forEach((property) => { - this.collectProperty(property, result, tsVarDecl); - }); - - if ('getBaseTypes' in type) { - type.getBaseTypes()?.forEach((baseType) => { - this.collectPropertiesRecursive(baseType, result, tsVarDecl); - }); - } - } - - private collectProperty( - property: ts.Symbol, - result: { - propNames: string[]; - typeNames: string[]; - allProps: Map; - }, - tsVarDecl: ts.VariableDeclaration - ): void { - const propName = property.getName(); - const propType = this.tsTypeChecker.getTypeOfSymbolAtLocation( - property, - property.valueDeclaration || tsVarDecl.type! - ); - const typeString = this.tsTypeChecker.typeToString(propType); - - if (!result.allProps.has(propName)) { - result.propNames.push(propName); - result.typeNames.push(typeString); - result.allProps.set(propName, typeString); - } - } - - handleNoStructuralTyping(tsVarDecl: ts.VariableDeclaration): void { - const { interfaceInfo, actualUsage } = this.getTypeComparisonData(tsVarDecl); - if (!interfaceInfo || !actualUsage) { - return; - } - if (!this.options.arkts2) { - return; - } - const actualMap = TypeScriptLinter.createActualTypeMap(actualUsage); - const hasMismatch = TypeScriptLinter.checkTypeMismatches(interfaceInfo, actualMap); - - if (hasMismatch) { - this.incrementCounters(tsVarDecl, FaultID.StructuralIdentity); - } - } - - private getTypeComparisonData(tsVarDecl: ts.VariableDeclaration): { - interfaceInfo: { propNames: string[]; typeNames: string[]; allProps: Map } | null; - actualUsage: { - propertyNames: string[]; - typeNames: string[]; - } | null; - } { - return { - interfaceInfo: this.interfaceSymbolType(tsVarDecl), - actualUsage: TypeScriptLinter.extractUsedObjectType(tsVarDecl) - }; - } - - private static createActualTypeMap(actualUsage: { - propertyNames: string[]; - typeNames: string[]; - }): Map { - const actualMap = new Map(); - actualUsage.propertyNames.forEach((prop, index) => { - if (actualUsage.typeNames[index]) { - actualMap.set(prop, actualUsage.typeNames[index]); - } - }); - return actualMap; - } - - private static checkTypeMismatches( - interfaceInfo: { allProps: Map }, - actualMap: Map - ): boolean { - let hasMismatch = false; - - interfaceInfo.allProps.forEach((expectedType, prop) => { - if (!actualMap.has(prop)) { - return; - } - - const actualType = actualMap.get(prop)!; - if (expectedType !== actualType) { - hasMismatch = true; - } - }); - - return hasMismatch; - } - private handleDeclarationDestructuring(decl: ts.VariableDeclaration | ts.ParameterDeclaration): void { const faultId = ts.isVariableDeclaration(decl) ? FaultID.DestructuringDeclaration : FaultID.DestructuringParameter; if (ts.isObjectBindingPattern(decl.name)) { @@ -5336,22 +5137,27 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { if (this.tsUtils.isWrongSendableFunctionAssignment(targetType, exprType)) { this.incrementCounters(tsAsExpr, FaultID.SendableFunctionAsExpr); } + this.handleAsExprStructuralTyping(tsAsExpr, targetType, exprType); + this.handleAsExpressionImport(tsAsExpr); + this.handleNoTuplesArrays(node, targetType, exprType); + this.handleObjectLiteralAssignmentToClass(tsAsExpr); + this.handleArrayTypeImmutable(tsAsExpr, exprType, targetType); + this.handleNotsLikeSmartTypeOnAsExpression(tsAsExpr); + } + + private handleAsExprStructuralTyping(asExpr: ts.AsExpression, targetType: ts.Type, exprType: ts.Type): void { if ( this.options.arkts2 && - this.tsUtils.needToDeduceStructuralIdentity(targetType, exprType, tsAsExpr.expression, true) + this.tsUtils.needToDeduceStructuralIdentity(targetType, exprType, asExpr.expression, true) && + this.tsUtils.needToDeduceStructuralIdentity(exprType, targetType, asExpr.expression, true) ) { - if (this.isExemptedAsExpression(tsAsExpr)) { + if (this.isExemptedAsExpression(asExpr)) { return; } if (!this.tsUtils.isObject(exprType)) { - this.incrementCounters(node, FaultID.StructuralIdentity); + this.incrementCounters(asExpr, FaultID.StructuralIdentity); } } - this.handleAsExpressionImport(tsAsExpr); - this.handleNoTuplesArrays(node, targetType, exprType); - this.handleObjectLiteralAssignmentToClass(tsAsExpr); - this.handleArrayTypeImmutable(tsAsExpr, exprType, targetType); - this.handleNotsLikeSmartTypeOnAsExpression(tsAsExpr); } private isExemptedAsExpression(node: ts.AsExpression): boolean { @@ -6223,41 +6029,72 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { * only need to strictly match the type of filling the check again */ private checkAssignmentMatching( - field: ts.Node, + contextNode: ts.Node, lhsType: ts.Type, rhsExpr: ts.Expression, isNewStructuralCheck: boolean = false ): void { const rhsType = this.tsTypeChecker.getTypeAtLocation(rhsExpr); - this.handleNoTuplesArrays(field, lhsType, rhsType); - this.handleArrayTypeImmutable(field, lhsType, rhsType, rhsExpr); + this.handleNoTuplesArrays(contextNode, lhsType, rhsType); + this.handleArrayTypeImmutable(contextNode, lhsType, rhsType, rhsExpr); // check that 'sendable typeAlias' is assigned correctly if (this.tsUtils.isWrongSendableFunctionAssignment(lhsType, rhsType)) { - this.incrementCounters(field, FaultID.SendableFunctionAssignment); + this.incrementCounters(contextNode, FaultID.SendableFunctionAssignment); } const isStrict = this.tsUtils.needStrictMatchType(lhsType, rhsType); // 'isNewStructuralCheck' means that this assignment scenario was previously omitted, so only strict matches are checked now if (isNewStructuralCheck && !isStrict) { return; } + this.handleStructuralTyping(contextNode, lhsType, rhsType, rhsExpr, isStrict); + } + + private handleStructuralTyping( + contextNode: ts.Node, + lhsType: ts.Type, + rhsType: ts.Type, + rhsExpr: ts.Expression, + isStrict: boolean + ): void { + if (TypeScriptLinter.isValidPromiseReturnedFromAsyncFunction(lhsType, rhsType, rhsExpr)) { + return; + } if (this.tsUtils.needToDeduceStructuralIdentity(lhsType, rhsType, rhsExpr, isStrict)) { - if (ts.isNewExpression(rhsExpr) && ts.isIdentifier(rhsExpr.expression) && rhsExpr.expression.text === 'Promise') { - const isReturnStatement = ts.isReturnStatement(rhsExpr.parent); - const enclosingFunction = ts.findAncestor(rhsExpr, ts.isFunctionLike); - const isAsyncFunction = - enclosingFunction && - (enclosingFunction.modifiers?.some((m) => { - return m.kind === ts.SyntaxKind.AsyncKeyword; - }) || - false); - if (isReturnStatement && isAsyncFunction) { - return; - } - } - this.incrementCounters(field, FaultID.StructuralIdentity); + this.incrementCounters(contextNode, FaultID.StructuralIdentity); } } + private static isValidPromiseReturnedFromAsyncFunction( + lhsType: ts.Type, + rhsType: ts.Type, + rhsExpr: ts.Expression + ): boolean { + + /* + * When resolving the contextual type for return expression in async function, the TS compiler + * infers 'PromiseLike' type instead of standard 'Promise' (see following link: + * https://github.com/microsoft/TypeScript/pull/27270). In this special case, we treat + * these two types as equal and only need to validate the type argument. + */ + + if (!ts.isReturnStatement(rhsExpr.parent)) { + return false; + } + const enclosingFunction = ts.findAncestor(rhsExpr, ts.isFunctionLike); + if (!TsUtils.hasModifier(enclosingFunction?.modifiers, ts.SyntaxKind.AsyncKeyword)) { + return false; + } + + const lhsPromiseLikeType = lhsType.isUnion() && lhsType.types.find(TsUtils.isStdPromiseLikeType); + if (!lhsPromiseLikeType || !TsUtils.isStdPromiseType(rhsType)) { + return false; + } + + const lhsTypeArg = TsUtils.isTypeReference(lhsPromiseLikeType) && lhsPromiseLikeType.typeArguments?.[0]; + const rhsTypeArg = TsUtils.isTypeReference(rhsType) && rhsType.typeArguments?.[0]; + return lhsTypeArg !== undefined && lhsTypeArg === rhsTypeArg; + } + private handleDecorator(node: ts.Node): void { this.handleExtendDecorator(node); this.handleEntryDecorator(node); diff --git a/ets2panda/linter/src/lib/utils/TsUtils.ts b/ets2panda/linter/src/lib/utils/TsUtils.ts index e164774602d29402032b17e067b07adee45db3f6..db9e766033add2d72cf30c8f2f477b363aa9e1d9 100644 --- a/ets2panda/linter/src/lib/utils/TsUtils.ts +++ b/ets2panda/linter/src/lib/utils/TsUtils.ts @@ -3794,4 +3794,14 @@ export class TsUtils { } return typeNode.kind === ts.SyntaxKind.VoidKeyword; } + + static isStdPromiseType(type: ts.Type): boolean { + const sym = type.getSymbol(); + return !!sym && sym.getName() === 'Promise' && isStdLibrarySymbol(sym); + } + + static isStdPromiseLikeType(type: ts.Type): boolean { + const sym = type.getSymbol(); + return !!sym && sym.getName() === 'PromiseLike' && isStdLibrarySymbol(sym); + } } diff --git a/ets2panda/linter/test/main/structural_identity.ets b/ets2panda/linter/test/main/structural_identity.ets index 406a7fee8edd059aa887b3e5d1f5e2eed7c97258..48def206813476e25f0cf507c80d32dab50be190 100644 --- a/ets2panda/linter/test/main/structural_identity.ets +++ b/ets2panda/linter/test/main/structural_identity.ets @@ -681,24 +681,17 @@ class MyObj3 { } interface goodPerson extends IPerson { - like: MyObj2, - like1: MyObj3 - } - - let lily:goodPerson = { - like: new MyObj1(), - name: 'Alice', - age: 30, - sayHello:()=> { - return new MyObj2() - } - } - - async function foo1(): Promise{ - - return new Promise(()=>{ + like: MyObj2, + like1: MyObj3 +} - }); +let lily: goodPerson = { + like: new MyObj1(), + name: 'Alice', + age: 30, + sayHello:()=> { + return new MyObj2() + } } function foo2(rule:Record){ diff --git a/ets2panda/linter/test/main/structural_identity.ets.arkts2.json b/ets2panda/linter/test/main/structural_identity.ets.arkts2.json index 9c75437ef721340f8b799c33a517e0341a0ef934..cadab9b537c3cf72ee3692e146eaf5b553907b1d 100644 --- a/ets2panda/linter/test/main/structural_identity.ets.arkts2.json +++ b/ets2panda/linter/test/main/structural_identity.ets.arkts2.json @@ -84,16 +84,6 @@ "rule": "Structural typing is not supported (arkts-no-structural-typing)", "severity": "ERROR" }, - { - "line": 66, - "column": 13, - "endLine": 66, - "endColumn": 23, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, { "line": 108, "column": 4, @@ -434,246 +424,6 @@ "rule": "Structural typing is not supported (arkts-no-structural-typing)", "severity": "ERROR" }, - { - "line": 244, - "column": 1, - "endLine": 244, - "endColumn": 16, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 245, - "column": 1, - "endLine": 245, - "endColumn": 16, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 246, - "column": 1, - "endLine": 246, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 247, - "column": 1, - "endLine": 247, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 248, - "column": 1, - "endLine": 248, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 249, - "column": 1, - "endLine": 249, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 250, - "column": 1, - "endLine": 250, - "endColumn": 16, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 251, - "column": 1, - "endLine": 251, - "endColumn": 16, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 252, - "column": 1, - "endLine": 252, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 253, - "column": 1, - "endLine": 253, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 254, - "column": 1, - "endLine": 254, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 255, - "column": 1, - "endLine": 255, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 281, - "column": 1, - "endLine": 281, - "endColumn": 16, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 282, - "column": 1, - "endLine": 282, - "endColumn": 16, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 283, - "column": 1, - "endLine": 283, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 284, - "column": 1, - "endLine": 284, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 285, - "column": 1, - "endLine": 285, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 286, - "column": 1, - "endLine": 286, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 287, - "column": 1, - "endLine": 287, - "endColumn": 16, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 288, - "column": 1, - "endLine": 288, - "endColumn": 16, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 289, - "column": 1, - "endLine": 289, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 290, - "column": 1, - "endLine": 290, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 291, - "column": 1, - "endLine": 291, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 292, - "column": 1, - "endLine": 292, - "endColumn": 17, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, { "line": 319, "column": 15, @@ -784,86 +534,6 @@ "rule": "Structural typing is not supported (arkts-no-structural-typing)", "severity": "ERROR" }, - { - "line": 400, - "column": 3, - "endLine": 400, - "endColumn": 11, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 401, - "column": 3, - "endLine": 401, - "endColumn": 19, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 402, - "column": 3, - "endLine": 402, - "endColumn": 19, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 406, - "column": 3, - "endLine": 406, - "endColumn": 32, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 413, - "column": 3, - "endLine": 413, - "endColumn": 13, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 414, - "column": 3, - "endLine": 414, - "endColumn": 21, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 415, - "column": 3, - "endLine": 415, - "endColumn": 21, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 419, - "column": 3, - "endLine": 419, - "endColumn": 36, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, { "line": 451, "column": 3, @@ -874,36 +544,6 @@ "rule": "Structural typing is not supported (arkts-no-structural-typing)", "severity": "ERROR" }, - { - "line": 452, - "column": 3, - "endLine": 452, - "endColumn": 20, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 453, - "column": 3, - "endLine": 453, - "endColumn": 15, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, - { - "line": 457, - "column": 3, - "endLine": 457, - "endColumn": 46, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, { "line": 460, "column": 3, @@ -1654,16 +1294,6 @@ "rule": "Structural typing is not supported (arkts-no-structural-typing)", "severity": "ERROR" }, - { - "line": 688, - "column": 6, - "endLine": 695, - "endColumn": 3, - "problem": "StructuralIdentity", - "suggest": "", - "rule": "Structural typing is not supported (arkts-no-structural-typing)", - "severity": "ERROR" - }, { "line": 688, "column": 24, @@ -1676,9 +1306,9 @@ }, { "line": 691, - "column": 10, + "column": 8, "endLine": 691, - "endColumn": 12, + "endColumn": 10, "problem": "NumericSemantics", "suggest": "", "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", diff --git a/ets2panda/linter/test/main/structural_identity_2.ets b/ets2panda/linter/test/main/structural_identity_2.ets new file mode 100644 index 0000000000000000000000000000000000000000..95729d873f528b33e3955a6fa3923ef60609f66c --- /dev/null +++ b/ets2panda/linter/test/main/structural_identity_2.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class A {} +class B {} +class C extends A {} + +function test1(ab?: A | B) { + const a = ab as A ?? new A(); // No error in ArkTS 1.2 + const b = ab as B ?? new B(); // No error in ArkTS 1.2 +} + +function test2(): A | null { + return new B(); // Error in ArkTS 1.2 +} + +function test3(a: A): C | undefined { + return a ? a as C : undefined; // No error in ArkTS 1.2 +} + +function test4(): void { + console.log(new A() as B); // Error in ArkTS 1.2 + console.log(new B() as C); // Error in ArkTS 1.2 + console.log(new A() as C); // No error in ArkTS 1.2 + console.log(new C() as A); // No error in ArkTS 1.2 +} + +interface I { + a?: A; +} +let i: I = { + a: new A() // No error in ArkTS 1.2 +}; \ No newline at end of file diff --git a/ets2panda/linter/test/main/structural_identity_2.ets.args.json b/ets2panda/linter/test/main/structural_identity_2.ets.args.json new file mode 100644 index 0000000000000000000000000000000000000000..3ef4496a819a201892114d1c90f78ae32053c334 --- /dev/null +++ b/ets2panda/linter/test/main/structural_identity_2.ets.args.json @@ -0,0 +1,19 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "mode": { + "arkts2": "" + } +} diff --git a/ets2panda/linter/test/main/structural_identity_2.ets.arkts2.json b/ets2panda/linter/test/main/structural_identity_2.ets.arkts2.json new file mode 100644 index 0000000000000000000000000000000000000000..822b39271030b39ef5c7261d1e574d8bce8f6a43 --- /dev/null +++ b/ets2panda/linter/test/main/structural_identity_2.ets.arkts2.json @@ -0,0 +1,48 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "result": [ + { + "line": 26, + "column": 10, + "endLine": 26, + "endColumn": 17, + "problem": "StructuralIdentity", + "suggest": "", + "rule": "Structural typing is not supported (arkts-no-structural-typing)", + "severity": "ERROR" + }, + { + "line": 34, + "column": 15, + "endLine": 34, + "endColumn": 27, + "problem": "StructuralIdentity", + "suggest": "", + "rule": "Structural typing is not supported (arkts-no-structural-typing)", + "severity": "ERROR" + }, + { + "line": 35, + "column": 15, + "endLine": 35, + "endColumn": 27, + "problem": "StructuralIdentity", + "suggest": "", + "rule": "Structural typing is not supported (arkts-no-structural-typing)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/structural_identity_2.ets.json b/ets2panda/linter/test/main/structural_identity_2.ets.json new file mode 100644 index 0000000000000000000000000000000000000000..ca88f857e960b437dcf767c0ac40be998c8f1236 --- /dev/null +++ b/ets2panda/linter/test/main/structural_identity_2.ets.json @@ -0,0 +1,17 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "result": [] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/structural_identity_promise.ets b/ets2panda/linter/test/main/structural_identity_promise.ets new file mode 100644 index 0000000000000000000000000000000000000000..e9fe68fd17fa5e01d5d58d483cc7488f3499cd18 --- /dev/null +++ b/ets2panda/linter/test/main/structural_identity_promise.ets @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class X {} + +async function foo(): Promise{ + return new Promise(() => { }); // No error in ArkTS 1.2 +} + +async function bar(): Promise{ + return new X(); // No error in ArkTS 1.2 +} + +class PromiseTest { + protected async foo(): Promise { + let arr: X[] = []; + return arr; + } + + public async bar(): Promise { + return this.foo(); // No error in ArkTS 1.2 + } +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/structural_identity_promise.ets.args.json b/ets2panda/linter/test/main/structural_identity_promise.ets.args.json new file mode 100644 index 0000000000000000000000000000000000000000..3ef4496a819a201892114d1c90f78ae32053c334 --- /dev/null +++ b/ets2panda/linter/test/main/structural_identity_promise.ets.args.json @@ -0,0 +1,19 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "mode": { + "arkts2": "" + } +} diff --git a/ets2panda/linter/test/main/structural_identity_promise.ets.arkts2.json b/ets2panda/linter/test/main/structural_identity_promise.ets.arkts2.json new file mode 100644 index 0000000000000000000000000000000000000000..ca88f857e960b437dcf767c0ac40be998c8f1236 --- /dev/null +++ b/ets2panda/linter/test/main/structural_identity_promise.ets.arkts2.json @@ -0,0 +1,17 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "result": [] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/structural_identity_promise.ets.json b/ets2panda/linter/test/main/structural_identity_promise.ets.json new file mode 100644 index 0000000000000000000000000000000000000000..ca88f857e960b437dcf767c0ac40be998c8f1236 --- /dev/null +++ b/ets2panda/linter/test/main/structural_identity_promise.ets.json @@ -0,0 +1,17 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "result": [] +} \ No newline at end of file diff --git a/ets2panda/parser/ETSparser.cpp b/ets2panda/parser/ETSparser.cpp index 4fca94525d37d5f6744fbe89f1c9ac23da6c1839..7c01ae71d99b5d4c4b9c82cd26fb104174d7cdc2 100644 --- a/ets2panda/parser/ETSparser.cpp +++ b/ets2panda/parser/ETSparser.cpp @@ -1073,7 +1073,8 @@ ir::TypeNode *ETSParser::ParseLiteralIdent(TypeAnnotationParsingOptions *options } if (Lexer()->TryEatTokenFromKeywordType(lexer::TokenType::KEYW_KEYOF)) { - auto *typeAnnotation = ParseTypeAnnotationNoPreferParam(options); + auto keyofOptions = *options | TypeAnnotationParsingOptions::REPORT_ERROR; + auto *typeAnnotation = ParseTypeAnnotationNoPreferParam(&keyofOptions); ES2PANDA_ASSERT(typeAnnotation != nullptr); typeAnnotation = AllocNode(typeAnnotation, Allocator()); typeAnnotation->SetRange(Lexer()->GetToken().Loc()); diff --git a/ets2panda/test/ast/compiler/ets/class_return_as_object.ets b/ets2panda/test/ast/compiler/ets/class_return_as_object.ets new file mode 100644 index 0000000000000000000000000000000000000000..0a870c07af65193e7bf1044ce5bdf56fd29c8381 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/class_return_as_object.ets @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class Example {} + +function getExampleClass() { + return Example; +} + +const exampleClassReference = getExampleClass; +exampleClassReference().toString(); + +/* @@? 19:12 Error TypeError: Class name 'Example' used in the wrong context */ \ No newline at end of file diff --git a/ets2panda/test/ast/compiler/ets/keyof_invalid_argument.ets b/ets2panda/test/ast/compiler/ets/keyof_invalid_argument.ets new file mode 100644 index 0000000000000000000000000000000000000000..6b2f5eab1a6df8c0bc2096187cba2dbd921f75bc --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/keyof_invalid_argument.ets @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function foo(t: T | {}, k: keyof (T | {})) {} + +class ObservableImpl extends Observable { + extendObservable(extraProps: U) { + new ObservableImpl<{ [K in keyof (T & U)]: (T & U)[K] }>(); + } +} + +type NestedKey = T[K] extends object ? keyof T[K] : never; + +/* @@? 16:24 Error SyntaxError: Unexpected token, expected ',' or ')'. */ +/* @@? 16:24 Error SyntaxError: Invalid Type. */ +/* @@? 16:26 Error SyntaxError: Unexpected token ','. */ +/* @@? 16:28 Error SyntaxError: Unexpected token 'k'. */ +/* @@? 16:31 Error SyntaxError: Label must be followed by a loop statement. */ +/* @@? 16:31 Error TypeError: Unresolved reference keyof */ +/* @@? 16:42 Error SyntaxError: Invalid Type. */ +/* @@? 16:42 Error SyntaxError: Unexpected token, expected ')'. */ +/* @@? 16:45 Error SyntaxError: Unexpected token ')'. */ +/* @@? 16:47 Error SyntaxError: Unexpected token '{'. */ +/* @@? 18:33 Error TypeError: Cannot find type 'Observable'. */ +/* @@? 18:33 Error TypeError: The super type of 'ObservableImpl' class is not extensible. */ +/* @@? 20:9 Error TypeError: Type 'ObservableImpl' is generic but type argument were not provided. */ +/* @@? 20:24 Error SyntaxError: Invalid Type. */ +/* @@? 20:24 Error SyntaxError: Unexpected token, expected '>'. */ +/* @@? 20:27 Error TypeError: Unresolved reference K */ +/* @@? 20:29 Error SyntaxError: Unexpected token, expected ',' or ']'. */ +/* @@? 20:29 Error SyntaxError: Unexpected token 'in'. */ +/* @@? 20:29 Error TypeError: Unresolved reference in */ +/* @@? 20:32 Error SyntaxError: Unexpected token 'keyof'. */ +/* @@? 20:32 Error TypeError: This expression is not callable. */ +/* @@? 20:41 Error SyntaxError: Unexpected token, expected ')'. */ +/* @@? 20:45 Error SyntaxError: Unexpected token ']'. */ +/* @@? 20:46 Error SyntaxError: Unexpected token ':'. */ +/* @@? 20:46 Error TypeError: Indexed access is not supported for such expression type. */ +/* @@? 20:60 Error SyntaxError: Unexpected token '>'. */ +/* @@? 20:61 Error SyntaxError: Unexpected token '('. */ +/* @@? 20:62 Error SyntaxError: Unexpected token ')'. */ +/* @@? 22:1 Error SyntaxError: Unexpected token '}'. */ +/* @@? 24:36 Error TypeError: The `keyof` keyword can only be used for class or interface type. */ +/* @@? 24:42 Error SyntaxError: Unexpected token ']'. */ +/* @@? 24:42 Error SyntaxError: Unexpected token, expected ']'. */ +/* @@? 24:43 Error SyntaxError: Unexpected token ']'. */ +/* @@? 24:45 Error SyntaxError: Unexpected token 'extends'. */ +/* @@? 24:53 Error SyntaxError: Unexpected token 'object'. */ +/* @@? 24:53 Error TypeError: Type name 'object' used in the wrong context */ +/* @@? 24:68 Error SyntaxError: Unexpected token. */ +/* @@? 24:68 Error TypeError: Unresolved reference T */ +/* @@? 24:68 Error TypeError: Indexed access is not supported for such expression type. */ +/* @@? 24:70 Error SyntaxError: Unexpected token, expected ']'. */ +/* @@? 24:73 Error SyntaxError: Unexpected token ':'. */ +/* @@? 24:75 Error SyntaxError: Unexpected token 'never'. */ +/* @@? 24:75 Error TypeError: Class name 'never' used in the wrong context */