Skip to content

Update cxx operator implementation #58910

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 21 commits into from
Jun 2, 2022
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
65 changes: 50 additions & 15 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Memory.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/YAMLParser.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/VirtualFileSystem.h"
#include <algorithm>
#include <string>
#include <memory>
#include <string>

using namespace swift;
using namespace importer;
Expand All @@ -86,6 +88,17 @@ using clang::CompilerInvocation;

#pragma mark Internal data structures

namespace {
static std::string getOperatorNameForToken(std::string OperatorToken) {
#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \
if (OperatorToken == Spelling) { \
return #Name; \
};
#include "clang/Basic/OperatorKinds.def"
return "None";
}
} // namespace

namespace {
class HeaderImportCallbacks : public clang::PPCallbacks {
ClangImporter::Implementation &Impl;
Expand Down Expand Up @@ -2738,6 +2751,11 @@ ClangImporter::Implementation::lookupTypedef(clang::DeclarationName name) {

static bool isDeclaredInModule(const ClangModuleUnit *ModuleFilter,
const Decl *VD) {
// Sometimes imported decls get put into the clang header module. If we
// found one of these decls, don't filter it out.
if (VD->getModuleContext()->getName().str() == CLANG_HEADER_MODULE_NAME) {
return true;
}
auto ContainingUnit = VD->getDeclContext()->getModuleScopeContext();
return ModuleFilter == ContainingUnit;
}
Expand Down Expand Up @@ -2795,7 +2813,6 @@ static bool isVisibleFromModule(const ClangModuleUnit *ModuleFilter,

const clang::Decl *D = ClangNode.castAsDecl();
auto &ClangASTContext = ModuleFilter->getClangASTContext();

// We don't handle Clang submodules; pop everything up to the top-level
// module.
auto OwningClangModule = getClangTopLevelOwningModule(ClangNode,
Expand Down Expand Up @@ -2868,7 +2885,7 @@ class FilteringVisibleDeclConsumer : public swift::VisibleDeclConsumer {

void foundDecl(ValueDecl *VD, DeclVisibilityKind Reason,
DynamicLookupInfo dynamicLookupInfo) override {
if (isVisibleFromModule(ModuleFilter, VD))
if (!VD->hasClangNode() || isVisibleFromModule(ModuleFilter, VD))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need !VD->hasClangNode() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! Basically it can happen that we try looking up the pure swift function that we synthesize which doesn't have a clang node (since we add it as an altDecl to the original clang operator that we import). And if that happens it will call isVisibleFromModule which assume it has clangNode which our pure swift function does not have. So this guards against that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's when VD isn't something we imported, but something we found via alternativeDecls. I think it would be a good idea to add a comment saying that. Maybe also add a comment to the top of the consumer that says it only filters imported decls/decls that have an associated clang node.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, does that mean that operators are effectively visible from Swift even if their module isn't imported? E.g.

// Foo.h
static MyClass operator+(MyClass a, MyClass b) { /* ... */ };
// A.swift
@_implementationOnly import Foo
// B.swift
import A
// + shouldn't be visible here

Could we instead add a clang decl to the synthesized functions to avoid a special case here? That's how this is handled for synthesized subscripts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@egorzhdan is that done in subscripts result->addMember(subscript) ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens in SwiftDeclConverter::makeSubscript, SubscriptDecl *subscript is created with a getterImpl->getClangNode() parameter.

Copy link
Contributor Author

@Huddie Huddie May 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm.. I was using createImplicit which doesn't have that option. But maybe createImported which does would be better here? And pass in the clangNode? And then call ->setImplicit() after to make it implicit?@zoecarver ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels a bit weird just cause this function technically wasn't imported.

NextConsumer.foundDecl(VD, Reason, dynamicLookupInfo);
}
};
Expand All @@ -2886,11 +2903,9 @@ class FilteringDeclaredDeclConsumer : public swift::VisibleDeclConsumer {

void foundDecl(ValueDecl *VD, DeclVisibilityKind Reason,
DynamicLookupInfo dynamicLookupInfo) override {
if (isDeclaredInModule(ModuleFilter, VD) ||
// Sometimes imported decls get put into the clang header module. If we
// found one of these decls, don't filter it out.
VD->getModuleContext()->getName().str() == CLANG_HEADER_MODULE_NAME)
if (isDeclaredInModule(ModuleFilter, VD)) {
NextConsumer.foundDecl(VD, Reason, dynamicLookupInfo);
}
}
};

Expand Down Expand Up @@ -4104,22 +4119,42 @@ bool ClangImporter::Implementation::forEachLookupTable(
bool ClangImporter::Implementation::lookupValue(SwiftLookupTable &table,
DeclName name,
VisibleDeclConsumer &consumer) {

auto &clangCtx = getClangASTContext();
auto clangTU = clangCtx.getTranslationUnitDecl();

bool declFound = false;

// For operators we have to look up static member functions in addition to the
// top-level function lookup below.
if (name.isOperator()) {
for (auto entry : table.lookupMemberOperators(name.getBaseName())) {
if (isVisibleClangEntry(entry)) {
if (auto decl = dyn_cast_or_null<ValueDecl>(
importDeclReal(entry->getMostRecentDecl(), CurrentVersion))) {
consumer.foundDecl(decl, DeclVisibilityKind::VisibleAtTopLevel);
declFound = true;

auto findAndConsumeBaseNameFromTable = [this, &table, &consumer, &declFound,
&name](DeclBaseName declBaseName) {
for (auto entry : table.lookupMemberOperators(declBaseName)) {
if (isVisibleClangEntry(entry)) {
if (auto decl = dyn_cast_or_null<ValueDecl>(
importDeclReal(entry->getMostRecentDecl(), CurrentVersion))) {
consumer.foundDecl(decl, DeclVisibilityKind::VisibleAtTopLevel);
declFound = true;
for (auto alternate : getAlternateDecls(decl)) {
if (alternate->getName().matchesRef(name)) {
consumer.foundDecl(alternate, DeclVisibilityKind::DynamicLookup,
DynamicLookupInfo::AnyObject);
}
}
}
}
}
};

findAndConsumeBaseNameFromTable(name.getBaseName());

// If CXXInterop is enabled we need to check the modified operator name as
// well
if (SwiftContext.LangOpts.EnableCXXInterop) {
auto declBaseName = DeclBaseName(SwiftContext.getIdentifier(
"__operator" + getOperatorNameForToken(
name.getBaseName().getIdentifier().str().str())));
findAndConsumeBaseNameFromTable(declBaseName);
}
}

Expand Down
167 changes: 152 additions & 15 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3593,6 +3593,26 @@ namespace {
// by changing the name of one. That changed method needs to be added
// to the lookup table since it cannot be found lazily.
if (auto cxxMethod = dyn_cast<clang::CXXMethodDecl>(m)) {
auto cxxOperatorKind = cxxMethod->getOverloadedOperator();

// Check if this method _is_ an overloaded operator but is not a
// call / subscript. Those 2 operators do not need static versions
if (cxxOperatorKind != clang::OverloadedOperatorKind::OO_None &&
cxxOperatorKind != clang::OverloadedOperatorKind::OO_Call &&
cxxOperatorKind !=
clang::OverloadedOperatorKind::OO_Subscript) {

auto opFuncDecl = makeOperator(MD, cxxMethod);

Impl.addAlternateDecl(MD, opFuncDecl);

auto msg = "use " + std::string{clang::getOperatorSpelling(cxxOperatorKind)} + " instead";
Impl.markUnavailable(MD,msg);

// Make the actual member operator private.
MD->overwriteAccess(AccessLevel::Private);
}

if (cxxMethod->getDeclName().isIdentifier()) {
auto &mutableFuncPtrs = Impl.cxxMethods[cxxMethod->getName()].second;
if(mutableFuncPtrs.contains(cxxMethod)) {
Expand Down Expand Up @@ -4133,11 +4153,6 @@ namespace {
if (!dc)
return nullptr;

// Support for importing operators is temporarily disabled: rdar://91070109
if (decl->getDeclName().getNameKind() == clang::DeclarationName::CXXOperatorName &&
decl->getDeclName().getCXXOverloadedOperator() != clang::OO_Subscript)
return nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one thing we will probably want to keep disabled is templated operators. Those cause issues with ambiguity. But we can probably disable those in a follow up patch, given this patch is pretty big already.


// Handle cases where 2 CXX methods differ strictly in "constness"
// In such a case append a suffix ("Mutating") to the mutable version
// of the method when importing to swift
Expand Down Expand Up @@ -4285,16 +4300,7 @@ namespace {
templateParams);

if (auto *mdecl = dyn_cast<clang::CXXMethodDecl>(decl)) {
// Subscripts and call operators are imported as normal methods.
bool staticOperator = mdecl->isOverloadedOperator() &&
mdecl->getOverloadedOperator() != clang::OO_Call &&
mdecl->getOverloadedOperator() != clang::OO_Subscript;
if (mdecl->isStatic() ||
// C++ operators that are implemented as non-static member
// functions get imported into Swift as static member functions
// that use an additional parameter for the left-hand side operand
// instead of the receiver object.
staticOperator) {
if (mdecl->isStatic()) {
selfIdx = None;
} else {
// Swift imports the "self" param last, even for clang functions.
Expand Down Expand Up @@ -5393,9 +5399,13 @@ namespace {
/// \param setter function returning `UnsafeMutablePointer<T>`
/// \return subscript declaration
SubscriptDecl *makeSubscript(FuncDecl *getter, FuncDecl *setter);
FuncDecl *makeOperator(FuncDecl *operatorMethod,
clang::CXXMethodDecl *clangOperator);

VarDecl *makeComputedPropertyFromCXXMethods(FuncDecl *getter,
FuncDecl *setter);


/// Import the accessor and its attributes.
AccessorDecl *importAccessor(const clang::ObjCMethodDecl *clangAccessor,
AbstractStorageDecl *storage,
Expand Down Expand Up @@ -8080,6 +8090,133 @@ SwiftDeclConverter::makeSubscript(FuncDecl *getter, FuncDecl *setter) {
return subscript;
}

static std::pair<BraceStmt *, bool>
synthesizeOperatorMethodBody(AbstractFunctionDecl *afd, void *context) {
ASTContext &ctx = afd->getASTContext();

auto funcDecl = cast<FuncDecl>(afd);
auto methodDecl =
static_cast<FuncDecl *>(context); /* Swift version of CXXMethod */

SmallVector<Expr *, 8> forwardingParams;

// We start from +1 since the first param is our lhs. All other params are
// forwarded
for (auto itr = funcDecl->getParameters()->begin() + 1;
itr != funcDecl->getParameters()->end(); itr++) {
auto param = *itr;
Expr *paramRefExpr =
new (ctx) DeclRefExpr(param, DeclNameLoc(), /*Implicit=*/true);
paramRefExpr->setType(param->getType());

if (param->isInOut()) {
paramRefExpr->setType(LValueType::get(param->getType()));

paramRefExpr = new (ctx) InOutExpr(SourceLoc(), paramRefExpr,
param->getType(), /*isImplicit*/ true);
paramRefExpr->setType(InOutType::get(param->getType()));
}

forwardingParams.push_back(paramRefExpr);
}

auto methodExpr =
new (ctx) DeclRefExpr(methodDecl, DeclNameLoc(), /*implicit*/ true);
methodExpr->setType(methodDecl->getInterfaceType());

// Lhs parameter
auto baseParam = funcDecl->getParameters()->front();
Expr *baseExpr =
new (ctx) DeclRefExpr(baseParam, DeclNameLoc(), /*implicit*/ true);
baseExpr->setType(baseParam->getType());
if (baseParam->isInOut()) {
baseExpr->setType(LValueType::get(baseParam->getType()));

baseExpr = new (ctx) InOutExpr(SourceLoc(), baseExpr, baseParam->getType(),
/*isImplicit*/ true);
baseExpr->setType(InOutType::get(baseParam->getType()));
}

auto dotCallExpr =
DotSyntaxCallExpr::create(ctx, methodExpr, SourceLoc(), baseExpr);
dotCallExpr->setType(methodDecl->getMethodInterfaceType());
dotCallExpr->setThrows(false);

auto *argList = ArgumentList::forImplicitUnlabeled(ctx, forwardingParams);
auto callExpr = CallExpr::createImplicit(ctx, dotCallExpr, argList);
callExpr->setType(funcDecl->getResultInterfaceType());
callExpr->setThrows(false);

auto returnStmt = new (ctx) ReturnStmt(SourceLoc(), callExpr,
/*implicit=*/true);

auto body = BraceStmt::create(ctx, SourceLoc(), {returnStmt}, SourceLoc(),
/*implicit=*/true);
return {body, /*isTypeChecked=*/true};
}

FuncDecl *
SwiftDeclConverter::makeOperator(FuncDecl *operatorMethod,
clang::CXXMethodDecl *clangOperator) {
auto &ctx = Impl.SwiftContext;
auto opName =
clang::getOperatorSpelling(clangOperator->getOverloadedOperator());
auto paramList = operatorMethod->getParameters();
auto genericParamList = operatorMethod->getGenericParams();

auto opId = ctx.getIdentifier(opName);

auto parentCtx = operatorMethod->getDeclContext();

auto lhsParam = new (ctx) ParamDecl(
SourceLoc(),
SourceLoc(),Identifier(),
SourceLoc(),ctx.getIdentifier("lhs"),
parentCtx);

lhsParam->setInterfaceType(operatorMethod->getDeclContext()->getSelfInterfaceType());

if (operatorMethod->isMutating()) {
// This implicitly makes the parameter indirect.
lhsParam->setSpecifier(ParamSpecifier::InOut);
} else {
lhsParam->setSpecifier(ParamSpecifier::Default);
}

SmallVector<ParamDecl *, 4> newParams;
newParams.push_back(lhsParam);

for (auto param : *paramList) {
newParams.push_back(param);
}

auto oldArgNames = operatorMethod->getName().getArgumentNames();
SmallVector<Identifier, 4> newArgNames;
newArgNames.push_back(Identifier());

for (auto id : oldArgNames) {
newArgNames.push_back(id);
}

auto opDeclName = DeclName(
ctx,opId,
{newArgNames.begin(), newArgNames.end()});

auto topLevelStaticFuncDecl = FuncDecl::createImplicit(
ctx, StaticSpellingKind::None, opDeclName, SourceLoc(),
/*Async*/ false, /*Throws*/ false, genericParamList,
ParameterList::create(ctx, newParams),
operatorMethod->getResultInterfaceType(), parentCtx);

topLevelStaticFuncDecl->setAccess(AccessLevel::Public);
topLevelStaticFuncDecl->setIsDynamic(false);
topLevelStaticFuncDecl->setStatic();
topLevelStaticFuncDecl->setBodySynthesizer(synthesizeOperatorMethodBody,
operatorMethod);

return topLevelStaticFuncDecl;
}

void SwiftDeclConverter::addProtocols(
ProtocolDecl *protocol, SmallVectorImpl<ProtocolDecl *> &protocols,
llvm::SmallPtrSetImpl<ProtocolDecl *> &known) {
Expand Down
34 changes: 23 additions & 11 deletions lib/ClangImporter/ImportName.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ using namespace importer;
using clang::CompilerInstance;
using clang::CompilerInvocation;

static const char *getOperatorName(clang::OverloadedOperatorKind Operator) {
switch (Operator) {
case clang::OO_None:
case clang::NUM_OVERLOADED_OPERATORS:
return nullptr;

#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \
case clang::OO_##Name: \
return #Name;
#include "clang/Basic/OperatorKinds.def"
}

llvm_unreachable("Invalid OverloadedOperatorKind!");
}

/// Determine whether the given Clang selector matches the given
/// selector pieces.
Expand Down Expand Up @@ -1501,14 +1515,14 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
completionHandlerParamIndex =
swiftAsyncAttr->getCompletionHandlerIndex().getASTIndex();
}

if (const auto *asyncErrorAttr = D->getAttr<clang::SwiftAsyncErrorAttr>()) {
switch (auto convention = asyncErrorAttr->getConvention()) {
// No flag parameter in these cases.
case clang::SwiftAsyncErrorAttr::NonNullError:
case clang::SwiftAsyncErrorAttr::None:
break;

// Get the flag argument index and polarity from the attribute.
case clang::SwiftAsyncErrorAttr::NonZeroArgument:
case clang::SwiftAsyncErrorAttr::ZeroArgument:
Expand Down Expand Up @@ -1842,17 +1856,15 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
case clang::OverloadedOperatorKind::OO_LessEqual:
case clang::OverloadedOperatorKind::OO_GreaterEqual:
case clang::OverloadedOperatorKind::OO_AmpAmp:
case clang::OverloadedOperatorKind::OO_PipePipe:
baseName = clang::getOperatorSpelling(op);
case clang::OverloadedOperatorKind::OO_PipePipe: {
auto operatorName = isa<clang::CXXMethodDecl>(functionDecl)
? "__operator" + std::string{getOperatorName(op)}
: clang::getOperatorSpelling(op);
baseName = swiftCtx.getIdentifier(operatorName).str();
isFunction = true;
argumentNames.resize(
functionDecl->param_size() +
// C++ operators that are implemented as non-static member functions
// get imported into Swift as static member functions that use an
// additional parameter for the left-hand side operand instead of
// the receiver object.
(isa<clang::CXXMethodDecl>(D) ? 1 : 0));
addEmptyArgNamesForClangFunction(functionDecl, argumentNames);
break;
}
case clang::OverloadedOperatorKind::OO_Call:
baseName = "callAsFunction";
isFunction = true;
Expand Down
Loading