-
Notifications
You must be signed in to change notification settings - Fork 10.5k
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! Thank you so much Ehud! I only have a few minor comments.
Also, either in this PR or another (maybe better to do another one), would you mind re-enabling these tests on Windows (they should work now) and adding the test from this PR: https://github.com/apple/swift/pull/32869/files ?
And as we've discussed offline, I think this will allow us to get rid of a lot of special-case code throughout silgen! Thanks again :)
lib/ClangImporter/ClangImporter.cpp
Outdated
// For operators we have to look up static member functions in addition to the | ||
// top-level function lookup below. | ||
auto declBaseName = (SwiftContext.LangOpts.EnableCXXInterop && name.isOperator()) | ||
? DeclBaseName(SwiftContext.getIdentifier("__operator" + getOperatorNameForToken(std::string{name.getBaseName().getIdentifier()}))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
? DeclBaseName(SwiftContext.getIdentifier("__operator" + getOperatorNameForToken(std::string{name.getBaseName().getIdentifier()}))) | |
? DeclBaseName(SwiftContext.getIdentifier("__operator" + getOperatorNameForToken(name.getBaseName().getIdentifier().str()))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do .str().str()
to go from identifier
to StringRef
to std::string
Or should I make getOperatorNameForToken
take take StringRef
?
lib/ClangImporter/ImportDecl.cpp
Outdated
auto d = makeOperator(MD, cxxMethod); | ||
Impl.addAlternateDecl(MD, d); | ||
|
||
Impl.markUnavailable(MD, "use - operator"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we update this to use getOperatorNameForToken
?
lib/ClangImporter/ImportName.cpp
Outdated
@@ -1843,15 +1856,9 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D, | |||
case clang::OverloadedOperatorKind::OO_GreaterEqual: | |||
case clang::OverloadedOperatorKind::OO_AmpAmp: | |||
case clang::OverloadedOperatorKind::OO_PipePipe: | |||
baseName = clang::getOperatorSpelling(op); | |||
baseName = isa<clang::CXXMethodDecl>(functionDecl) ? StringRef{"__operator" + std::string{getOperatorName(op)}} : clang::getOperatorSpelling(op); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here std::string is a temporary, so you can't get a "ref" to it (StringRef is basically a pointer to the temporary memory). Beyond that, I think we'll need to make baseName a std::string or allocate an identifier (like you did above), because it's declared/used out of this scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is happening in the other cases here? Like:
baseName = "callAsFunction";
Is this not equivalent to:
baseName = StringRef{"callAsFunction"};
?
Is this an rValue
that gets moved into baseName
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String literals are sort of like globals, they last for the duration of the program, so you don't have to worry about their lifetime. std::string
s on the other hand are destroyed at the end of the scope if they're locals, otherwise at the end of the expression of they're temporaries. A StringRef
doesn't store anything, it's a reference to a char *
. If you take the reference of a "global" that's always fine. If you take the reference of a local/temporary, that's where we run into problems, because the string may have already been destroyed.
Also, would you mind running this whole PR through git-clang-format. You can just run |
@swift-ci please test. |
@@ -6,24 +6,21 @@ import MemberInline | |||
public func sub(_ lhs: inout LoadableIntWrapper, _ rhs: LoadableIntWrapper) -> LoadableIntWrapper { lhs - rhs } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an XFAIL at the top of this file and in a few of these operator tests. I suspect you didn't see these locally because your repo is out of date. Anyway, you'll also need to re-enable operators once you pull from main. Let me know if you have any questions about this.
lib/ClangImporter/ClangImporter.cpp
Outdated
// For operators we have to look up static member functions in addition to the | ||
// top-level function lookup below. | ||
auto declBaseName = (SwiftContext.LangOpts.EnableCXXInterop && name.isOperator()) | ||
? DeclBaseName(SwiftContext.getIdentifier("__operator" + getOperatorNameForToken(std::string{name.getBaseName().getIdentifier()}))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also nit: Seems to be missing clang-format
You can use either git-clang-format tool that is on ${PATH_TO_SWIFT}/llvm-project/clang/tools/clang-format and you can add it to your path, and run on the git directory after git add .
Or clang-format -lines 1630:1660 lib/Sema/CSFix.h -i for example to run at lines in specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Will do!
lib/ClangImporter/ClangImporter.cpp
Outdated
importDeclReal(entry->getMostRecentDecl(), CurrentVersion))) { | ||
consumer.foundDecl(decl, DeclVisibilityKind::VisibleAtTopLevel); | ||
declFound = true; | ||
for (auto entry : table.lookupMemberOperators(declBaseName)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like those two loops are exact the same code(just over different lists), could this be abstracted into a function/lambda in some way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think so! Just gotta rebase and fix some tests first.
…names __operator(Minus, Plus,...) and fix test cases
33c7002
to
e7fe6f0
Compare
@swift-ci please smoke test |
@swift-ci please smoke test |
Is this ready for re-review? |
Yes please :) |
@swift-ci please smoke test |
@@ -799,7 +799,7 @@ SwiftLookupTable::lookupMemberOperators(SerializedSwiftName baseName) { | |||
SmallVector<clang::NamedDecl *, 4> result; | |||
|
|||
// Find the lookup table entry for this base name. | |||
auto known = findOrCreate(LookupTable, baseName, | |||
auto known = findOrCreate(LookupTable, baseName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: spacing
// Support for importing operators is temporarily disabled: rdar://91070109 | ||
if (decl->getDeclName().getNameKind() == clang::DeclarationName::CXXOperatorName && | ||
decl->getDeclName().getCXXOverloadedOperator() != clang::OO_Subscript) | ||
return nullptr; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to do a final review pass but this generally looks good to me. Thanks again, Ehud!
lib/AST/DeclContext.cpp
Outdated
@@ -870,6 +870,7 @@ void IterableDeclContext::addMember(Decl *member, Decl *hint, bool insertAtHead) | |||
} | |||
} | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were those changes intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope lol fixing
lib/ClangImporter/ClangImporter.cpp
Outdated
if (isVisibleFromModule(ModuleFilter, VD)) | ||
NextConsumer.foundDecl(VD, Reason, dynamicLookupInfo); | ||
if (!VD->hasClangNode() || isVisibleFromModule(ModuleFilter, VD)) | ||
NextConsumer.foundDecl(VD, Reason, dynamicLookupInfo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably just run git-clang-formater
will fix all formatting issues =]
lib/ClangImporter/ImportDecl.cpp
Outdated
); | ||
|
||
topLevelStaticFuncDecl->setAccess(AccessLevel::Public); | ||
topLevelStaticFuncDecl->setImplicit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woudn't createImplicit
already set topLevelStaticFuncDecl
as implicit already making this redundant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! Just checked
lib/ClangImporter/ImportDecl.cpp
Outdated
ctx, | ||
StaticSpellingKind::None, | ||
opDeclName, SourceLoc(), | ||
false, false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
false, false, | |
/*Async=*/false, /*Throws=*/ false, |
lib/ClangImporter/ImportName.cpp
Outdated
@@ -1843,15 +1856,9 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D, | |||
case clang::OverloadedOperatorKind::OO_GreaterEqual: | |||
case clang::OverloadedOperatorKind::OO_AmpAmp: | |||
case clang::OverloadedOperatorKind::OO_PipePipe: | |||
baseName = clang::getOperatorSpelling(op); | |||
baseName = isa<clang::CXXMethodDecl>(functionDecl) ? swiftCtx.getIdentifier("__operator" + std::string{getOperatorName(op)}).str() : swiftCtx.getIdentifier(clang::getOperatorSpelling(op)).str(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: just a suggestion for simplification :)
baseName = isa<clang::CXXMethodDecl>(functionDecl) ? swiftCtx.getIdentifier("__operator" + std::string{getOperatorName(op)}).str() : swiftCtx.getIdentifier(clang::getOperatorSpelling(op)).str(); | |
auto operatorName = isa<clang::CXXMethodDecl>(functionDecl) ? "__operator" + std::string{getOperatorName(op)} ? getOperatorSpelling(op); | |
baseName = swiftCtx.getIdentifier(operatorName).str(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
baseName is defined before the switch (both the nested operator switch and outer switch) and used for all cases (not just operators)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand, the implication suggestion was just to do ternary on the argument of swiftCtx.getIdentifier
, but it was just a nit...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see! I misread (on GH phone) I thought you were just renaming baseName to operatorName
Ya this looks neat! Thanks
@swift-ci please test |
@zoecarver Ready for another pass! |
@@ -2868,7 +2880,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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
lib/ClangImporter/ImportDecl.cpp
Outdated
|
||
Impl.addAlternateDecl(MD, opFuncDecl); | ||
|
||
Impl.markUnavailable( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This formatting seems really weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are still some formatting issues but please land this once those are fixed. The patch looks good to me.
@swift-ci please test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, other than a couple of minor code formatting issues. Thanks!
There is a potential issue when an operator is declared in a different module from the struct, however, that never worked even with the previous implementation and can be fixed later.
@swift-ci please test |
@swift-ci please smoke test |
@swift-ci please smoke test |
@swift-ci please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 it!
@swift-ci please test |
@swift-ci please test macos |
This PR aims to improve the CXX class member operator implementation by making it a more true mapping from the CXX world.
Previous Impl
Previously if a class in CPP has an operator member function
it would get mapped into static method inside the generated
Foo
classThis achieves the correct result but the operator is a
static
function and therefore theFoo
class doesn't really have the same methods on it that it did in CPPNew Impl
This change maps the member operator function to a member function in the swift class & also create a separate swift operator static function which calls the member function
A few windows tests need tor enabled in a follow up
Huge thanks to @zoecarver !