Skip to content

[Concurrency] Improve source compatibility with 'async' imports #34171

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
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
4 changes: 2 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4172,10 +4172,10 @@ NOTE(actor_mutable_state,none,
WARNING(shared_mutable_state_access,none,
"reference to %0 %1 is not concurrency-safe because it involves "
"shared mutable state", (DescriptiveDeclKind, DeclName))
NOTE(actor_isolated_witness,none,
ERROR(actor_isolated_witness,none,
"actor-isolated %0 %1 cannot be used to satisfy a protocol requirement",
(DescriptiveDeclKind, DeclName))
NOTE(actor_isolated_witness_could_be_async_handler,none,
ERROR(actor_isolated_witness_could_be_async_handler,none,
"actor-isolated %0 %1 cannot be used to satisfy a protocol requirement; "
"did you mean to make it an asychronous handler?",
(DescriptiveDeclKind, DeclName))
Expand Down
82 changes: 40 additions & 42 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3701,48 +3701,46 @@ void ClangImporter::Implementation::lookupValue(

// If we have a declaration and nothing matched so far, try the names used
// in other versions of Swift.
if (!anyMatching) {
if (auto clangDecl = entry.dyn_cast<clang::NamedDecl *>()) {
const clang::NamedDecl *recentClangDecl =
clangDecl->getMostRecentDecl();

CurrentVersion.forEachOtherImportNameVersion(
SwiftContext.LangOpts.EnableExperimentalConcurrency,
[&](ImportNameVersion nameVersion) {
if (anyMatching)
return;

// Check to see if the name and context match what we expect.
ImportedName newName = importFullName(recentClangDecl, nameVersion);
if (!newName.getDeclName().matchesRef(name))
return;

// If we asked for an async import and didn't find one, skip this.
// This filters out duplicates.
if (nameVersion.supportsConcurrency() &&
!newName.getAsyncInfo())
return;

const clang::DeclContext *clangDC =
newName.getEffectiveContext().getAsDeclContext();
if (!clangDC || !clangDC->isFileContext())
return;

// Then try to import the decl under the alternate name.
auto alternateNamedDecl =
cast_or_null<ValueDecl>(importDeclReal(recentClangDecl,
nameVersion));
if (!alternateNamedDecl || alternateNamedDecl == decl)
return;
assert(alternateNamedDecl->getName().matchesRef(name) &&
"importFullName behaved differently from importDecl");
if (alternateNamedDecl->getDeclContext()->isModuleScopeContext()) {
consumer.foundDecl(alternateNamedDecl,
DeclVisibilityKind::VisibleAtTopLevel);
anyMatching = true;
}
});
}
if (auto clangDecl = entry.dyn_cast<clang::NamedDecl *>()) {
const clang::NamedDecl *recentClangDecl =
clangDecl->getMostRecentDecl();

CurrentVersion.forEachOtherImportNameVersion(
SwiftContext.LangOpts.EnableExperimentalConcurrency,
[&](ImportNameVersion nameVersion) {
if (anyMatching)
return;

// Check to see if the name and context match what we expect.
ImportedName newName = importFullName(recentClangDecl, nameVersion);
if (!newName.getDeclName().matchesRef(name))
return;

// If we asked for an async import and didn't find one, skip this.
// This filters out duplicates.
if (nameVersion.supportsConcurrency() &&
!newName.getAsyncInfo())
return;

const clang::DeclContext *clangDC =
newName.getEffectiveContext().getAsDeclContext();
if (!clangDC || !clangDC->isFileContext())
return;

// Then try to import the decl under the alternate name.
auto alternateNamedDecl =
cast_or_null<ValueDecl>(importDeclReal(recentClangDecl,
nameVersion));
if (!alternateNamedDecl || alternateNamedDecl == decl)
return;
assert(alternateNamedDecl->getName().matchesRef(name) &&
"importFullName behaved differently from importDecl");
if (alternateNamedDecl->getDeclContext()->isModuleScopeContext()) {
consumer.foundDecl(alternateNamedDecl,
DeclVisibilityKind::VisibleAtTopLevel);
anyMatching = true;
}
});
}
}
}
Expand Down
15 changes: 10 additions & 5 deletions lib/ClangImporter/ImportName.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2084,13 +2084,15 @@ ImportedName NameImporter::importName(const clang::NamedDecl *decl,
bool NameImporter::forEachDistinctImportName(
const clang::NamedDecl *decl, ImportNameVersion activeVersion,
llvm::function_ref<bool(ImportedName, ImportNameVersion)> action) {
using ImportNameKey = std::pair<DeclName, EffectiveClangContext>;
using ImportNameKey = std::tuple<DeclName, EffectiveClangContext, bool>;
SmallVector<ImportNameKey, 8> seenNames;

ImportedName newName = importName(decl, activeVersion);
if (!newName)
return true;
ImportNameKey key(newName.getDeclName(), newName.getEffectiveContext());

ImportNameKey key(newName.getDeclName(), newName.getEffectiveContext(),
newName.getAsyncInfo().hasValue());
if (action(newName, activeVersion))
seenNames.push_back(key);

Expand All @@ -2101,15 +2103,18 @@ bool NameImporter::forEachDistinctImportName(
ImportedName newName = importName(decl, nameVersion);
if (!newName)
return;
ImportNameKey key(newName.getDeclName(), newName.getEffectiveContext());
ImportNameKey key(newName.getDeclName(), newName.getEffectiveContext(),
newName.getAsyncInfo().hasValue());

bool seen = llvm::any_of(
seenNames, [&key](const ImportNameKey &existing) -> bool {
return key.first == existing.first &&
key.second.equalsWithoutResolving(existing.second);
return std::get<0>(key) == std::get<0>(existing) &&
std::get<2>(key) == std::get<2>(existing) &&
std::get<1>(key).equalsWithoutResolving(std::get<1>(existing));
});
if (seen)
return;

if (action(newName, nameVersion))
seenNames.push_back(key);
});
Expand Down
6 changes: 4 additions & 2 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,16 @@ bool IsAsyncHandlerRequest::evaluate(

// Are we in a context where inference is possible?
auto dc = func->getDeclContext();
if (!dc->isTypeContext() || !dc->getParentSourceFile() ||
isa<ProtocolDecl>(dc) || !func->hasBody())
if (!dc->getSelfClassDecl() || !dc->getParentSourceFile() || !func->hasBody())
return false;

// Is it possible to infer @asyncHandler for this function at all?
if (!func->canBeAsyncHandler())
return false;

if (!dc->getSelfClassDecl()->isActor())
return false;

// Add an implicit @asyncHandler attribute and return true. We're done.
auto addImplicitAsyncHandlerAttr = [&] {
func->getAttrs().add(new (func->getASTContext()) AsyncHandlerAttr(true));
Expand Down
36 changes: 23 additions & 13 deletions lib/Sema/TypeCheckDeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2372,12 +2372,22 @@ bool swift::diagnoseObjCUnsatisfiedOptReqConflicts(SourceFile &sf) {
if (conflicts.empty())
continue;

auto bestConflict = conflicts[0];
for (auto conflict : conflicts) {
if (conflict->getName().isCompoundName() &&
conflict->getName().getArgumentNames().size() ==
req->getName().getArgumentNames().size()) {
bestConflict = conflict;
break;
}
}

// Diagnose the conflict.
auto reqDiagInfo = getObjCMethodDiagInfo(unsatisfied.second);
auto conflictDiagInfo = getObjCMethodDiagInfo(conflicts[0]);
auto conflictDiagInfo = getObjCMethodDiagInfo(bestConflict);
auto protocolName
= cast<ProtocolDecl>(req->getDeclContext())->getName();
Ctx.Diags.diagnose(conflicts[0],
Ctx.Diags.diagnose(bestConflict,
diag::objc_optional_requirement_conflict,
conflictDiagInfo.first,
conflictDiagInfo.second,
Expand All @@ -2387,9 +2397,9 @@ bool swift::diagnoseObjCUnsatisfiedOptReqConflicts(SourceFile &sf) {
protocolName);

// Fix the name of the witness, if we can.
if (req->getName() != conflicts[0]->getName() &&
req->getKind() == conflicts[0]->getKind() &&
isa<AccessorDecl>(req) == isa<AccessorDecl>(conflicts[0])) {
if (req->getName() != bestConflict->getName() &&
req->getKind() == bestConflict->getKind() &&
isa<AccessorDecl>(req) == isa<AccessorDecl>(bestConflict)) {
// They're of the same kind: fix the name.
unsigned kind;
if (isa<ConstructorDecl>(req))
Expand All @@ -2402,29 +2412,29 @@ bool swift::diagnoseObjCUnsatisfiedOptReqConflicts(SourceFile &sf) {
llvm_unreachable("unhandled @objc declaration kind");
}

auto diag = Ctx.Diags.diagnose(conflicts[0],
auto diag = Ctx.Diags.diagnose(bestConflict,
diag::objc_optional_requirement_swift_rename,
kind, req->getName());

// Fix the Swift name.
fixDeclarationName(diag, conflicts[0], req->getName());
fixDeclarationName(diag, bestConflict, req->getName());

// Fix the '@objc' attribute, if needed.
if (!conflicts[0]->canInferObjCFromRequirement(req))
fixDeclarationObjCName(diag, conflicts[0],
conflicts[0]->getObjCRuntimeName(),
if (!bestConflict->canInferObjCFromRequirement(req))
fixDeclarationObjCName(diag, bestConflict,
bestConflict->getObjCRuntimeName(),
req->getObjCRuntimeName(),
/*ignoreImpliedName=*/true);
}

// @nonobjc will silence this warning.
bool hasExplicitObjCAttribute = false;
if (auto objcAttr = conflicts[0]->getAttrs().getAttribute<ObjCAttr>())
if (auto objcAttr = bestConflict->getAttrs().getAttribute<ObjCAttr>())
hasExplicitObjCAttribute = !objcAttr->isImplicit();
if (!hasExplicitObjCAttribute)
Ctx.Diags.diagnose(conflicts[0], diag::req_near_match_nonobjc, true)
Ctx.Diags.diagnose(bestConflict, diag::req_near_match_nonobjc, true)
.fixItInsert(
conflicts[0]->getAttributeInsertionLoc(/*forModifier=*/false),
bestConflict->getAttributeInsertionLoc(/*forModifier=*/false),
"@nonobjc ");

Ctx.Diags.diagnose(getDeclContextLoc(unsatisfied.first),
Expand Down
Loading