Support signature mismatch between virtual fn and override fn. (#7198)

For now, hide `override fn`s from name lookup, so that the base class
version is always used, as the derived-class version does not have its
own vptr entry and so would not do the right thing if a further-derived
class adds a new override. This is implemented via a new access kind of
`Hidden`.

When checking the overriding function, pass in the expected `Self` type
and check the `self` parameter against that; the signature that we
generate for the thunk in the derived class is the base class signature
with the `self` parameter's type changed to the derived class.

When we generate a thunk for a virtual function, the thunk is assigned a
`virtual_index`, and the virtual function itself is not. When the thunk
makes a direct call to the virtual function, recognize this situation by
checking for a `virtual_index`, and perform a non-virtual call if there
isn't one.

Assisted-by: Gemini via Antigravity
This commit is contained in:
Richard Smith
2026-05-13 17:40:45 +00:00
committed by GitHub
parent 0b47efa57a
commit c33fb9fc48
18 changed files with 427 additions and 110 deletions
+97 -44
View File
@@ -202,17 +202,20 @@ base class Base {
class Derived {
extend base: Base;
// CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE+7]]:3: error: redeclaration differs because of parameter count of 1 [RedeclParamCountDiffers]
// CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE+10]]:3: error: 0 arguments passed to function expecting 1 argument [CallArgCountMismatch]
// CHECK:STDERR: override fn F[self: Self](v: i32);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE-8]]:3: note: previously declared with parameter count of 0 [RedeclParamCountPrevious]
// CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE+7]]:3: note: calling function declared here [InCallToEntity]
// CHECK:STDERR: override fn F[self: Self](v: i32);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE-11]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
// CHECK:STDERR: virtual fn F[self: Self]();
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
override fn F[self: Self](v: i32);
}
// --- fail_todo_impl_conversion.carbon
// --- impl_conversion.carbon
library "[[@TEST_NAME]]";
@@ -235,17 +238,14 @@ base class Base {
class Derived {
extend base: Base;
//@dump-sem-ir-begin
// CHECK:STDERR: fail_todo_impl_conversion.carbon:[[@LINE+7]]:3: error: function redeclaration differs because return type is `T2` [FunctionRedeclReturnTypeDiffers]
// CHECK:STDERR: override fn F[self: Self]() -> T2;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_impl_conversion.carbon:[[@LINE-9]]:3: note: previously declared with return type `T1` [FunctionRedeclReturnTypePrevious]
// CHECK:STDERR: virtual fn F[self: Self]() -> T1;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
override fn F[self: Self]() -> T2;
//@dump-sem-ir-end
}
fn Derived.F[unused self: Self]() -> T2 {
return {};
}
// --- fail_generic_virtual_decl.carbon
library "[[@TEST_NAME]]";
@@ -309,17 +309,20 @@ class T2 {
library "[[@TEST_NAME]]";
base class T1 {
// CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE+3]]:17: error: value expression passed to reference parameter [ValueForRefParam]
// CHECK:STDERR: virtual fn F1[self: Self]();
// CHECK:STDERR: ^~~~~~~~~~
virtual fn F1[self: Self]();
}
class T2 {
extend base: T1;
// CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE+7]]:18: error: redeclaration differs at implicit parameter 1 [RedeclParamDiffers]
// CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE+7]]:18: note: initializing function parameter [InCallToFunctionParam]
// CHECK:STDERR: override fn F1[ref self: Self]();
// CHECK:STDERR: ^~~~~~~~~~~~~~
// CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE-8]]:17: note: previous declaration's corresponding implicit parameter here [RedeclParamPrevious]
// CHECK:STDERR: fail_ref_self_mismatch.carbon:[[@LINE-8]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
// CHECK:STDERR: virtual fn F1[self: Self]();
// CHECK:STDERR: ^~~~~~~~~~
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
override fn F1[ref self: Self]();
}
@@ -431,17 +434,23 @@ class T1;
class T2;
base class Base(T:! type) {
// CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE+6]]:42: error: cannot implicitly convert expression of type `T1*` to `T2*` [ConversionFailure]
// CHECK:STDERR: virtual fn F[unused self: Self](unused t: T1*) { }
// CHECK:STDERR: ^~~~~~
// CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE+3]]:42: note: type `T1*` does not implement interface `Core.ImplicitAs(T2*)` [MissingImplInMemberAccessInContext]
// CHECK:STDERR: virtual fn F[unused self: Self](unused t: T1*) { }
// CHECK:STDERR: ^~~~~~
virtual fn F[unused self: Self](unused t: T1*) { }
}
class D1 {
extend base: Base(T1);
// CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE+7]]:43: error: type `<pattern for T2*>` of parameter 1 in redeclaration differs from previous parameter type `<pattern for T1*>` [RedeclParamDiffersType]
// CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE+7]]:43: note: initializing function parameter [InCallToFunctionParam]
// CHECK:STDERR: override fn F[unused self: Self](unused t: T2*) { }
// CHECK:STDERR: ^~~~~~
// CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE-8]]:42: note: previous declaration's corresponding parameter here [RedeclParamPrevious]
// CHECK:STDERR: fail_impl_generic_specifically_mismatch.carbon:[[@LINE-8]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
// CHECK:STDERR: virtual fn F[unused self: Self](unused t: T1*) { }
// CHECK:STDERR: ^~~~~~
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
override fn F[unused self: Self](unused t: T2*) { }
}
@@ -451,16 +460,22 @@ class D1 {
library "[[@TEST_NAME]]";
abstract class Base(T:! type) {
// CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE+6]]:42: error: cannot implicitly convert expression of type `T*` to `T` [ConversionFailure]
// CHECK:STDERR: virtual fn F[unused self: Self](unused t: T*) { }
// CHECK:STDERR: ^~~~~
// CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE+3]]:42: note: type `T*` does not implement interface `Core.ImplicitAs(T)` [MissingImplInMemberAccessInContext]
// CHECK:STDERR: virtual fn F[unused self: Self](unused t: T*) { }
// CHECK:STDERR: ^~~~~
virtual fn F[unused self: Self](unused t: T*) { }
}
class Derived(T:! type) {
extend base: Base(T);
// CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE+7]]:43: error: type `<pattern for T>` of parameter 1 in redeclaration differs from previous parameter type `<pattern for T*>` [RedeclParamDiffersType]
// CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE+7]]:43: note: initializing function parameter [InCallToFunctionParam]
// CHECK:STDERR: override fn F[unused self: Self](unused t: T) { }
// CHECK:STDERR: ^~~~
// CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE-7]]:42: note: previous declaration's corresponding parameter here [RedeclParamPrevious]
// CHECK:STDERR: fail_impl_generic_generic_mismatch.carbon:[[@LINE-7]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
// CHECK:STDERR: virtual fn F[unused self: Self](unused t: T*) { }
// CHECK:STDERR: ^~~~~
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
override fn F[unused self: Self](unused t: T) { }
}
@@ -688,7 +703,7 @@ class T2(G2:! type) {
// CHECK:STDOUT: .Self = constants.%Derived
// CHECK:STDOUT: .Modifiers = <poisoned>
// CHECK:STDOUT: .base = %.loc8
// CHECK:STDOUT: .H = %Derived.H.decl
// CHECK:STDOUT: .H [hidden] = %Derived.H.decl
// CHECK:STDOUT: extend %Base.ref
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -1005,7 +1020,7 @@ class T2(G2:! type) {
// CHECK:STDOUT: .Self = constants.%Derived
// CHECK:STDOUT: .AbstractIntermediate = <poisoned>
// CHECK:STDOUT: .base = %.loc13
// CHECK:STDOUT: .F = %Derived.F.decl
// CHECK:STDOUT: .F [hidden] = %Derived.F.decl
// CHECK:STDOUT: extend %AbstractIntermediate.ref
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -1052,7 +1067,7 @@ class T2(G2:! type) {
// CHECK:STDOUT: .Self = constants.%Derived
// CHECK:STDOUT: .VirtualIntermediate = <poisoned>
// CHECK:STDOUT: .base = %.loc13
// CHECK:STDOUT: .F = %Derived.F.decl
// CHECK:STDOUT: .F [hidden] = %Derived.F.decl
// CHECK:STDOUT: extend %VirtualIntermediate.ref
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -1070,33 +1085,48 @@ class T2(G2:! type) {
// CHECK:STDOUT:
// CHECK:STDOUT: override fn @Derived.F(%self.param: %Derived);
// CHECK:STDOUT:
// CHECK:STDOUT: --- fail_todo_impl_conversion.carbon
// CHECK:STDOUT: --- impl_conversion.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %T1: type = class_type @T1 [concrete]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
// CHECK:STDOUT: %T2: type = class_type @T2 [concrete]
// CHECK:STDOUT: %pattern_type.b8b: type = pattern_type %T2 [concrete]
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
// CHECK:STDOUT: %ImplicitAs.type.8c9: type = facet_type <@ImplicitAs, @ImplicitAs(%T1)> [concrete]
// CHECK:STDOUT: %ImplicitAs.impl_witness: <witness> = impl_witness @T2.as.ImplicitAs.impl.%ImplicitAs.impl_witness_table [concrete]
// CHECK:STDOUT: %T2.as.ImplicitAs.impl.Convert.type: type = fn_type @T2.as.ImplicitAs.impl.Convert [concrete]
// CHECK:STDOUT: %T2.as.ImplicitAs.impl.Convert: %T2.as.ImplicitAs.impl.Convert.type = struct_value () [concrete]
// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.8c9 = facet_value %T2, (%ImplicitAs.impl_witness) [concrete]
// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.597: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%T1, %ImplicitAs.facet) [concrete]
// CHECK:STDOUT: %Derived: type = class_type @Derived [concrete]
// CHECK:STDOUT: %pattern_type.9f6: type = pattern_type %Derived [concrete]
// CHECK:STDOUT: %.c13: Core.Form = init_form %T2 [concrete]
// CHECK:STDOUT: %Derived.F.type: type = fn_type @Derived.F [concrete]
// CHECK:STDOUT: %Derived.F: %Derived.F.type = struct_value () [concrete]
// CHECK:STDOUT: %Derived.F.type.5bfd52.1: type = fn_type @Derived.F.loc27 [concrete]
// CHECK:STDOUT: %Derived.F.f9b6d2.1: %Derived.F.type.5bfd52.1 = struct_value () [concrete]
// CHECK:STDOUT: %Derived.F.type.5bfd52.2: type = fn_type @Derived.F.loc23 [concrete]
// CHECK:STDOUT: %Derived.F.f9b6d2.2: %Derived.F.type.5bfd52.2 = struct_value () [concrete]
// CHECK:STDOUT: %.968: type = fn_type_with_self_type %ImplicitAs.WithSelf.Convert.type.597, %ImplicitAs.facet [concrete]
// CHECK:STDOUT: %Destroy.Op.type.bae255.2: type = fn_type @Destroy.Op.loc23_36.2 [concrete]
// CHECK:STDOUT: %Destroy.Op.651ba6.2: %Destroy.Op.type.bae255.2 = struct_value () [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @Derived {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %Derived.F.decl: %Derived.F.type = fn_decl @Derived.F [concrete = constants.%Derived.F] {
// CHECK:STDOUT: %self.param_patt: %pattern_type.9f6 = value_param_pattern [concrete]
// CHECK:STDOUT: %self.patt: %pattern_type.9f6 = at_binding_pattern self, %self.param_patt [concrete]
// CHECK:STDOUT: %return.param_patt: %pattern_type.b8b = out_param_pattern [concrete]
// CHECK:STDOUT: %return.patt: %pattern_type.b8b = return_slot_pattern %return.param_patt, %T2.ref [concrete]
// CHECK:STDOUT: %Derived.F.decl.loc23_36.1: %Derived.F.type.5bfd52.1 = fn_decl @Derived.F.loc27 [concrete = constants.%Derived.F.f9b6d2.1] {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: } {
// CHECK:STDOUT: %T2.ref: type = name_ref T2, file.%T2.decl [concrete = constants.%T2]
// CHECK:STDOUT: %.loc30: Core.Form = init_form %T2.ref [concrete = constants.%.c13]
// CHECK:STDOUT: %self.param: %Derived = value_param call_param0
// CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
// CHECK:STDOUT: %self: %Derived = value_binding self, %self.param
// CHECK:STDOUT: %return.param: ref %T2 = out_param call_param1
// CHECK:STDOUT: %return: ref %T2 = return_slot %return.param
// CHECK:STDOUT: %T2.ref.loc23: type = name_ref T2, file.%T2.decl [concrete = constants.%T2]
// CHECK:STDOUT: %.loc23: Core.Form = init_form %T2.ref.loc23 [concrete = constants.%.c13]
// CHECK:STDOUT: %self.param.loc23: %Derived = value_param call_param0
// CHECK:STDOUT: %Self.ref.loc23: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
// CHECK:STDOUT: %self.loc23: %Derived = value_binding self, %self.param.loc23
// CHECK:STDOUT: %return.param.loc23: ref %T2 = out_param call_param1
// CHECK:STDOUT: %return.loc23: ref %T2 = return_slot %return.param.loc23
// CHECK:STDOUT: }
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %Derived.F.decl.loc23_36.2: %Derived.F.type.5bfd52.2 = fn_decl @Derived.F.loc23 [concrete = constants.%Derived.F.f9b6d2.2] {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: } {
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: complete_type_witness = %complete_type
@@ -1107,7 +1137,7 @@ class T2(G2:! type) {
// CHECK:STDOUT: .Base = <poisoned>
// CHECK:STDOUT: .base = %.loc21
// CHECK:STDOUT: .T2 = <poisoned>
// CHECK:STDOUT: .F = %Derived.F.decl
// CHECK:STDOUT: .F [hidden] = %Derived.F.decl.loc23_36.1
// CHECK:STDOUT: extend %Base.ref
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -1116,10 +1146,33 @@ class T2(G2:! type) {
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: vtable @Derived.vtable {
// CHECK:STDOUT: @Derived.%Derived.F.decl
// CHECK:STDOUT: @Derived.%Derived.F.decl.loc23_36.2
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: override fn @Derived.F(%self.param: %Derived) -> out %return.param: %T2;
// CHECK:STDOUT: override fn @Derived.F.loc23(%self.param: %Derived) -> out %return.param: %T1 [thunk @Derived.%Derived.F.decl.loc23_36.1 for @Base.%Base.F.decl] {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %F.ref: %Derived.F.type.5bfd52.1 = name_ref F, @Derived.%Derived.F.decl.loc23_36.1 [concrete = constants.%Derived.F.f9b6d2.1]
// CHECK:STDOUT: %Derived.F.bound: <bound method> = bound_method %self.param, %F.ref
// CHECK:STDOUT: %.loc23_36.1: ref %T2 = temporary_storage
// CHECK:STDOUT: %Derived.F.call: init %T2 to %.loc23_36.1 = call %Derived.F.bound(%self.param)
// CHECK:STDOUT: %impl.elem0: %.968 = impl_witness_access constants.%ImplicitAs.impl_witness, element0 [concrete = constants.%T2.as.ImplicitAs.impl.Convert]
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %Derived.F.call, %impl.elem0
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %.loc23_36.2: ref %T2 = temporary %.loc23_36.1, %Derived.F.call
// CHECK:STDOUT: %.loc23_36.3: %T2 = acquire_value %.loc23_36.2
// CHECK:STDOUT: %T2.as.ImplicitAs.impl.Convert.call: init %T1 to %.loc17 = call %bound_method(%.loc23_36.3)
// CHECK:STDOUT: %.loc23_36.4: init %T1 = converted %Derived.F.call, %T2.as.ImplicitAs.impl.Convert.call
// CHECK:STDOUT: %Destroy.Op.bound: <bound method> = bound_method %.loc23_36.2, constants.%Destroy.Op.651ba6.2
// CHECK:STDOUT: %Destroy.Op.call: init %empty_tuple.type = call %Destroy.Op.bound(%.loc23_36.2)
// CHECK:STDOUT: return %.loc23_36.4 to %return.param
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @Destroy.Op.loc23_36.1(%self.param: ref %empty_struct_type) = "no_op";
// CHECK:STDOUT:
// CHECK:STDOUT: fn @Destroy.Op.loc23_36.2(%self.param: ref %T2) {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- generic_with_virtual.carbon
// CHECK:STDOUT:
@@ -1333,7 +1386,7 @@ class T2(G2:! type) {
// CHECK:STDOUT: .Base = <poisoned>
// CHECK:STDOUT: .T1 = <poisoned>
// CHECK:STDOUT: .base = %.loc9
// CHECK:STDOUT: .F = %D1.F.decl
// CHECK:STDOUT: .F [hidden] = %D1.F.decl
// CHECK:STDOUT: extend %Base
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -1409,7 +1462,7 @@ class T2(G2:! type) {
// CHECK:STDOUT: .Base = <poisoned>
// CHECK:STDOUT: .T = <poisoned>
// CHECK:STDOUT: .base = %.loc8
// CHECK:STDOUT: .F = %Derived.F.decl
// CHECK:STDOUT: .F [hidden] = %Derived.F.decl
// CHECK:STDOUT: extend %Base.loc8_23.1
// CHECK:STDOUT: }
// CHECK:STDOUT: }