From 50417ae7b1299de33b2a1d9b1aab2c9ef233c3b9 Mon Sep 17 00:00:00 2001 From: Changqing Jing Date: Sat, 11 Apr 2026 20:35:17 +0800 Subject: [PATCH 1/2] WIP --- src/diagnosticMessages.json | 1 + src/program.ts | 10 +++++++ src/resolver.ts | 27 ++++++++++++------- tests/compiler/override-generic-mismatch.json | 8 ++++++ tests/compiler/override-generic-mismatch.ts | 25 +++++++++++++++++ 5 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 tests/compiler/override-generic-mismatch.json create mode 100644 tests/compiler/override-generic-mismatch.ts diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 6b98f6fd34..016ba4ec3f 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -53,6 +53,7 @@ "Definitive assignment has no effect on local variables.": 239, "Ambiguous operator overload '{0}' (conflicting overloads '{1}' and '{2}').": 240, "An interface or abstract method '{0}' cannot have type parameters.": 241, + "Cannot override generic method '{0}' with a non-generic method or vice versa.": 242, "Importing the table disables some indirect call optimizations.": 901, "Exporting the table disables some indirect call optimizations.": 902, diff --git a/src/program.ts b/src/program.ts index e5e27ebc59..3522465ba0 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1557,6 +1557,16 @@ export class Program extends DiagnosticEmitter { ) { let thisMethod = thisMember; let baseMethod = baseMember; + let thisIsGeneric = thisMethod.is(CommonFlags.Generic); + let baseIsGeneric = baseMethod.is(CommonFlags.Generic); + if (thisIsGeneric != baseIsGeneric) { + this.errorRelated( + DiagnosticCode.Cannot_override_generic_method_0_with_a_non_generic_method_or_vice_versa, + thisMethod.identifierNode.range, baseMethod.identifierNode.range, + thisMethod.name + ); + return; + } if (!thisMethod.visibilityEquals(baseMethod)) { this.errorRelated( DiagnosticCode.Overload_signatures_must_all_be_public_private_or_protected, diff --git a/src/resolver.ts b/src/resolver.ts index 2a4df6c3e7..80e3e4c75f 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -2933,11 +2933,23 @@ export class Resolver extends DiagnosticEmitter { incompatibleOverride = false; } else { if (baseMember.kind == ElementKind.FunctionPrototype) { - // Possibly generic. Resolve with same type arguments to obtain the correct one. let basePrototype = baseMember; - let baseFunction = this.resolveFunction(basePrototype, typeArguments, new Map(), ReportMode.Swallow); - if (baseFunction && instance.signature.isAssignableTo(baseFunction.signature, true)) { - incompatibleOverride = false; + let baseIsGeneric = basePrototype.typeParameterNodes != null && basePrototype.typeParameterNodes.length > 0; + let instanceIsGeneric = typeArguments != null && typeArguments.length > 0; + if (baseIsGeneric != instanceIsGeneric) { + // Cannot mix generic and non-generic functions in an override chain + this.errorRelated( + DiagnosticCode.Cannot_override_generic_method_0_with_a_non_generic_method_or_vice_versa, + instance.identifierAndSignatureRange, baseMember.identifierAndSignatureRange, + methodOrPropertyName + ); + incompatibleOverride = false; // already reported + } else { + // Possibly generic. Resolve with same type arguments to obtain the correct one. + let baseFunction = this.resolveFunction(basePrototype, typeArguments, new Map(), ReportMode.Swallow); + if (baseFunction && instance.signature.isAssignableTo(baseFunction.signature, true)) { + incompatibleOverride = false; + } } } } @@ -3069,11 +3081,8 @@ export class Resolver extends DiagnosticEmitter { // arguments to forward to the monomorphized child. // - generic child → generic base: OK; type args come from the base call site. // - non-generic child → non-generic base: OK; plain vtable override. - // FIXME: non-generic child → generic base is also mismatched (resolveFunction - // would assert on typeArguments/typeParameterNodes length mismatch) but that - // case is not yet guarded here. The correct fix is to replace this condition - // with `boundFuncPrototype.is(Generic) == instance.is(Generic)`. - if (!boundFuncPrototype.is(CommonFlags.Generic) || instance.is(CommonFlags.Generic)) { + // - non-generic child → generic base: skip; mismatched generic-ness. + if (boundFuncPrototype.is(CommonFlags.Generic) == instance.is(CommonFlags.Generic)) { overrideInstance = this.resolveFunction(boundFuncPrototype, instance.typeArguments); } } diff --git a/tests/compiler/override-generic-mismatch.json b/tests/compiler/override-generic-mismatch.json new file mode 100644 index 0000000000..d719ccfd28 --- /dev/null +++ b/tests/compiler/override-generic-mismatch.json @@ -0,0 +1,8 @@ +{ + "asc_flags": [], + "stderr": [ + "AS242: Cannot override generic method 'foo' with a non-generic method or vice versa.", + "AS242: Cannot override generic method 'bar' with a non-generic method or vice versa.", + "EOF" + ] +} diff --git a/tests/compiler/override-generic-mismatch.ts b/tests/compiler/override-generic-mismatch.ts new file mode 100644 index 0000000000..1848d804b8 --- /dev/null +++ b/tests/compiler/override-generic-mismatch.ts @@ -0,0 +1,25 @@ +// Non-generic method overriding generic method +class A { + foo(x: T): void {} +} + +class B extends A { + foo(x: i32): void {} +} + +let a:A = new B(); +a.foo(1); + +// Generic method overriding non-generic method +class C { + bar(x: i32): void {} +} + +class D extends C { + bar(x: T): void {} +} + +let c:C = new D(); +c.bar(1); + +ERROR("EOF"); From 5fdd69b6dc3e9e7273fa45509055f263dcadcebd Mon Sep 17 00:00:00 2001 From: Changqing Jing Date: Sat, 11 Apr 2026 21:44:02 +0800 Subject: [PATCH 2/2] Fix --- src/resolver.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resolver.ts b/src/resolver.ts index 80e3e4c75f..56ee1c7ccb 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -2934,7 +2934,8 @@ export class Resolver extends DiagnosticEmitter { } else { if (baseMember.kind == ElementKind.FunctionPrototype) { let basePrototype = baseMember; - let baseIsGeneric = basePrototype.typeParameterNodes != null && basePrototype.typeParameterNodes.length > 0; + let baseTypeParameterNodes = basePrototype.typeParameterNodes; + let baseIsGeneric = baseTypeParameterNodes != null && baseTypeParameterNodes.length > 0; let instanceIsGeneric = typeArguments != null && typeArguments.length > 0; if (baseIsGeneric != instanceIsGeneric) { // Cannot mix generic and non-generic functions in an override chain