From 5cca1a6dc412a5e6f3ef09a167ce0b3caedb3ae0 Mon Sep 17 00:00:00 2001 From: John Hui Date: Mon, 10 Nov 2025 15:46:57 -0800 Subject: [PATCH] [ClangImporter] Do not import enum when already imported via DeclContext Before we import an enum, we import its DeclContext. If that DeclContext gets resolved to a struct that contains that field of that enum type, e.g.: typedef CF_OPTIONS(uint32_t, MyFlags) { ... } CF_SWIFT_NAME(MyCtx.Flags); struct MyStruct { MyFlags flags; ... } CF_SWIFT_NAME(MyCtx); We end up with a cycle, i.e., MyFlags -> MyCtx -> MyStruct -> MyFlags. Existing cycle-breaking mechanisms seem to prevent this from looping infinitely, but it leads to us importing two copies of the enum, which can cause errors later during CodeGen. This patch breaks the cycle by checking the cache again. N.B. as of this commit, the circular-import-as-member.swift test doesn't actually fail *without* the fix, because ClangImporter is quite lenient about importing the same clang::Decl multiple times. An earlier attempt at this patch ABORT()'d the compiler as soon as a duplicate is detected, but that led to more isseus beyond the scope of this patch. rdar://162317760 --- lib/ClangImporter/ImportDecl.cpp | 8 ++++ .../circular-import-as-member.swift | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 test/ClangImporter/circular-import-as-member.swift diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 3a5312f4ce622..320a52c129ab8 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -1612,6 +1612,14 @@ namespace { if (!dc) return nullptr; + // It's possible that we already encountered and imported decl while + // importing its decl context. If we are able to find a cached result, + // use it to avoid making a duplicate imported decl. + auto alreadyImported = + Impl.ImportedDecls.find({decl->getCanonicalDecl(), getVersion()}); + if (alreadyImported != Impl.ImportedDecls.end()) + return alreadyImported->second; + auto name = importedName.getBaseIdentifier(Impl.SwiftContext); // Create the enum declaration and record it. diff --git a/test/ClangImporter/circular-import-as-member.swift b/test/ClangImporter/circular-import-as-member.swift new file mode 100644 index 0000000000000..5af9885d87287 --- /dev/null +++ b/test/ClangImporter/circular-import-as-member.swift @@ -0,0 +1,37 @@ +// RUN: split-file %s %t +// RUN: %target-swift-frontend -typecheck -verify -I %t/Inputs -cxx-interoperability-mode=default -module-name main %t/program.swift +// RUN: %target-swift-frontend -typecheck -verify -I %t/Inputs -cxx-interoperability-mode=off -module-name main %t/program.swift +// REQUIRES: objc_interop + +//--- Inputs/module.modulemap +module TheModule { + header "the-header.h" +} + +//--- Inputs/the-header.h +#pragma once + +#define CF_SWIFT_NAME(_name) __attribute__((swift_name(#_name))) + +#define __CF_OPTIONS_ATTRIBUTES __attribute__((flag_enum,enum_extensibility(open))) +#if (__cplusplus) +#define CF_OPTIONS(_type, _name) __attribute__((availability(swift,unavailable))) _type _name; enum __CF_OPTIONS_ATTRIBUTES : _name +#else +#define CF_OPTIONS(_type, _name) enum __CF_OPTIONS_ATTRIBUTES _name : _type _name; enum _name : _type +#endif + +typedef CF_OPTIONS(unsigned, TheFlags) { + TheFlagsFoo = (1 << 1), + TheFlagsBar = (1 << 2) +} CF_SWIFT_NAME(The.Flags); + +typedef TheFlags DaFlags; + +struct TheContext { + TheFlags flags; +} CF_SWIFT_NAME(The); + +//--- program.swift +import TheModule + +func f(_ _: DaFlags) {}