Handle signature mismatch when a Carbon function overrides a C++ virtual function. (#7499)

When a Carbon virtual function overrides a C++ virtual function, we need
to export it with the C++ signature in order for it to work as an
override. Instead of mapping the C++ signature into Carbon and then back
again, use the original C++ signature from the base class as the
signature exported to C++.

Also add documentation explaining how we use thunks in C++ interop,
including in this new virtual function handling logic.
This commit is contained in:
Richard Smith
2026-07-15 18:42:03 +00:00
committed by GitHub
parent 14e8bffe24
commit 9ae73d2847
14 changed files with 635 additions and 174 deletions
+36 -23
View File
@@ -510,7 +510,6 @@ auto CarbonExternalASTSource::GetOrExportFunctionToCpp(
clang_function_decl,
context_->clang_decl_signatures().Add(std::move(thunk_signature))),
.inst_id = function.first_decl_id()});
return clang_function_decl;
}
@@ -687,6 +686,15 @@ auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void {
// TODO: Import any special member functions that affect class properties.
// Virtual functions whose definitions we have deferred generating until the
// class is complete.
struct PendingVirtualFunction {
SemIR::LocId loc_id;
SemIR::FunctionId function_id;
clang::CXXMethodDecl* method_decl;
};
llvm::SmallVector<PendingVirtualFunction> pending_virtual_functions;
if (class_info.vtable_decl_id.has_value()) {
auto vtable_inst_block = context_->inst_blocks().Get(
context_->vtables()
@@ -699,37 +707,42 @@ auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void {
continue;
}
// The Carbon vtable entry for a function override is a thunk, which wraps
// the function declaration to expose the signature of the overridden
// virtual function. Here we want to generate a C++ method declaration for
// the override, so we need to look through the thunk wrapping.
const auto [callee_function, function] =
[&]() -> std::pair<SemIR::CalleeFunction, const SemIR::Function&> {
auto vtable_callee_function =
GetCalleeAsFunction(context_->sem_ir(), vtable_entry_id);
const SemIR::Function& vtable_function =
context_->functions().Get(vtable_callee_function.function_id);
if (!vtable_function.thunk_id().has_value()) {
return {vtable_callee_function, vtable_function};
}
auto vtable_thunk =
context_->sem_ir().thunks().Get(vtable_function.thunk_id());
auto callee_function =
GetCalleeAsFunction(context_->sem_ir(), vtable_thunk.callee_id);
return {callee_function,
context_->functions().Get(callee_function.function_id)};
}();
const auto callee_function =
GetCalleeAsFunction(context_->sem_ir(), vtable_entry_id);
const SemIR::Function& function =
context_->functions().Get(callee_function.function_id);
// If this is a member of a base class, nothing to do here.
if (function.parent_scope_id != class_info.scope_id) {
continue;
}
auto* method_decl = cast<clang::CXXMethodDecl>(GetOrExportFunctionToCpp(
vtable_entry_id, callee_function.function_id));
auto* method_decl =
cast_or_null<clang::CXXMethodDecl>(ExportVirtualFunctionDeclToCpp(
*context_, SemIR::LocId(vtable_entry_id), class_decl,
callee_function.function_id));
if (!method_decl) {
continue;
}
context_->clang_sema().AddOverriddenMethods(class_decl, method_decl);
context_->clang_decls().Add(
{.key = SemIR::ClangDeclKey::ForFunctionDecl(
method_decl,
MakeVirtualFunctionSignature(*context_, method_decl)),
.inst_id = function.first_decl_id()});
pending_virtual_functions.push_back(
{.loc_id = SemIR::LocId(vtable_entry_id),
.function_id = callee_function.function_id,
.method_decl = method_decl});
}
}
class_decl->completeDefinition();
// Now the class is complete, we can define the virtual function thunks.
for (auto virtual_fn : pending_virtual_functions) {
DefineExportedVirtualFunction(*context_, virtual_fn.loc_id,
virtual_fn.function_id,
virtual_fn.method_decl);
}
}
auto CarbonExternalASTSource::layoutRecordType(