Skip to content

[5.9][interop] do not import functions whose return type is not imported #65105

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

Closed
wants to merge 1 commit into from

Conversation

hyp
Copy link
Contributor

@hyp hyp commented Apr 12, 2023

(cherry picked from commit 8e0c17b)

Explanation: Clang importer can sometimes import C++ functions with the Never return type because the original type can not be imported. That is wrong, as making such calls isn't going to cleanup the return value. Therefore, such functions should not be imported into Swift. The compiler diagnoses them as unavailable with a helpful message about the return type.
Scope: Swift's and C++ interoperability, Clang importer.
Issue: Resolves #64401
Risk: Medium.
Testing: Swift unit tests.
Reviewer: @egorzhdan

@hyp hyp added the c++ interop Feature: Interoperability with C++ label Apr 12, 2023
@hyp hyp requested a review from a team as a code owner April 12, 2023 17:05
@hyp
Copy link
Contributor Author

hyp commented Apr 12, 2023

@swift-ci please test

@@ -3391,6 +3394,14 @@ namespace {
func->setAccess(AccessLevel::Public);
}

if (!isa<clang::CXXConstructorDecl>(decl) && !importedType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like !importedType should be a sufficient condition here. Why try importing it twice?

@@ -3391,6 +3394,14 @@ namespace {
func->setAccess(AccessLevel::Public);
}

if (!isa<clang::CXXConstructorDecl>(decl) && !importedType) {
if (!Impl.importFunctionReturnType(decl, result->getDeclContext())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We intentionally don't eagerly import some class template specializations. These really need to be lazily evaluated to respect C++ semantics. You are allowed to have a completely broken class template specialization, as long as the method is never called.

Copy link
Contributor

Choose a reason for hiding this comment

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

We are currently handling this logic on line 3304/3307: if (!bodyParams). We use body params, because we sometimes want to import the return type lazily. For better or worse, this is an invariant that we've had in the importer, we should change it with caution.

Copy link
Contributor

Choose a reason for hiding this comment

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

To elaborate on the first comment, this is the case I'm talking about, where we may violate C++ "semantics" if we try to do this too eagerly:

template<class T> struct Borked { static_assert(__is_same(T, char), "I'm broken!"); };

struct HasMethod { Borked<int> test(); };

void test(HasMethod hm) {
    // As long as this is never called, we're fine.
    // hm.test();
}

In other words, we cannot import test's return type eagerly here.

Impl.addImportDiagnostic(
decl, Diagnostic(diag::unsupported_return_type, decl),
decl->getSourceRange().getBegin());
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.

Just for the sake of completeness, I'd like to mention that Objective-C and C will also hit this codepath, so there's a (hopefully very small?) chance of behavior change there.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the important (and oh so terrible) condition here is

            (!(isa<clang::RecordType>(returnType) ||
               isa<clang::TemplateSpecializationType>(returnType)) ||
             // TODO: we currently don't lazily load operator return types, but
             // this should be trivial to add.
             clangDecl->isOverloadedOperator() ||
             // Dependant types are trivially mapped as Any.
             returnType->isDependentType()) 

If that condition is true, then we'll already apply the behavior of this patch by returning a null bodyParams. If that condition is false, that's where this patch kicks in. So thinking about C, I think we'll see a behavior change for any function that returns a record that we can't import. Now let me see if I can create one of those...

Copy link
Contributor

Choose a reason for hiding this comment

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

And here's an example:

struct alignas(128) OverAligned {
  char buff[128];
};

OverAligned test();

@hyp
Copy link
Contributor Author

hyp commented Jul 11, 2023

This was reverted from main, dropping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++ 🍒 release cherry pick Flag: Release branch cherry picks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[interop] C++ function returning NonCopyable type returns Never in Swift and is callable
3 participants