diff --git a/ets2panda/linter/src/lib/TypeScriptLinter.ts b/ets2panda/linter/src/lib/TypeScriptLinter.ts
index ca04dbcfcd70cd084c01b8f28c23f983dd279534..6df04dd82c05367cbc7880796bf86d5e436a6baf 100644
--- a/ets2panda/linter/src/lib/TypeScriptLinter.ts
+++ b/ets2panda/linter/src/lib/TypeScriptLinter.ts
@@ -3302,35 +3302,71 @@ export class TypeScriptLinter extends BaseTypeScriptLinter {
const classType = this.tsTypeChecker.getTypeAtLocation(classDecl);
const allBaseTypes = this.getAllBaseTypes(classType, classDecl);
- if (!allBaseTypes || allBaseTypes.length === 0) {
- return;
- }
const methodName = node.name.text;
+ if (allBaseTypes && allBaseTypes.length > 0) {
+ for (const baseType of allBaseTypes) {
+ const baseMethod = baseType.getProperty(methodName);
+ if (!baseMethod) {
+ continue;
+ }
- for (const baseType of allBaseTypes) {
- const baseMethod = baseType.getProperty(methodName);
- if (!baseMethod) {
- continue;
- }
+ const baseMethodDecl = baseMethod.declarations?.find((d) => {
+ return (
+ (ts.isMethodDeclaration(d) || ts.isMethodSignature(d)) &&
+ this.tsTypeChecker.getTypeAtLocation(d.parent) === baseType
+ );
+ }) as ts.MethodDeclaration | ts.MethodSignature;
- const baseMethodDecl = baseMethod.declarations?.find((d) => {
- return (
- (ts.isMethodDeclaration(d) || ts.isMethodSignature(d)) &&
- this.tsTypeChecker.getTypeAtLocation(d.parent) === baseType
- );
- }) as ts.MethodDeclaration | ts.MethodSignature;
+ if (!baseMethodDecl) {
+ continue;
+ }
+
+ this.checkMethodParameters(node, baseMethodDecl);
- if (!baseMethodDecl) {
+ this.checkMethodReturnType(node, baseMethodDecl);
+
+ break;
+ }
+ }
+ this.checkIncompatibleFunctionTypes(node);
+ }
+
+ private checkIncompatibleFunctionTypes(method: ts.MethodDeclaration): void {
+ const declaredReturnType = this.getActualReturnType(method);
+ if (!declaredReturnType) {
+ return;
+ }
+ const returnStatements = this.collectReturnStatements(method);
+ const declaredReturnTypeStr = this.tsTypeChecker.typeToString(declaredReturnType);
+ for (const returnStmt of returnStatements) {
+ if (!returnStmt.expression) {
continue;
}
+ const actualReturnType = this.tsTypeChecker.getTypeAtLocation(returnStmt.expression);
+ const actualReturnTypeStr = this.tsTypeChecker.typeToString(actualReturnType);
+ if (declaredReturnTypeStr === actualReturnTypeStr) {
+ return;
+ }
+ if (this.isSubtypeByBaseTypesList(actualReturnType, declaredReturnType)) {
+ this.incrementCounters(returnStmt.expression, FaultID.IncompationbleFunctionType);
+ return;
+ }
+ }
+ }
- this.checkMethodParameters(node, baseMethodDecl);
+ private collectReturnStatements(node: ts.Node): ts.ReturnStatement[] {
+ const result: ts.ReturnStatement[] = [];
- this.checkMethodReturnType(node, baseMethodDecl);
+ ts.forEachChild(node, (child) => {
+ if (ts.isReturnStatement(child)) {
+ result.push(child);
+ } else {
+ result.push(...this.collectReturnStatements(child));
+ }
+ });
- break;
- }
+ return result;
}
private getAllBaseTypes(type: ts.Type, classDecl: ts.ClassDeclaration): ts.Type[] | undefined {
@@ -3514,7 +3550,12 @@ export class TypeScriptLinter extends BaseTypeScriptLinter {
const baseTypeNode = baseDeclarations[0];
const derivedTypeNode = derivedDeclarations[0];
- if (ts.isClassDeclaration(baseTypeNode) && ts.isClassDeclaration(derivedTypeNode)) {
+ if (
+ baseTypeNode &&
+ derivedTypeNode &&
+ ts.isClassDeclaration(baseTypeNode) &&
+ ts.isClassDeclaration(derivedTypeNode)
+ ) {
const baseTypes = this.tsTypeChecker.getTypeAtLocation(derivedTypeNode).getBaseTypes();
const baseTypesExtends = baseTypes?.some((t) => {
return t === baseType;
@@ -6739,15 +6780,12 @@ export class TypeScriptLinter extends BaseTypeScriptLinter {
if (!this.options.arkts2) {
return;
}
- const isArray =
- this.tsUtils.isOrDerivedFrom(lhsType, this.tsUtils.isArray) &&
- this.tsUtils.isOrDerivedFrom(rhsType, this.tsUtils.isArray);
+ const isArray = this.tsUtils.isArray(lhsType) && this.tsUtils.isArray(rhsType);
const isTuple =
this.tsUtils.isOrDerivedFrom(lhsType, TsUtils.isTuple) && this.tsUtils.isOrDerivedFrom(rhsType, TsUtils.isTuple);
if (!((isArray || isTuple) && lhsType !== rhsType)) {
return;
}
-
const rhsTypeStr = this.tsTypeChecker.typeToString(rhsType);
let lhsTypeStr = this.tsTypeChecker.typeToString(lhsType);
if (rhsExpr && (this.isNullOrEmptyArray(rhsExpr) || ts.isArrayLiteralExpression(rhsExpr))) {
@@ -6766,6 +6804,16 @@ export class TypeScriptLinter extends BaseTypeScriptLinter {
}
}
+ private isSubtypeByBaseTypesList(baseType: ts.Type, actualType: ts.Type): boolean {
+ if (this.isTypeAssignable(actualType, baseType)) {
+ return true;
+ }
+ const actualBaseTypes = actualType.getBaseTypes() || [];
+ return actualBaseTypes.some((base) => {
+ return this.isSubtypeByBaseTypesList(baseType, base);
+ });
+ }
+
private isNullOrEmptyArray(expr: ts.Expression): boolean {
if (ts.isNewExpression(expr)) {
const constructorSym = this.tsTypeChecker.getSymbolAtLocation(expr.expression);
@@ -9944,11 +9992,34 @@ export class TypeScriptLinter extends BaseTypeScriptLinter {
private isExactlySameType(type1: ts.Type, type2: ts.Type): boolean {
if (type2.getCallSignatures().length > 0) {
const returnType = TsUtils.getFunctionReturnType(type2);
- return returnType ?
- this.tsTypeChecker.typeToString(type1) === this.tsTypeChecker.typeToString(returnType) :
- false;
+ return returnType ? this.isExactlySameType(type1, returnType) : false;
}
- return this.tsTypeChecker.typeToString(type1) === this.tsTypeChecker.typeToString(type2);
+
+ const type1String = this.tsTypeChecker.typeToString(type1);
+ const type2String = this.tsTypeChecker.typeToString(type2);
+ if (type1String === type2String) {
+ return true;
+ }
+
+ if (this.checkBaseTypes(type1, type2) || this.checkBaseTypes(type2, type1)) {
+ return true;
+ }
+ return type1String === type2String;
+ }
+
+ private checkBaseTypes(type1: ts.Type, type2: ts.Type): boolean {
+ const isClassType =
+ (type1.getFlags() & ts.TypeFlags.Object) !== 0 &&
+ ((type1 as ts.ObjectType).objectFlags & ts.ObjectFlags.Class) !== 0;
+ if (isClassType) {
+ const baseTypes = (type1 as any).getBaseTypes?.() || [];
+ for (const baseType of baseTypes) {
+ if (this.isExactlySameType(baseType, type2)) {
+ return true;
+ }
+ }
+ }
+ return false;
}
private handleNumericBigintCompare(node: ts.BinaryExpression): void {
diff --git a/ets2panda/linter/test/main/incompatible_function.ets b/ets2panda/linter/test/main/incompatible_function.ets
index e137fe5ecc7c1d95332b75831e1e407433c031df..687a6bb89ab5743d3bc7da68fe09903c76d1fe00 100644
--- a/ets2panda/linter/test/main/incompatible_function.ets
+++ b/ets2panda/linter/test/main/incompatible_function.ets
@@ -79,4 +79,16 @@ type FuncTypeNoParams = () => void;
let fNoParams: FuncTypeNoParams = () => {
return 0;
-};
\ No newline at end of file
+};
+
+class A {}
+
+class C extends Array {}
+
+class B {
+ private arr: Array = [];
+
+ test(): C {
+ return this.arr;
+ }
+}
\ No newline at end of file
diff --git a/ets2panda/linter/test/main/incompatible_function.ets.arkts2.json b/ets2panda/linter/test/main/incompatible_function.ets.arkts2.json
index da908095d3d48a520457fd8c8455423a513ff7ff..2757f6f80c22bafa79806545a2c7b026468d11c0 100644
--- a/ets2panda/linter/test/main/incompatible_function.ets.arkts2.json
+++ b/ets2panda/linter/test/main/incompatible_function.ets.arkts2.json
@@ -203,6 +203,26 @@
"suggest": "",
"rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)",
"severity": "ERROR"
+ },
+ {
+ "line": 92,
+ "column": 12,
+ "endLine": 92,
+ "endColumn": 20,
+ "problem": "IncompationbleFunctionType",
+ "suggest": "",
+ "rule": "Stricter assignments into variables of function type (arkts-incompatible-function-types)",
+ "severity": "ERROR"
+ },
+ {
+ "line": 92,
+ "column": 12,
+ "endLine": 92,
+ "endColumn": 20,
+ "problem": "StructuralIdentity",
+ "suggest": "",
+ "rule": "Structural typing is not supported (arkts-no-structural-typing)",
+ "severity": "ERROR"
}
]
}
\ No newline at end of file