Skip to content

Commit ae16014

Browse files
authored
Don't import a C++ class definition until the class is required to be complete. (#5865)
When importing a class definition, ask Clang to complete it first. This causes class template specializations to get instantiated as needed when the type-checking of Carbon requires a C++ class to be complete. It also allows Clang to implement things like modules-aware definition visibility checking. Don't reject importing a class with a virtual base if it's never required to be complete. Instead, defer diagnosing until the definition is required. This also removes the recursion from `MapType`, as mapping a class type no longer maps its definition. In order to get diagnostics from instantiation failures, fix a bug that caused any Clang diagnostics produced after the initial building of the `ASTUnit` to get discarded. This exposed some duplicate diagnostic issues in `ImportNameFromCpp` which are fixed here too.
1 parent 14f51d7 commit ae16014

File tree

13 files changed

+352
-81
lines changed

13 files changed

+352
-81
lines changed

toolchain/check/import_cpp.cpp

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,32 @@ static auto BuildClassDecl(Context& context,
525525
return {class_decl.class_id, context.types().GetAsTypeInstId(class_decl_id)};
526526
}
527527

528+
// Imports a record declaration from Clang to Carbon. If successful, returns
529+
// the new Carbon class declaration `InstId`.
530+
static auto ImportCXXRecordDecl(Context& context,
531+
clang::CXXRecordDecl* clang_decl)
532+
-> SemIR::InstId {
533+
auto import_ir_inst_id = AddImportIRInst(context, clang_decl->getLocation());
534+
535+
auto [class_id, class_inst_id] = BuildClassDecl(
536+
context, import_ir_inst_id, GetParentNameScopeId(context, clang_decl),
537+
AddIdentifierName(context, clang_decl->getName()));
538+
539+
// TODO: The caller does the same lookup. Avoid doing it twice.
540+
auto clang_decl_id = context.sem_ir().clang_decls().Add(
541+
{.decl = clang_decl->getCanonicalDecl(), .inst_id = class_inst_id});
542+
543+
// Name lookup into the Carbon class looks in the C++ class definition.
544+
auto& class_info = context.classes().Get(class_id);
545+
class_info.scope_id = context.name_scopes().Add(
546+
class_inst_id, SemIR::NameId::None, class_info.parent_scope_id);
547+
context.name_scopes()
548+
.Get(class_info.scope_id)
549+
.set_clang_decl_context_id(clang_decl_id);
550+
551+
return class_inst_id;
552+
}
553+
528554
// Determines the Carbon inheritance kind to use for a C++ class definition.
529555
static auto GetInheritanceKind(clang::CXXRecordDecl* class_def)
530556
-> SemIR::Class::InheritanceKind {
@@ -558,8 +584,6 @@ static auto GetInheritanceKind(clang::CXXRecordDecl* class_def)
558584

559585
// Checks that the specified finished class definition is valid and builds and
560586
// returns a corresponding complete type witness instruction.
561-
// TODO: Remove recursion into mapping field types.
562-
// NOLINTNEXTLINE(misc-no-recursion)
563587
static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
564588
SemIR::ImportIRInstId import_ir_inst_id,
565589
SemIR::TypeInstId class_type_inst_id,
@@ -726,21 +750,14 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
726750

727751
// Creates a class definition based on the information in the given Clang
728752
// declaration, which is assumed to be for a class definition.
729-
// TODO: Remove recursion into mapping field types.
730-
// NOLINTNEXTLINE(misc-no-recursion)
731753
static auto BuildClassDefinition(Context& context,
732754
SemIR::ImportIRInstId import_ir_inst_id,
733755
SemIR::ClassId class_id,
734756
SemIR::TypeInstId class_inst_id,
735-
SemIR::ClangDeclId clang_decl_id,
736757
clang::CXXRecordDecl* clang_def) -> void {
737758
auto& class_info = context.classes().Get(class_id);
738-
StartClassDefinition(context, class_info, class_inst_id);
739-
740-
// Name lookup into the Carbon class looks in the C++ class definition.
741-
context.name_scopes()
742-
.Get(class_info.scope_id)
743-
.set_clang_decl_context_id(clang_decl_id);
759+
CARBON_CHECK(!class_info.has_definition_started());
760+
class_info.definition_id = class_inst_id;
744761

745762
context.inst_block_stack().Push();
746763

@@ -757,41 +774,53 @@ static auto BuildClassDefinition(Context& context,
757774
class_info.body_block_id = context.inst_block_stack().Pop();
758775
}
759776

760-
// Mark the given `Decl` as failed in `clang_decls`.
761-
static auto MarkFailedDecl(Context& context, clang::Decl* clang_decl) {
762-
context.sem_ir().clang_decls().Add({.decl = clang_decl->getCanonicalDecl(),
763-
.inst_id = SemIR::ErrorInst::InstId});
764-
}
765-
766-
// Imports a record declaration from Clang to Carbon. If successful, returns
767-
// the new Carbon class declaration `InstId`.
768-
// TODO: Change `clang_decl` to `const &` when lookup is using `clang::DeclID`
769-
// and we don't need to store the decl for lookup context.
770-
// TODO: Remove recursion into mapping field types.
771-
// NOLINTNEXTLINE(misc-no-recursion)
772-
static auto ImportCXXRecordDecl(Context& context,
773-
clang::CXXRecordDecl* clang_decl)
774-
-> SemIR::InstId {
775-
clang::CXXRecordDecl* clang_def = clang_decl->getDefinition();
776-
if (clang_def) {
777-
clang_decl = clang_def;
778-
}
779-
auto import_ir_inst_id = AddImportIRInst(context, clang_decl->getLocation());
777+
auto ImportCppClassDefinition(Context& context, SemIR::LocId loc_id,
778+
SemIR::ClassId class_id,
779+
SemIR::ClangDeclId clang_decl_id) -> bool {
780+
clang::ASTUnit* ast = context.sem_ir().cpp_ast();
781+
CARBON_CHECK(ast);
780782

781-
auto [class_id, class_inst_id] = BuildClassDecl(
782-
context, import_ir_inst_id, GetParentNameScopeId(context, clang_decl),
783-
AddIdentifierName(context, clang_decl->getName()));
783+
auto* clang_decl = cast<clang::CXXRecordDecl>(
784+
context.sem_ir().clang_decls().Get(clang_decl_id).decl);
785+
auto class_inst_id = context.types().GetAsTypeInstId(
786+
context.classes().Get(class_id).first_owning_decl_id);
784787

785-
// TODO: The caller does the same lookup. Avoid doing it twice.
786-
auto clang_decl_id = context.sem_ir().clang_decls().Add(
787-
{.decl = clang_decl->getCanonicalDecl(), .inst_id = class_inst_id});
788+
// TODO: Map loc_id into a clang location and use it for diagnostics if
789+
// instantiation fails, instead of annotating the diagnostic with another
790+
// location.
791+
clang::SourceLocation loc = clang_decl->getLocation();
792+
Diagnostics::AnnotationScope annotate_diagnostics(
793+
&context.emitter(), [&](auto& builder) {
794+
CARBON_DIAGNOSTIC(InCppTypeCompletion, Note,
795+
"while completing C++ class type {0}", SemIR::TypeId);
796+
builder.Note(loc_id, InCppTypeCompletion,
797+
context.classes().Get(class_id).self_type_id);
798+
});
788799

789-
if (clang_def) {
790-
BuildClassDefinition(context, import_ir_inst_id, class_id, class_inst_id,
791-
clang_decl_id, clang_def);
800+
// Ask Clang whether the type is complete. This triggers template
801+
// instantiation if necessary.
802+
clang::DiagnosticErrorTrap trap(ast->getDiagnostics());
803+
if (!ast->getSema().isCompleteType(
804+
loc, context.ast_context().getRecordType(clang_decl))) {
805+
// Type is incomplete. Nothing more to do, but tell the caller if we
806+
// produced an error.
807+
return !trap.hasErrorOccurred();
792808
}
793809

794-
return class_inst_id;
810+
clang::CXXRecordDecl* clang_def = clang_decl->getDefinition();
811+
CARBON_CHECK(clang_def, "Complete type has no definition");
812+
813+
auto import_ir_inst_id =
814+
context.insts().GetCanonicalLocId(class_inst_id).import_ir_inst_id();
815+
BuildClassDefinition(context, import_ir_inst_id, class_id, class_inst_id,
816+
clang_def);
817+
return true;
818+
}
819+
820+
// Mark the given `Decl` as failed in `clang_decls`.
821+
static auto MarkFailedDecl(Context& context, clang::Decl* clang_decl) {
822+
context.sem_ir().clang_decls().Add({.decl = clang_decl->getCanonicalDecl(),
823+
.inst_id = SemIR::ErrorInst::InstId});
795824
}
796825

797826
// Creates an integer type of the given size.
@@ -837,9 +866,6 @@ static auto MapBuiltinType(Context& context, clang::QualType qual_type,
837866
}
838867

839868
// Maps a C++ record type to a Carbon type.
840-
// TODO: Support more record types.
841-
// TODO: Remove recursion mapping fields of class types.
842-
// NOLINTNEXTLINE(misc-no-recursion)
843869
static auto MapRecordType(Context& context, const clang::RecordType& type)
844870
-> TypeExpr {
845871
auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(type.getDecl());
@@ -862,8 +888,6 @@ static auto MapRecordType(Context& context, const clang::RecordType& type)
862888
// Maps a C++ type that is not a wrapper type such as a pointer to a Carbon
863889
// type.
864890
// TODO: Support more types.
865-
// TODO: Remove recursion mapping fields of class types.
866-
// NOLINTNEXTLINE(misc-no-recursion)
867891
static auto MapNonWrapperType(Context& context, clang::QualType type)
868892
-> TypeExpr {
869893
if (const auto* builtin_type = type->getAs<clang::BuiltinType>()) {
@@ -928,8 +952,6 @@ static auto MapPointerType(Context& context, SemIR::LocId loc_id,
928952
// Maps a C++ type to a Carbon type. `type` should not be canonicalized because
929953
// we check for pointer nullability and nullability will be lost by
930954
// canonicalization.
931-
// TODO: Remove recursion mapping fields of class types.
932-
// NOLINTNEXTLINE(misc-no-recursion)
933955
static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
934956
-> TypeExpr {
935957
// Unwrap any type modifiers and wrappers.
@@ -1318,7 +1340,6 @@ static auto AddDependentUnimportedTypeDecls(const Context& context,
13181340

13191341
if (const auto* record_type = type->getAs<clang::RecordType>()) {
13201342
AddDependentDecl(context, record_type->getDecl(), decls);
1321-
// TODO: Also import bases and fields if the class is defined.
13221343
}
13231344
}
13241345

toolchain/check/import_cpp.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
3030
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
3131
-> SemIR::ScopeLookupResult;
3232

33+
// Given a class declaration that was imported from C++, attempt to import a
34+
// corresponding class definition. Returns true if nothing went wrong (whether
35+
// or not a definition could be imported), false if a diagnostic was produced.
36+
auto ImportCppClassDefinition(Context& context, SemIR::LocId loc_id,
37+
SemIR::ClassId class_id,
38+
SemIR::ClangDeclId clang_decl_id) -> bool;
39+
3340
} // namespace Carbon::Check
3441

3542
#endif // CARBON_TOOLCHAIN_CHECK_IMPORT_CPP_H_

toolchain/check/testdata/interop/cpp/class/base.carbon

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,19 @@ fn ConvertB(p: Cpp.C*) -> Cpp.B* {
158158
struct A { int a; };
159159
struct B : virtual A {};
160160

161+
void UseB(B * _Nonnull p);
162+
163+
// --- use_virtual_inheritance_incomplete.carbon
164+
165+
library "[[@TEST_NAME]]";
166+
167+
import Cpp library "virtual_inheritance.h";
168+
169+
fn Convert(p: Cpp.B*) {
170+
// OK, doesn't require `Cpp.B` to be a complete type.
171+
Cpp.UseB(p);
172+
}
173+
161174
// --- fail_todo_use_virtual_inheritance.carbon
162175

163176
library "[[@TEST_NAME]]";
@@ -166,11 +179,11 @@ library "[[@TEST_NAME]]";
166179
// CHECK:STDERR: ./virtual_inheritance.h:3: error: semantics TODO: `class with virtual bases` [SemanticsTodo]
167180
import Cpp library "virtual_inheritance.h";
168181

169-
// CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+4]]:15: note: in `Cpp` name lookup for `B` [InCppNameLookup]
170-
// CHECK:STDERR: fn Convert(p: Cpp.B*) -> Cpp.A* {
171-
// CHECK:STDERR: ^~~~~
172-
// CHECK:STDERR:
173182
fn Convert(p: Cpp.B*) -> Cpp.A* {
183+
// CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+11]]:3: note: while completing C++ class type `Cpp.B` [InCppTypeCompletion]
184+
// CHECK:STDERR: return p;
185+
// CHECK:STDERR: ^~~~~~~~~
186+
// CHECK:STDERR:
174187
// CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+7]]:3: error: cannot implicitly convert expression of type `Cpp.B*` to `Cpp.A*` [ConversionFailure]
175188
// CHECK:STDERR: return p;
176189
// CHECK:STDERR: ^~~~~~~~~
@@ -221,9 +234,9 @@ class V {
221234
// CHECK:STDOUT:
222235
// CHECK:STDOUT: constants {
223236
// CHECK:STDOUT: %Derived: type = class_type @Derived [concrete]
224-
// CHECK:STDOUT: %Base: type = class_type @Base [concrete]
225237
// CHECK:STDOUT: %ptr.ddb: type = ptr_type %Derived [concrete]
226238
// CHECK:STDOUT: %pattern_type.5c8: type = pattern_type %ptr.ddb [concrete]
239+
// CHECK:STDOUT: %Base: type = class_type @Base [concrete]
227240
// CHECK:STDOUT: %ptr.fb2: type = ptr_type %Base [concrete]
228241
// CHECK:STDOUT: %pattern_type.72a: type = pattern_type %ptr.fb2 [concrete]
229242
// CHECK:STDOUT: %ConvertPtr.type: type = fn_type @ConvertPtr [concrete]
@@ -345,9 +358,9 @@ class V {
345358
// CHECK:STDOUT:
346359
// CHECK:STDOUT: constants {
347360
// CHECK:STDOUT: %Derived: type = class_type @Derived [concrete]
348-
// CHECK:STDOUT: %Base: type = class_type @Base [concrete]
349361
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
350362
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
363+
// CHECK:STDOUT: %Base: type = class_type @Base [concrete]
351364
// CHECK:STDOUT: %Base.elem: type = unbound_element_type %Base, %i32 [concrete]
352365
// CHECK:STDOUT: }
353366
// CHECK:STDOUT:
@@ -389,8 +402,8 @@ class V {
389402
// CHECK:STDOUT:
390403
// CHECK:STDOUT: constants {
391404
// CHECK:STDOUT: %Derived: type = class_type @Derived [concrete]
392-
// CHECK:STDOUT: %Base: type = class_type @Base [concrete]
393405
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
406+
// CHECK:STDOUT: %Base: type = class_type @Base [concrete]
394407
// CHECK:STDOUT: %Base.f.type: type = fn_type @Base.f [concrete]
395408
// CHECK:STDOUT: %Base.f: %Base.f.type = struct_value () [concrete]
396409
// CHECK:STDOUT: %Base.g.type: type = fn_type @Base.g [concrete]

toolchain/check/testdata/interop/cpp/class/class.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,10 +439,10 @@ fn MyF(bar: Cpp.Bar*);
439439
// CHECK:STDOUT: constants {
440440
// CHECK:STDOUT: %Bar: type = class_type @Bar [concrete]
441441
// CHECK:STDOUT: %ptr.f68: type = ptr_type %Bar [concrete]
442-
// CHECK:STDOUT: %Bar.elem: type = unbound_element_type %Bar, %ptr.f68 [concrete]
443442
// CHECK:STDOUT: %pattern_type: type = pattern_type %ptr.f68 [concrete]
444443
// CHECK:STDOUT: %MyF.type: type = fn_type @MyF [concrete]
445444
// CHECK:STDOUT: %MyF: %MyF.type = struct_value () [concrete]
445+
// CHECK:STDOUT: %Bar.elem: type = unbound_element_type %Bar, %ptr.f68 [concrete]
446446
// CHECK:STDOUT: }
447447
// CHECK:STDOUT:
448448
// CHECK:STDOUT: imports {

toolchain/check/testdata/interop/cpp/class/field.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,10 @@ fn G(s: Cpp.Union) -> i32 {
165165
// CHECK:STDOUT: %Struct: type = class_type @Struct [concrete]
166166
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
167167
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
168+
// CHECK:STDOUT: %tuple.type.189: type = tuple_type (%i32, %i32, %i32) [concrete]
168169
// CHECK:STDOUT: %Struct.elem.86b: type = unbound_element_type %Struct, %i32 [concrete]
169170
// CHECK:STDOUT: %ptr.235: type = ptr_type %i32 [concrete]
170171
// CHECK:STDOUT: %Struct.elem.765: type = unbound_element_type %Struct, %ptr.235 [concrete]
171-
// CHECK:STDOUT: %tuple.type.189: type = tuple_type (%i32, %i32, %i32) [concrete]
172172
// CHECK:STDOUT: }
173173
// CHECK:STDOUT:
174174
// CHECK:STDOUT: imports {

toolchain/check/testdata/interop/cpp/class/struct.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,10 +451,10 @@ fn MyF(bar: Cpp.Bar*);
451451
// CHECK:STDOUT: constants {
452452
// CHECK:STDOUT: %Bar: type = class_type @Bar [concrete]
453453
// CHECK:STDOUT: %ptr.f68: type = ptr_type %Bar [concrete]
454-
// CHECK:STDOUT: %Bar.elem: type = unbound_element_type %Bar, %ptr.f68 [concrete]
455454
// CHECK:STDOUT: %pattern_type: type = pattern_type %ptr.f68 [concrete]
456455
// CHECK:STDOUT: %MyF.type: type = fn_type @MyF [concrete]
457456
// CHECK:STDOUT: %MyF: %MyF.type = struct_value () [concrete]
457+
// CHECK:STDOUT: %Bar.elem: type = unbound_element_type %Bar, %ptr.f68 [concrete]
458458
// CHECK:STDOUT: }
459459
// CHECK:STDOUT:
460460
// CHECK:STDOUT: imports {

0 commit comments

Comments
 (0)