Skip to content

[cxx-interop] Import non-public inherited members #79348

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 32 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0f8b97a
[cxx-interop] Import non-public inherited members
j-hui Feb 1, 2025
1f853c0
Add tests for importing non-public inherited members
j-hui Feb 2, 2025
5438249
Check for null pointers
j-hui Feb 7, 2025
18ab1b4
Fix some test cases
j-hui Feb 7, 2025
8eec909
Handle inaccessible private members
j-hui Feb 11, 2025
9c39093
Formatting
j-hui Feb 11, 2025
a5ac168
Merge remote-tracking branch 'origin/main' into import-inherited-members
j-hui Feb 11, 2025
6a9a291
Add hash_value() to ClangInheritanceInfo
j-hui Feb 11, 2025
917bdd1
Import inaccessible private base class members but make them unavailable
j-hui Feb 12, 2025
17d2be9
Adjust module interface test case for using base members to acccount …
j-hui Feb 14, 2025
3ca3fde
Correct silly typo in header guard
j-hui Feb 14, 2025
8652f1b
Add test case for shadowing of non-public members
j-hui Feb 14, 2025
6bb3409
Formatting
j-hui Feb 14, 2025
606cc1d
Address review feedback
j-hui Feb 15, 2025
fccb266
Get rid of confusing and error-prone ClangInheritanceInfo::forUsingDe…
j-hui Feb 17, 2025
742aad9
Merge remote-tracking branch 'origin/main' into import-inherited-members
j-hui Feb 18, 2025
e18734a
Add 'using' tests with more emphasis on access levels and inheritance
j-hui Feb 20, 2025
0d2fac5
Add protected inheritance test case to using-base-members
j-hui Feb 20, 2025
ee61104
Add some test cases to using-base-members test case
j-hui Feb 21, 2025
34359c5
Comment out tests for some broken operators
j-hui Feb 21, 2025
e93cf5c
Eliminate nestedPrivate field from ClangInheritanceInfo
j-hui Feb 21, 2025
d99a9e8
Remove stray tee %.output
j-hui Feb 21, 2025
b70cff9
Account for multiple pointees in conformToCxxOptionalIfNeeded()
j-hui Feb 21, 2025
8f7cf9c
Use private inheritance for using-base-members
j-hui Feb 21, 2025
cd87c7e
Formatting
j-hui Feb 21, 2025
27960ee
Adjust access tests
j-hui Feb 22, 2025
bdd8ea4
REVERTME: logging for Windows CI
j-hui Feb 22, 2025
07af2d4
Revert "REVERTME: logging for Windows CI"
j-hui Feb 25, 2025
046c7df
Add hack that fixes MSVC std::optional
j-hui Feb 25, 2025
04e79dd
Revert "Account for multiple pointees in conformToCxxOptionalIfNeeded()"
j-hui Feb 25, 2025
f4da02a
Formatting
j-hui Feb 25, 2025
53502db
Fix test from reordering
j-hui Feb 25, 2025
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
14 changes: 11 additions & 3 deletions include/swift/AST/ClangModuleLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class SourceLocation;

namespace swift {

class ClangInheritanceInfo;
class ConcreteDeclRef;
class Decl;
class FuncDecl;
Expand Down Expand Up @@ -202,10 +203,17 @@ class ClangModuleLoader : public ModuleLoader {
/// Imports a clang decl directly, rather than looking up its name.
virtual Decl *importDeclDirectly(const clang::NamedDecl *decl) = 0;

/// Imports a clang decl from a base class, cloning it for \param newContext
/// if it wasn't cloned for this specific context before.
/// Clones an imported \param decl from its base class to its derived class
/// \param newContext where it is inherited. Its access level is determined
/// with respect to \param inheritance, which signifies whether \param decl
/// was inherited via C++ public/protected/private inheritance.
///
/// This function uses a cache so that it is idempotent; successive
/// invocations will only generate one cloned ValueDecl (and all return
/// a pointer to it). Returns a NULL pointer upon failure.
virtual ValueDecl *importBaseMemberDecl(ValueDecl *decl,
DeclContext *newContext) = 0;
DeclContext *newContext,
ClangInheritanceInfo inheritance) = 0;

/// Emits diagnostics for any declarations named name
/// whose direct declaration context is a TU.
Expand Down
117 changes: 115 additions & 2 deletions include/swift/ClangImporter/ClangImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#ifndef SWIFT_CLANG_IMPORTER_H
#define SWIFT_CLANG_IMPORTER_H

#include "swift/AST/Attr.h"
#include "swift/AST/AttrKind.h"
#include "swift/AST/ClangModuleLoader.h"
#include "clang/Basic/Specifiers.h"
Expand Down Expand Up @@ -72,6 +73,7 @@ namespace swift {
class ASTContext;
class CompilerInvocation;
class ClangImporterOptions;
class ClangInheritanceInfo;
class ClangModuleUnit;
class ClangNode;
class ConcreteDeclRef;
Expand Down Expand Up @@ -660,8 +662,8 @@ class ClangImporter final : public ClangModuleLoader {
/// Imports a clang decl directly, rather than looking up it's name.
Decl *importDeclDirectly(const clang::NamedDecl *decl) override;

ValueDecl *importBaseMemberDecl(ValueDecl *decl,
DeclContext *newContext) override;
ValueDecl *importBaseMemberDecl(ValueDecl *decl, DeclContext *newContext,
ClangInheritanceInfo inheritance) override;

/// Emits diagnostics for any declarations named name
/// whose direct declaration context is a TU.
Expand Down Expand Up @@ -779,6 +781,117 @@ ClangInvocationFileMapping getClangInvocationFileMapping(
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs = nullptr,
bool suppressDiagnostic = false);

/// Information used to compute the access level of inherited C++ members.
class ClangInheritanceInfo {
/// The cumulative inheritance access specifier, that is used to compute the
/// effective access level of a particular inherited member.
///
/// When constructing ClangInheritanceInfo for nested inheritance, this field
/// gets clamped to the least permissive level between its current value and
/// the inheritance access specifier.
///
/// If we encounter \e nested private inheritance in the class hierarchy
/// (i.e., private inheritance beyond the first level of inheritance), we set
/// the access level to nullopt to indicate that none of the members from
/// classes beyond that point in the hierarchy should be accessible. This case
/// must be treated separately from non-nested private inheritance, where
/// inherited members are private but accessible from extensions.
///
/// See ClangInheritanceInfo::cumulativeInheritedAccess() for an example.
std::optional<clang::AccessSpecifier> access;

public:
/// Default constructor for this class that is used as the base case when
/// recursively walking up a class inheritance hierarchy.
ClangInheritanceInfo() : access(clang::AS_none) {}

/// Inductive case for this class that is used to accumulate inheritance
/// metadata for cases of (nested) inheritance.
ClangInheritanceInfo(ClangInheritanceInfo prev, clang::CXXBaseSpecifier base)
: access(computeAccess(prev, base)) {}

/// Whether this info represents a case of nested private inheritance.
bool isNestedPrivate() const { return !access.has_value(); }

/// Whether this info represents a case of C++ inheritance.
///
/// Returns \c false for the default instance of this class.
bool isInheriting() const {
return isNestedPrivate() || *access != clang::AS_none;
}

/// Whether this is info represents a case of C++ inheritance.
operator bool() const { return isInheriting(); }

/// Compute the (Swift) access level for inherited base member \param decl,
/// for when its inherited (cloned) member in the derived class.
///
/// This access level is determined by whichever is more restrictive: what the
/// \param decl was declared with (in its base class), or what it is being
/// inherited with (ClangInheritanceInfo::access).
///
/// Always returns swift::AccessLevel::Public (i.e., corresponding to
/// clang::AS_none) if this ClangInheritanceInfo::isInheriting() is \c false.
AccessLevel accessForBaseDecl(const ValueDecl *baseDecl) const;

/// Marks \param clonedDecl as unavailable (using \c @available) if it
/// cannot be accessed from the derived class, either because \param baseDecl
/// was declared as private in the base class, or because \param clonedDecl
/// was inherited with private inheritance.
///
/// Does nothing if this ClangInheritanceInfo::isInheriting() is \c false.
void setUnavailableIfNecessary(const ValueDecl *baseDecl,
ValueDecl *clonedDecl) const;

friend llvm::hash_code hash_value(const ClangInheritanceInfo &info) {
return llvm::hash_combine(info.access);
}

private:
/// An example of how ClangInheritanceInfo:access is accumulated while
/// recursively traversing the class hierarchy starting from \c E:
///
/// \code{.cpp}
/// struct A { ... }; // access = nullopt (nested private)
/// struct B : private A { ... }; // access = protected
/// struct C : public B { ... }; // access = protected
/// struct D : protected C { ... }; // access = public
/// struct E : public D { ... }; // access = none [base case]
/// \endcode
///
/// Another example, this time with non-nested private inheritance:
///
/// \code{.cpp}
/// struct A { ... }; // access = nullopt
/// struct B : public A { ... }; // access = nullopt
/// struct C : private B { ... }; // access = private
/// struct D : public C { ... }; // access = private
/// struct E : private D { ... }; // access = none [base case]
/// \endcode
static std::optional<clang::AccessSpecifier>
computeAccess(ClangInheritanceInfo prev, clang::CXXBaseSpecifier base) {
auto baseAccess = base.getAccessSpecifier();
assert(baseAccess != clang::AS_none &&
"this should always be public, protected, or private");

if (!prev.isInheriting())
// This is the first level of inheritance, so we just take the access
// specifier from CXXBaseSpecifier. Note that this is the only scenario
// where we can have access = private.
return {baseAccess};

if (prev.isNestedPrivate() || baseAccess == clang::AS_private)
// This is a case of nested inheritance, and either we encountered nested
// private inheritance before, or this is our first time encountering it.
return std::nullopt;

static_assert(clang::AS_private > clang::AS_protected &&
clang::AS_protected > clang::AS_public &&
"using std::max() relies on this ordering");
return {std::max(*prev.access, baseAccess)};
}
};

} // end namespace swift

#endif
29 changes: 21 additions & 8 deletions include/swift/ClangImporter/ClangImporterRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "swift/Basic/Statistic.h"
#include "swift/ClangImporter/ClangImporter.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Specifiers.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/TinyPtrVector.h"

Expand Down Expand Up @@ -133,25 +134,33 @@ class CXXNamespaceMemberLookup
};

/// The input type for a record member lookup request.
///
/// These lookups may be requested recursively in the case of inheritance,
/// for which we separately keep track of the derived class where we started
/// looking (startDecl) and the access level for the current inheritance.
struct ClangRecordMemberLookupDescriptor final {
NominalTypeDecl *recordDecl;
NominalTypeDecl *inheritingDecl;
DeclName name;
NominalTypeDecl *recordDecl; // Where we are currently looking
NominalTypeDecl *inheritingDecl; // Where we started looking from
DeclName name; // What we are looking for
ClangInheritanceInfo inheritance;

ClangRecordMemberLookupDescriptor(NominalTypeDecl *recordDecl, DeclName name)
: recordDecl(recordDecl), inheritingDecl(recordDecl), name(name) {
: recordDecl(recordDecl), inheritingDecl(recordDecl), name(name),
inheritance() {
assert(isa<clang::RecordDecl>(recordDecl->getClangDecl()));
}

friend llvm::hash_code
hash_value(const ClangRecordMemberLookupDescriptor &desc) {
return llvm::hash_combine(desc.name, desc.recordDecl, desc.inheritingDecl);
return llvm::hash_combine(desc.name, desc.recordDecl, desc.inheritingDecl,
desc.inheritance);
}

friend bool operator==(const ClangRecordMemberLookupDescriptor &lhs,
const ClangRecordMemberLookupDescriptor &rhs) {
return lhs.name == rhs.name && lhs.recordDecl == rhs.recordDecl &&
lhs.inheritingDecl == rhs.inheritingDecl;
lhs.inheritingDecl == rhs.inheritingDecl &&
lhs.inheritance == rhs.inheritance;
}

friend bool operator!=(const ClangRecordMemberLookupDescriptor &lhs,
Expand All @@ -165,10 +174,14 @@ struct ClangRecordMemberLookupDescriptor final {
// This private constructor should only be used in ClangRecordMemberLookup,
// for recursively traversing base classes that inheritingDecl inherites from.
ClangRecordMemberLookupDescriptor(NominalTypeDecl *recordDecl, DeclName name,
NominalTypeDecl *inheritingDecl)
: recordDecl(recordDecl), inheritingDecl(inheritingDecl), name(name) {
NominalTypeDecl *inheritingDecl,
ClangInheritanceInfo inheritance)
: recordDecl(recordDecl), inheritingDecl(inheritingDecl), name(name),
inheritance(inheritance) {
assert(isa<clang::RecordDecl>(recordDecl->getClangDecl()));
assert(isa<clang::CXXRecordDecl>(inheritingDecl->getClangDecl()));
assert(inheritance.isInheriting() &&
"recursive calls should indicate inheritance");
assert(recordDecl != inheritingDecl &&
"recursive calls should lookup elsewhere");
}
Expand Down
Loading