Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3063,7 +3063,14 @@ export class Resolver extends DiagnosticEmitter {
let boundPrototype = classInstance.getMember(unboundOverridePrototype.name);
if (boundPrototype) { // might have errored earlier and wasn't added
assert(boundPrototype.kind == ElementKind.FunctionPrototype);
overrideInstance = this.resolveFunction(<FunctionPrototype>boundPrototype, instance.typeArguments);
let boundFuncPrototype = <FunctionPrototype>boundPrototype;
let overrideTypeParams = boundFuncPrototype.typeParameterNodes;
let numOverrideTypeParams = overrideTypeParams ? overrideTypeParams.length : 0;
let baseTypeArgs = instance.typeArguments;
let numBaseTypeArgs = baseTypeArgs ? baseTypeArgs.length : 0;
if (numOverrideTypeParams == numBaseTypeArgs) {
overrideInstance = this.resolveFunction(boundFuncPrototype, baseTypeArgs);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we allow this implement when the generic number are the same? I think it will crash also if the typing mismatched.
IMO, maybe we should forbidden all generic function in interface?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I see only one edge case in this scenario:

interface I {
  add<T, U>(x: T, y: U): U;
}

class C2 implements I {
  add<U, T>(x: T, y: U): U {
    return x;
  }
}

Copy link
Copy Markdown
Contributor Author

@Changqing-JING Changqing-JING Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. If we follow C++ design:

  1. template function aka. generic function can't be member in interface
  2. template function also can't override virtual function in interface.

Shall we do the same?

I would prefer to follow C++ design because generic function in interface is a confused behaviour
But it may break some existing code on user side.
What do maintainers prefer?

Copy link
Copy Markdown
Contributor Author

@Changqing-JING Changqing-JING Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HerrCai0907 @MaxGraey

Dear maintainers,

I have pushed a new commit to forbidden interface function and abstract function to be generic.
Could you check this design concept.

By the way, I just awared that if base class and child class one is generic, the other one is non-generic, then there is another crash. But I would prefer to fix it in another PR. I just left a fix me in current PR.

var called: string = "";

class A {
  foo<T>(x: T): void {
    called = "A";
  }
}

class B extends A {
  foo(x: i32): void { // non-generic overrides generic
    called = "B";
  }
}

// Static dispatch: B.foo called directly
var b = new B();
b.foo(1);
assert(called == "B");
called = "";

// Virtual dispatch through A reference: which gets called?
var a: A = new B();
a.foo<i32>(1);
assert(called == "B"); // if vtable works, "B"; if fallback to base, "A"

}
}
if (overrideInstance) overrides.add(overrideInstance);
Expand Down Expand Up @@ -3439,6 +3446,19 @@ export class Resolver extends DiagnosticEmitter {
default: assert(false);
}
if (!member.is(CommonFlags.Abstract)) {
// A generic method cannot implement a non-generic interface method
// because monomorphization requires a concrete type to generate code,
// but virtual dispatch through the interface has no type arguments.
if (unimplemented.has(memberName)
&& member.kind == ElementKind.FunctionPrototype
&& unimplemented.get(memberName)!.kind == ElementKind.FunctionPrototype
) {
let memberTypeParams = (<FunctionPrototype>member).typeParameterNodes;
let ifaceTypeParams = (<FunctionPrototype>unimplemented.get(memberName)).typeParameterNodes;
let numMemberTypeParams = memberTypeParams ? memberTypeParams.length : 0;
let numIfaceTypeParams = ifaceTypeParams ? ifaceTypeParams.length : 0;
if (numMemberTypeParams != numIfaceTypeParams) continue;
}
unimplemented.delete(memberName);
}
}
Expand Down
7 changes: 7 additions & 0 deletions tests/compiler/override-typeparam-mismatch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"asc_flags": [],
"stderr": [
"TS2515: Non-abstract class 'override-typeparam-mismatch/CC' does not implement inherited abstract member 'foo' from 'override-typeparam-mismatch/I'.",
"EOF"
]
}
14 changes: 14 additions & 0 deletions tests/compiler/override-typeparam-mismatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
interface I {
foo(x: i32): i32;
}

class CC implements I {
foo<T>(x: i32): i32 {
return x;
}
}

let c:I = new CC();
c.foo(1);

ERROR("EOF");
Loading