Skip to content

Commit b72c11e

Browse files
authored
Support importing nested types from C++. (#5955)
Fix the algorithm for importing declarations in dependency order to properly walk the dependency graph. Add the parent declaration of a declaration to the dependency set so that we have a parent declaration context to import a declaration into. Fixes a crash when attempting to import a class whose parent is not imported.
1 parent 3c9b87a commit b72c11e

File tree

3 files changed

+158
-44
lines changed

3 files changed

+158
-44
lines changed

toolchain/check/import_cpp.cpp

Lines changed: 101 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -680,8 +680,8 @@ static auto ImportNamespaceDecl(Context& context,
680680
return result.inst_id;
681681
}
682682

683-
static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
684-
-> TypeExpr;
683+
static auto ImportTypeAndDependencies(Context& context, SemIR::LocId loc_id,
684+
clang::QualType type) -> TypeExpr;
685685

686686
// Creates a class declaration for the given class name in the given scope.
687687
// Returns the `InstId` for the declaration.
@@ -828,7 +828,7 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
828828
"Should not import definition for class with a virtual base");
829829

830830
auto [base_type_inst_id, base_type_id] =
831-
MapType(context, import_ir_inst_id, base.getType());
831+
ImportTypeAndDependencies(context, import_ir_inst_id, base.getType());
832832
if (!base_type_id.has_value()) {
833833
// TODO: If the base class's type can't be mapped, skip it.
834834
continue;
@@ -898,7 +898,7 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
898898

899899
auto field_name_id = AddIdentifierName(context, field->getName());
900900
auto [field_type_inst_id, field_type_id] =
901-
MapType(context, import_ir_inst_id, field->getType());
901+
ImportTypeAndDependencies(context, import_ir_inst_id, field->getType());
902902
if (!field_type_inst_id.has_value()) {
903903
// TODO: For now, just skip over fields whose types we can't map.
904904
continue;
@@ -1577,22 +1577,33 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
15771577
return function_info.first_owning_decl_id;
15781578
}
15791579

1580-
using DeclSet = llvm::SetVector<clang::Decl*>;
1580+
namespace {
1581+
// An item to be imported in an import worklist.
1582+
// TODO: If worklists ever become particularly large, consider changing this
1583+
// to use a `PointerIntPair`.
1584+
struct ImportItem {
1585+
// A declaration that we want to import.
1586+
clang::Decl* decl;
1587+
// Whether we have added `decl`'s dependencies to the worklist.
1588+
bool added_dependencies;
1589+
};
1590+
// A worklist of declarations to import.
1591+
using ImportWorklist = llvm::SmallVector<ImportItem>;
1592+
} // namespace
15811593

15821594
// Adds the given declaration to our list of declarations to import.
15831595
static auto AddDependentDecl(const Context& context, clang::Decl* decl,
1584-
DeclSet& decls) -> void {
1585-
// TODO: Do we need to also add the parent of the declaration, recursively?
1596+
ImportWorklist& worklist) -> void {
15861597
if (!IsClangDeclImported(context, decl)) {
1587-
decls.insert(decl);
1598+
worklist.push_back({.decl = decl, .added_dependencies = false});
15881599
}
15891600
}
15901601

15911602
// Finds all decls that need to be imported before importing the given type and
15921603
// adds them to the given set.
15931604
static auto AddDependentUnimportedTypeDecls(const Context& context,
15941605
clang::QualType type,
1595-
DeclSet& decls) -> void {
1606+
ImportWorklist& worklist) -> void {
15961607
while (true) {
15971608
if (type->isPointerType() || type->isReferenceType()) {
15981609
type = type->getPointeeType();
@@ -1605,41 +1616,45 @@ static auto AddDependentUnimportedTypeDecls(const Context& context,
16051616
}
16061617

16071618
if (const auto* record_type = type->getAs<clang::RecordType>()) {
1608-
AddDependentDecl(context, record_type->getDecl(), decls);
1619+
AddDependentDecl(context, record_type->getDecl(), worklist);
16091620
}
16101621
}
16111622

16121623
// Finds all decls that need to be imported before importing the given function
16131624
// and adds them to the given set.
16141625
static auto AddDependentUnimportedFunctionDecls(
16151626
const Context& context, const clang::FunctionDecl& clang_decl,
1616-
DeclSet& decls) -> void {
1627+
ImportWorklist& worklist) -> void {
16171628
for (const auto* param : clang_decl.parameters()) {
1618-
AddDependentUnimportedTypeDecls(context, param->getType(), decls);
1629+
AddDependentUnimportedTypeDecls(context, param->getType(), worklist);
16191630
}
1620-
AddDependentUnimportedTypeDecls(context, clang_decl.getReturnType(), decls);
1631+
AddDependentUnimportedTypeDecls(context, clang_decl.getReturnType(),
1632+
worklist);
16211633
}
16221634

16231635
// Finds all decls that need to be imported before importing the given
16241636
// declaration and adds them to the given set.
16251637
static auto AddDependentUnimportedDecls(const Context& context,
1626-
clang::Decl* clang_decl, DeclSet& decls)
1627-
-> void {
1628-
if (auto* parent_decl = GetParentDecl(clang_decl)) {
1629-
AddDependentDecl(context, parent_decl, decls);
1630-
}
1631-
1638+
clang::Decl* clang_decl,
1639+
ImportWorklist& worklist) -> void {
16321640
if (auto* clang_function_decl = clang_decl->getAsFunction()) {
1633-
AddDependentUnimportedFunctionDecls(context, *clang_function_decl, decls);
1641+
AddDependentUnimportedFunctionDecls(context, *clang_function_decl,
1642+
worklist);
16341643
} else if (auto* type_decl = dyn_cast<clang::TypeDecl>(clang_decl)) {
1635-
AddDependentUnimportedTypeDecls(
1636-
context, type_decl->getASTContext().getTypeDeclType(type_decl), decls);
1644+
if (!isa<clang::RecordDecl>(clang_decl)) {
1645+
AddDependentUnimportedTypeDecls(
1646+
context, type_decl->getASTContext().getTypeDeclType(type_decl),
1647+
worklist);
1648+
}
1649+
}
1650+
if (!isa<clang::TranslationUnitDecl>(clang_decl)) {
1651+
AddDependentDecl(context, GetParentDecl(clang_decl), worklist);
16371652
}
16381653
}
16391654

1640-
// Imports a declaration from Clang to Carbon. If successful, returns the
1641-
// instruction for the new Carbon declaration. Assumes all dependencies have
1642-
// already been imported.
1655+
// Imports a declaration from Clang to Carbon. Returns the instruction for the
1656+
// new Carbon declaration, which will be an ErrorInst on failure. Assumes all
1657+
// dependencies have already been imported.
16431658
static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
16441659
clang::Decl* clang_decl)
16451660
-> SemIR::InstId {
@@ -1658,6 +1673,8 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
16581673
type.getAsString()));
16591674
return SemIR::ErrorInst::InstId;
16601675
}
1676+
context.sem_ir().clang_decls().Add(
1677+
{.decl = clang_decl, .inst_id = type_inst_id});
16611678
return type_inst_id;
16621679
}
16631680
if (isa<clang::FieldDecl, clang::IndirectFieldDecl>(clang_decl)) {
@@ -1678,30 +1695,71 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
16781695
return SemIR::ErrorInst::InstId;
16791696
}
16801697

1698+
// Attempts to import a set of declarations. Returns `false` if an error was
1699+
// produced, `true` otherwise.
1700+
static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
1701+
ImportWorklist& worklist) -> bool {
1702+
// Walk the dependency graph in depth-first order, and import declarations
1703+
// once we've imported all of their dependencies.
1704+
while (!worklist.empty()) {
1705+
auto& item = worklist.back();
1706+
if (!item.added_dependencies) {
1707+
// Skip items we've already imported. We checked this when initially
1708+
// adding the item to the worklist, but it might have been added to the
1709+
// worklist twice before the first time we visited it. For example, this
1710+
// happens for `fn F(a: Cpp.T, b: Cpp.T)`.
1711+
if (IsClangDeclImported(context, item.decl)) {
1712+
worklist.pop_back();
1713+
continue;
1714+
}
1715+
1716+
// First time visiting this declaration (preorder): add its dependencies
1717+
// to the work list.
1718+
item.added_dependencies = true;
1719+
AddDependentUnimportedDecls(context, item.decl, worklist);
1720+
} else {
1721+
// Second time visiting this declaration (postorder): its dependencies are
1722+
// already imported, so we can import it now.
1723+
auto* decl = worklist.pop_back_val().decl;
1724+
auto inst_id = ImportDeclAfterDependencies(context, loc_id, decl);
1725+
CARBON_CHECK(inst_id.has_value());
1726+
if (inst_id == SemIR::ErrorInst::InstId) {
1727+
return false;
1728+
}
1729+
CARBON_CHECK(IsClangDeclImported(context, decl));
1730+
}
1731+
}
1732+
1733+
return true;
1734+
}
1735+
16811736
// Imports a declaration from Clang to Carbon. If successful, returns the
1682-
// instruction for the new Carbon declaration. All unimported dependencies would
1683-
// be imported first.
1737+
// instruction for the new Carbon declaration. All unimported dependencies are
1738+
// imported first.
16841739
static auto ImportDeclAndDependencies(Context& context, SemIR::LocId loc_id,
16851740
clang::Decl* clang_decl)
16861741
-> SemIR::InstId {
1687-
// Collect dependencies.
1688-
llvm::SetVector<clang::Decl*> clang_decls;
1689-
clang_decls.insert(clang_decl);
1690-
for (size_t i = 0; i < clang_decls.size(); ++i) {
1691-
AddDependentUnimportedDecls(context, clang_decls[i], clang_decls);
1692-
}
1693-
1694-
// Import dependencies in reverse order.
1695-
auto inst_id = SemIR::InstId::None;
1696-
for (clang::Decl* clang_decl_to_import : llvm::reverse(clang_decls)) {
1697-
inst_id =
1698-
ImportDeclAfterDependencies(context, loc_id, clang_decl_to_import);
1699-
if (!inst_id.has_value()) {
1700-
break;
1701-
}
1742+
// Collect dependencies by walking the dependency graph in depth-first order.
1743+
ImportWorklist worklist;
1744+
AddDependentDecl(context, clang_decl, worklist);
1745+
if (!ImportDeclSet(context, loc_id, worklist)) {
1746+
return SemIR::ErrorInst::InstId;
17021747
}
1748+
return LookupClangDeclInstId(context, clang_decl);
1749+
}
17031750

1704-
return inst_id;
1751+
// Imports a type from Clang to Carbon. If successful, returns the imported
1752+
// TypeId. All unimported dependencies are imported first.
1753+
static auto ImportTypeAndDependencies(Context& context, SemIR::LocId loc_id,
1754+
clang::QualType type) -> TypeExpr {
1755+
// Collect dependencies by walking the dependency graph in depth-first order.
1756+
ImportWorklist worklist;
1757+
AddDependentUnimportedTypeDecls(context, type, worklist);
1758+
if (!ImportDeclSet(context, loc_id, worklist)) {
1759+
return {.inst_id = SemIR::ErrorInst::TypeInstId,
1760+
.type_id = SemIR::ErrorInst::TypeId};
1761+
}
1762+
return MapType(context, loc_id, type);
17051763
}
17061764

17071765
// Maps `clang::AccessSpecifier` to `SemIR::AccessKind`.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon
6+
//
7+
// AUTOUPDATE
8+
// TIP: To test this file alone, run:
9+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/class/nested.carbon
10+
// TIP: To dump output, run:
11+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/class/nested.carbon
12+
13+
// --- import_nested_class.carbon
14+
15+
library "[[@TEST_NAME]]";
16+
17+
import Cpp inline '''
18+
struct A {
19+
struct B {
20+
struct C {
21+
int abc;
22+
};
23+
};
24+
B::C c;
25+
};
26+
''';
27+
28+
fn F(p: Cpp.A*) -> i32 {
29+
return p->c.abc;
30+
}

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Exceptions. See /LICENSE for license information.
33
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
44
//
5-
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/destroy.carbon
5+
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon
66
//
77
// AUTOUPDATE
88
// TIP: To test this file alone, run:
@@ -207,6 +207,32 @@ fn MyF() {
207207
//@dump-sem-ir-end
208208
}
209209

210+
// ============================================================================
211+
// Nested namespaces.
212+
// ============================================================================
213+
214+
// --- nested.carbon
215+
216+
library "[[@TEST_NAME]]";
217+
218+
import Cpp inline '''
219+
namespace A {
220+
namespace B {
221+
namespace C {
222+
struct X { int n; };
223+
}
224+
}
225+
}
226+
227+
struct Y {
228+
A::B::C::X x;
229+
};
230+
''';
231+
232+
fn Use(y: Cpp.Y) -> i32 {
233+
return y.x.n;
234+
}
235+
210236
// CHECK:STDOUT: --- import_single.carbon
211237
// CHECK:STDOUT:
212238
// CHECK:STDOUT: constants {

0 commit comments

Comments
 (0)