diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index 7d54ac666f3e..f9b27a9776d4 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -8,6 +8,7 @@ #include #include "clang/AST/ASTConsumer.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Sema/EnterExpressionEvaluationContext.h" #include "clang/Sema/Sema.h" #include "llvm/Support/Casting.h" @@ -251,11 +252,25 @@ static auto CreateCppFieldDecl(Context& context, return cpp_field_decl; } -auto ExportAllFieldsToCpp(Context& context, SemIR::Class& class_info) -> void { - if (class_info.fields_exported) { - return; - } +// Create an invalid `clang::FieldDecl`. This is only used as an error marker +// to indicate that a Carbon field has already been unsuccessfully exported. +static auto CreateInvalidFieldDecl(Context& context, + clang::DeclContext* decl_context) + -> clang::FieldDecl* { + clang::SourceLocation clang_loc; + auto* identifier_info = + context.clang_sema().getPreprocessor().getIdentifierInfo("invalid_field"); + auto cpp_type = context.ast_context().IntTy; + auto* field_decl = clang::FieldDecl::Create( + context.ast_context(), decl_context, /*StartLoc=*/clang_loc, + /*IdLoc=*/clang_loc, identifier_info, cpp_type, /*TInfo=*/nullptr, + /*BW=*/nullptr, + /*Mutable=*/true, clang::ICIS_NoInit); + field_decl->setInvalidDecl(); + return field_decl; +} +auto ExportAllFieldsToCpp(Context& context, SemIR::Class& class_info) -> void { const auto& class_scope = context.name_scopes().Get(class_info.scope_id); for (const auto& struct_field : @@ -266,6 +281,13 @@ auto ExportAllFieldsToCpp(Context& context, SemIR::Class& class_info) -> void { continue; } + // Return early if the field is already exported. Since fields are always + // exported as a group, this indicates all fields have been exported so + // there's no need to continue to the rest. + if (context.clang_decls().Lookup(class_field->inst_id)) { + return; + } + // Map the parent scope into the C++ AST. auto* decl_context = ExportNameScopeToCpp( context, SemIR::LocId(class_field->inst_id), class_info.scope_id); @@ -276,16 +298,19 @@ auto ExportAllFieldsToCpp(Context& context, SemIR::Class& class_info) -> void { auto* cpp_field_decl = CreateCppFieldDecl( context, class_scope, cast(decl_context), class_field->inst_id, class_field->inst); + + // If the field cannot be exported, create an invalid `FieldDecl` to store + // in `clang_decls`. This marks the field as unsuccessfully exported, so + // that we know not to attempt export again (which could create duplicate + // error diagnostics). if (!cpp_field_decl) { - continue; + cpp_field_decl = CreateInvalidFieldDecl(context, decl_context); } // Create and store the `ClangDeclId`. auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(cpp_field_decl); context.clang_decls().Add({.key = key, .inst_id = class_field->inst_id}); } - - class_info.fields_exported = true; } auto ExportFieldToCpp(Context& context, SemIR::InstId field_inst_id, @@ -303,7 +328,9 @@ auto ExportFieldToCpp(Context& context, SemIR::InstId field_inst_id, // Get the exported `clang::FieldDecl`. if (const auto* clang_decl = context.clang_decls().Lookup(field_inst_id)) { - return cast(clang_decl->decl()); + if (!clang_decl->decl()->isInvalidDecl()) { + return cast(clang_decl->decl()); + } } return nullptr; } diff --git a/toolchain/sem_ir/class.h b/toolchain/sem_ir/class.h index 476d884b861c..29221f40dd4f 100644 --- a/toolchain/sem_ir/class.h +++ b/toolchain/sem_ir/class.h @@ -46,9 +46,6 @@ struct ClassFields { // Whether this class or any base class has at least one virtual function. bool is_dynamic = false; - // Whether the class's fields have been exported to C++. - bool fields_exported = false; - // The following members are set at the `{` of the class definition. // The class scope.