Skip to content

🌸 [ClangImporter] Fix import of aliased enum cases #80780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1864,10 +1864,28 @@ namespace {
break;
}

/// A table mapping each raw value used in this enum to the clang or
/// Swift decl for the "canonical" constant corresponding to that raw
/// value. The clang decls represent cases that haven't yet been imported;
/// the Swift decls represent cases that have been imported before.
///
/// The problem we are trying to solve here is that C allows several
/// constants in the same enum to have the same raw value, but Swift does
/// not. We must therefore resolve collisions by selecting one case to be
/// the "canonical" one that will be imported as an \c EnumElementDecl
/// and importing the others as static \c VarDecl aliases of it. This
/// map knows which constants are canonical and can map a constant's raw
/// value to its corresponding canonical constant.
///
/// Note that unavailable constants don't get inserted into this table,
/// so if an unavailable constant has no available alias, it simply won't
/// be present here. (Potential raw value conflicts doesn't really matter
/// for them because they will be imported as unavailable anyway.)
llvm::SmallDenseMap<llvm::APSInt,
PointerUnion<const clang::EnumConstantDecl *,
EnumElementDecl *>, 8> canonicalEnumConstants;

// Fill in `canonicalEnumConstants` if it will be used.
if (enumKind == EnumKind::NonFrozenEnum ||
enumKind == EnumKind::FrozenEnum) {
for (auto constant : decl->enumerators()) {
Expand Down Expand Up @@ -1942,24 +1960,32 @@ namespace {
SwiftDeclConverter(Impl, getActiveSwiftVersion())
.importEnumCase(constant, decl, cast<EnumDecl>(result));
} else {
const clang::EnumConstantDecl *unimported =
// Will initially be nullptr if `canonicalCaseIter` points to a
// memoized result.
const clang::EnumConstantDecl *canonConstant =
canonicalCaseIter->
second.dyn_cast<const clang::EnumConstantDecl *>();

// Import the canonical enumerator for this case first.
if (unimported) {
// First, either import the canonical constant for this case,
// or extract the memoized result of a previous import (and use it
// to populate `canonConstant`).
if (canonConstant) {
enumeratorDecl = SwiftDeclConverter(Impl, getActiveSwiftVersion())
.importEnumCase(unimported, decl, cast<EnumDecl>(result));
.importEnumCase(canonConstant, decl, cast<EnumDecl>(result));
if (enumeratorDecl) {
// Memoize so we end up in the `else` branch next time.
canonicalCaseIter->getSecond() =
cast<EnumElementDecl>(enumeratorDecl);
}
} else {
enumeratorDecl =
canonicalCaseIter->second.get<EnumElementDecl *>();
canonConstant =
cast<clang::EnumConstantDecl>(enumeratorDecl->getClangDecl());
}

if (unimported != constant && enumeratorDecl) {
// If `constant` wasn't the `canonConstant`, import it as an alias.
if (canonConstant != constant && enumeratorDecl) {
ImportedName importedName =
Impl.importFullName(constant, getActiveSwiftVersion());
Identifier name = importedName.getBaseIdentifier(Impl.SwiftContext);
Expand All @@ -1975,6 +2001,7 @@ namespace {
}
}

// Now import each of the constant's alternate names.
Impl.forEachDistinctName(constant,
[&](ImportedName newName,
ImportNameVersion nameVersion) -> bool {
Expand Down Expand Up @@ -2025,6 +2052,19 @@ namespace {
}
}

// We don't always add an imported canonical constant to the enum's
// members right away, but we should have by the time we leave the loop.
// Verify that they are all in the enum's member list. (rdar://148213237)
if (CONDITIONAL_ASSERT_enabled()) {
for (const auto &entry : canonicalEnumConstants) {
auto importedCase = entry.second.dyn_cast<EnumElementDecl *>();
if (!importedCase)
continue;

ASSERT(llvm::is_contained(result->getCurrentMembers(), importedCase));
}
}

return result;
}

Expand Down
6 changes: 3 additions & 3 deletions test/ClangImporter/enum.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %s -verify
// RUN: not %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %s 2>&1 | %FileCheck %s
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %s -verify -compiler-assertions
// RUN: not %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %s -compiler-assertions 2>&1 | %FileCheck %s
// -- Check that we can successfully round-trip.
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -D IRGEN -emit-ir -primary-file %s | %FileCheck -check-prefix=CHECK-IR %s
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -D IRGEN -emit-ir -primary-file %s -compiler-assertions | %FileCheck -check-prefix=CHECK-IR %s

// REQUIRES: objc_interop

Expand Down