Skip to content

Commit 805d5c5

Browse files
authored
Merge pull request #69596 from ktoso/pick-wip-distributed-overloads
🍒[5.10][Distributed] Allow overloads with different types; mangling can handle it
2 parents fc6ba4e + 6ff4513 commit 805d5c5

File tree

7 files changed

+142
-75
lines changed

7 files changed

+142
-75
lines changed

include/swift/AST/Decl.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ struct OverloadSignature {
273273
/// Whether this is an async function.
274274
unsigned IsAsyncFunction : 1;
275275

276+
/// Whether this is an distributed function.
277+
unsigned IsDistributed : 1;
278+
276279
/// Whether this is a enum element.
277280
unsigned IsEnumElement : 1;
278281

@@ -298,8 +301,8 @@ struct OverloadSignature {
298301
OverloadSignature()
299302
: UnaryOperator(UnaryOperatorKind::None), IsInstanceMember(false),
300303
IsVariable(false), IsFunction(false), IsAsyncFunction(false),
301-
InProtocolExtension(false), InExtensionOfGenericType(false),
302-
HasOpaqueReturnType(false) { }
304+
IsDistributed(false), InProtocolExtension(false),
305+
InExtensionOfGenericType(false), HasOpaqueReturnType(false) { }
303306
};
304307

305308
/// Determine whether two overload signatures conflict.

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5210,8 +5210,8 @@ ERROR(conflicting_default_argument_isolation,none,
52105210
ERROR(distributed_actor_needs_explicit_distributed_import,none,
52115211
"'Distributed' module not imported, required for 'distributed actor'",
52125212
())
5213-
ERROR(distributed_func_cannot_overload_on_async_only,none,
5214-
"ambiguous distributed func declaration %0, cannot overload distributed methods on effect only", (DeclName))
5213+
NOTE(distributed_func_cannot_overload_on_async_only,none,
5214+
"%0 previously declared here, cannot overload distributed methods on effect only", (const ValueDecl *))
52155215
NOTE(distributed_func_other_ambiguous_overload_here,none,
52165216
"ambiguous distributed func %0 declared here", (DeclName))
52175217
ERROR(actor_isolated_inout_state,none,

lib/AST/Decl.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,9 +3073,35 @@ bool swift::conflicting(const OverloadSignature& sig1,
30733073
if (sig1.IsInstanceMember != sig2.IsInstanceMember)
30743074
return false;
30753075

3076-
// If one is an async function and the other is not, they can't conflict.
3077-
if (sig1.IsAsyncFunction != sig2.IsAsyncFunction)
3078-
return false;
3076+
// For distributed decls, check there's no async/no-async overloads,
3077+
// since those are more fragile in distribution than we'd want distributed calls to be.
3078+
//
3079+
// A remote call is always 'async throws', and we can always record
3080+
// an async throws "accessor" (see AccessibleFunction.cpp) as such.
3081+
// This means, if we allowed async/no-async overloads of functions,
3082+
// we'd have to store the precise "it was not throwing" information,
3083+
// but we'll _never_ make use of such because all remote calls are
3084+
// necessarily going to async to the actor in the recipient process,
3085+
// and for the remote caller, they are always as-if-async.
3086+
//
3087+
// By banning such overloads, which may be useful in local APIs,
3088+
// but too fragile in distributed APIs, we allow a remote 'v2' version
3089+
// of an implementation to add or remove `async` to their implementation
3090+
// without breaking calls which were made on previous 'v1' versions of
3091+
// the same interface; Callers are never broken this way, and rollouts
3092+
// are simpler.
3093+
//
3094+
// The restriction on overloads is not a problem for distributed calls,
3095+
// as we don't have a vast swab of APIs which must compatibly get async
3096+
// versions, as that is what the async overloading aimed to address.
3097+
//
3098+
// Note also, that overloading on throws is already illegal anyway.
3099+
if (!sig1.IsDistributed && !sig2.IsDistributed) {
3100+
// For non-distributed functions,
3101+
// if one is an async function and the other is not, they don't conflict.
3102+
if (sig1.IsAsyncFunction != sig2.IsAsyncFunction)
3103+
return false;
3104+
} // else, if any of the methods was distributed, continue checking
30793105

30803106
// If one is a macro and the other is not, they can't conflict.
30813107
if (sig1.IsMacro != sig2.IsMacro)
@@ -3328,6 +3354,8 @@ OverloadSignature ValueDecl::getOverloadSignature() const {
33283354
signature.IsFunction = true;
33293355
if (func->hasAsync())
33303356
signature.IsAsyncFunction = true;
3357+
if (func->isDistributed())
3358+
signature.IsDistributed = true;
33313359
}
33323360

33333361
if (auto *extension = dyn_cast<ExtensionDecl>(getDeclContext()))

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,16 @@ CheckRedeclarationRequest::evaluate(Evaluator &eval, ValueDecl *current,
861861
wouldBeSwift5Redeclaration = false;
862862
}
863863

864+
// Distributed declarations cannot be overloaded on async-ness only,
865+
// because it'd cause problems with the always async distributed thunks.
866+
// Provide an extra diagnostic if this is the case we're facing.
867+
bool diagnoseDistributedAsyncOverload = false;
868+
if (auto func = dyn_cast<AbstractFunctionDecl>(other)) {
869+
diagnoseDistributedAsyncOverload = func->isDistributed();
870+
} else if (auto var = dyn_cast<VarDecl>(other)) {
871+
diagnoseDistributedAsyncOverload = var->isDistributed();
872+
}
873+
864874
// If this isn't a redeclaration in the current version of Swift, but
865875
// would be in Swift 5 mode, emit a warning instead of an error.
866876
if (wouldBeSwift5Redeclaration) {
@@ -967,7 +977,13 @@ CheckRedeclarationRequest::evaluate(Evaluator &eval, ValueDecl *current,
967977
} else {
968978
ctx.Diags.diagnoseWithNotes(
969979
current->diagnose(diag::invalid_redecl, current), [&]() {
970-
other->diagnose(diag::invalid_redecl_prev, other);
980+
981+
// Add a specialized note about the 'other' overload
982+
if (diagnoseDistributedAsyncOverload) {
983+
other->diagnose(diag::distributed_func_cannot_overload_on_async_only, other);
984+
} else {
985+
other->diagnose(diag::invalid_redecl_prev, other);
986+
}
971987
});
972988

973989
current->setInvalid();

lib/Sema/TypeCheckDistributed.cpp

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -705,19 +705,6 @@ void TypeChecker::checkDistributedActor(SourceFile *SF, NominalTypeDecl *nominal
705705
// If applicable, this will create the default 'init(transport:)' initializer
706706
(void)nominal->getDefaultInitializer();
707707

708-
// We check decls for ambiguity more strictly than normal nominal types,
709-
// because we want to record distributed accessors the same if function they
710-
// point at (in a remote process) is async or not, as that has no effect on
711-
// a caller from a different process, so we want to make the remoteCall target
712-
// identifiers, less fragile against such refactorings.
713-
//
714-
// To achieve this, we ban overloads on just "effects" of functions,
715-
// which are useful in local settings, but really should not be relied
716-
// on as differenciators in remote calls - the call will always be "async"
717-
// since it will go through a thunk, and then be asynchronously transferred
718-
// to the called process.
719-
llvm::SmallDenseSet<DeclName, 2> diagnosedAmbiguity;
720-
721708
for (auto member : nominal->getMembers()) {
722709
// --- Ensure 'distributed func' all thunks
723710
if (auto *var = dyn_cast<VarDecl>(member)) {
@@ -743,48 +730,6 @@ void TypeChecker::checkDistributedActor(SourceFile *SF, NominalTypeDecl *nominal
743730
nominal->getName());
744731
return;
745732
}
746-
747-
// Check there's no async/no-async overloads, since those are more
748-
// fragile in distribution than we'd want distributed calls to be.
749-
// A remote call is always 'async throws', and we can always record
750-
// an async throws "accessor" (see AccessibleFunction.cpp) as such.
751-
// This means, if we allowed async/no-async overloads of functions,
752-
// we'd have to store the precise "it was not throwing" information,
753-
// but we'll _never_ make use of such because all remote calls are
754-
// necessarily going to async to the actor in the recipient process,
755-
// and for the remote caller, they are always as-if-async.
756-
//
757-
// By banning such overloads, which may be useful in local APIs,
758-
// but too fragile in distributed APIs, we allow a remote 'v2' version
759-
// of an implementation to add or remove `async` to their implementation
760-
// without breaking calls which were made on previous 'v1' versions of
761-
// the same interface; Callers are never broken this way, and rollouts
762-
// are simpler.
763-
//
764-
// The restriction on overloads is not a problem for distributed calls,
765-
// as we don't have a vast swab of APIs which must compatibly get async
766-
// versions, as that is what the async overloading aimed to address.
767-
//
768-
// Note also, that overloading on throws is already illegal anyway.
769-
if (!diagnosedAmbiguity.contains(func->getName())) {
770-
auto candidates = nominal->lookupDirect(func->getName());
771-
if (candidates.size() > 1) {
772-
auto firstDecl = dyn_cast<AbstractFunctionDecl>(candidates.back());
773-
for (auto decl : candidates) {
774-
if (decl == firstDecl) {
775-
decl->diagnose(
776-
diag::distributed_func_cannot_overload_on_async_only,
777-
decl->getName());
778-
} else {
779-
decl->diagnose(
780-
diag::distributed_func_other_ambiguous_overload_here,
781-
decl->getName());
782-
}
783-
}
784-
785-
diagnosedAmbiguity.insert(func->getName());
786-
}
787-
}
788733
}
789734

790735
if (auto thunk = func->getDistributedThunk()) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/../Inputs/FakeDistributedActorSystems.swift
3+
// RUN: %target-build-swift -module-name main -Xfrontend -disable-availability-checking -j2 -parse-as-library -I %t %s %S/../Inputs/FakeDistributedActorSystems.swift -o %t/a.out
4+
// RUN: %target-codesign %t/a.out
5+
// RUN: %target-run %t/a.out | %FileCheck %s --color
6+
7+
// REQUIRES: executable_test
8+
// REQUIRES: concurrency
9+
// REQUIRES: distributed
10+
11+
// rdar://76038845
12+
// UNSUPPORTED: use_os_stdlib
13+
// UNSUPPORTED: back_deployment_runtime
14+
15+
// FIXME(distributed): Distributed actors currently have some issues on windows, isRemote always returns false. rdar://82593574
16+
// UNSUPPORTED: OS=windows-msvc
17+
18+
import Distributed
19+
import FakeDistributedActorSystems
20+
21+
typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem
22+
23+
distributed actor Greeter {
24+
distributed func callMe(_ name: String) -> String {
25+
return "\(name)"
26+
}
27+
distributed func callMe(_ number: Int) -> String {
28+
return "\(number)"
29+
}
30+
}
31+
32+
struct SomeError: Error, Sendable, Codable {}
33+
34+
func test() async throws {
35+
let system = DefaultDistributedActorSystem()
36+
37+
let local = Greeter(actorSystem: system)
38+
let ref = try Greeter.resolve(id: local.id, using: system)
39+
40+
do {
41+
let echo = try await ref.callMe("hello")
42+
precondition(echo == "hello")
43+
// CHECK: >> remoteCall: on:main.Greeter, target:main.Greeter.callMe(_:), invocation:FakeInvocationEncoder(genericSubs: [], arguments: ["hello"], returnType: Optional(Swift.String), errorType: nil), throwing:Swift.Never, returning:Swift.String
44+
45+
let echo2 = try await ref.callMe(42)
46+
precondition(echo2 == "42")
47+
// CHECK: >> remoteCall: on:main.Greeter, target:main.Greeter.callMe(_:), invocation:FakeInvocationEncoder(genericSubs: [], arguments: [42], returnType: Optional(Swift.String), errorType: nil), throwing:Swift.Never, returning:Swift.String
48+
49+
print("did not throw")
50+
// CHECK: did not throw
51+
} catch {
52+
print("error: \(error)")
53+
// CHECK-NOT: error:
54+
}
55+
}
56+
57+
@main struct Main {
58+
static func main() async {
59+
try! await test()
60+
}
61+
}

test/Distributed/distributed_func_overloads.swift

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,39 @@ distributed actor Overloader {
1616
func overloaded() {}
1717
func overloaded() async {}
1818

19-
distributed func overloadedDistA() {} // expected-note{{ambiguous distributed func 'overloadedDistA()' declared here}}
20-
distributed func overloadedDistA() async {} // expected-error{{ambiguous distributed func declaration 'overloadedDistA()', cannot overload distributed methods on effect only}}
19+
distributed func overloadedDistA() {}
20+
// expected-note@-1{{'overloadedDistA()' previously declared here, cannot overload distributed methods on effect only}}
21+
distributed func overloadedDistA() async {}
22+
// expected-error@-1{{invalid redeclaration of 'overloadedDistA()'}}
2123

22-
distributed func overloadedDistT() throws {} // expected-note{{ambiguous distributed func 'overloadedDistT()' declared here}}
23-
distributed func overloadedDistT() async throws {} // expected-error{{ambiguous distributed func declaration 'overloadedDistT()', cannot overload distributed methods on effect only}}
24+
distributed func overloadedDistT() throws {}
25+
// expected-note@-1{{'overloadedDistT()' previously declared here, cannot overload distributed methods on effect only}}
26+
distributed func overloadedDistT() async throws {}
27+
// expected-error@-1{{invalid redeclaration of 'overloadedDistT()'}}
28+
29+
distributed func overloadedDistST(string: String) throws {}
30+
// expected-note@-1{{'overloadedDistST(string:)' previously declared here, cannot overload distributed methods on effect only}}
31+
distributed func overloadedDistST(string: String) async throws {}
32+
// expected-error@-1{{invalid redeclaration of 'overloadedDistST(string:)'}}
2433

2534
// Throws overloads are not legal anyway, but let's check for them here too:
2635
distributed func overloadedDistThrows() {}
27-
// expected-note@-1{{ambiguous distributed func 'overloadedDistThrows()' declared here}}
28-
// expected-note@-2{{'overloadedDistThrows()' previously declared here}}
36+
// expected-note@-1{{'overloadedDistThrows()' previously declared here}}
2937
distributed func overloadedDistThrows() throws {}
30-
// expected-error@-1{{ambiguous distributed func declaration 'overloadedDistThrows()', cannot overload distributed methods on effect only}}
31-
// expected-error@-2{{invalid redeclaration of 'overloadedDistThrows()'}}
38+
// expected-error@-1{{invalid redeclaration of 'overloadedDistThrows()'}}
3239

3340
distributed func overloadedDistAsync() async {}
34-
// expected-note@-1{{ambiguous distributed func 'overloadedDistAsync()' declared here}}
35-
// expected-note@-2{{'overloadedDistAsync()' previously declared here}}
41+
// expected-note@-1{{'overloadedDistAsync()' previously declared here}}
3642
distributed func overloadedDistAsync() async throws {}
37-
// expected-error@-1{{ambiguous distributed func declaration 'overloadedDistAsync()', cannot overload distributed methods on effect only}}
38-
// expected-error@-2{{invalid redeclaration of 'overloadedDistAsync()'}}
43+
// expected-error@-1{{invalid redeclaration of 'overloadedDistAsync()'}}
44+
45+
// overloads differing by parameter type are allowed,
46+
// since the mangled identifier includes full type information:
47+
distributed func overloadedDistParams(param: String) async {} // ok
48+
distributed func overloadedDistParams(param: Int) async {} // ok
49+
50+
distributed func overloadedDistParams() async {} // also ok
51+
52+
distributed func overloadedDistParams<A: Sendable & Codable>(param: A) async {} // ok
3953
}
4054

0 commit comments

Comments
 (0)