Skip to content

Commit 62a24b5

Browse files
authored
Merge pull request #69595 from ktoso/wip-distributed-overloads
[Distributed] Allow overloads with different types; mangling can handle it
2 parents c47e943 + 4d93923 commit 62a24b5

File tree

7 files changed

+144
-75
lines changed

7 files changed

+144
-75
lines changed

include/swift/AST/Decl.h

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

282+
/// Whether this is an distributed function.
283+
unsigned IsDistributed : 1;
284+
282285
/// Whether this is a enum element.
283286
unsigned IsEnumElement : 1;
284287

@@ -304,8 +307,8 @@ struct OverloadSignature {
304307
OverloadSignature()
305308
: UnaryOperator(UnaryOperatorKind::None), IsInstanceMember(false),
306309
IsVariable(false), IsFunction(false), IsAsyncFunction(false),
307-
InProtocolExtension(false), InExtensionOfGenericType(false),
308-
HasOpaqueReturnType(false) { }
310+
IsDistributed(false), InProtocolExtension(false),
311+
InExtensionOfGenericType(false), HasOpaqueReturnType(false) { }
309312
};
310313

311314
/// 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
@@ -5354,8 +5354,8 @@ ERROR(conflicting_default_argument_isolation,none,
53545354
ERROR(distributed_actor_needs_explicit_distributed_import,none,
53555355
"'Distributed' module not imported, required for 'distributed actor'",
53565356
())
5357-
ERROR(distributed_func_cannot_overload_on_async_only,none,
5358-
"ambiguous distributed func declaration %0, cannot overload distributed methods on effect only", (DeclName))
5357+
NOTE(distributed_func_cannot_overload_on_async_only,none,
5358+
"%0 previously declared here, cannot overload distributed methods on effect only", (const ValueDecl *))
53595359
NOTE(distributed_func_other_ambiguous_overload_here,none,
53605360
"ambiguous distributed func %0 declared here", (DeclName))
53615361
ERROR(actor_isolated_inout_state,none,

lib/AST/Decl.cpp

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3203,9 +3203,37 @@ bool swift::conflicting(const OverloadSignature& sig1,
32033203
if (sig1.IsInstanceMember != sig2.IsInstanceMember)
32043204
return false;
32053205

3206-
// If one is an async function and the other is not, they can't conflict.
3207-
if (sig1.IsAsyncFunction != sig2.IsAsyncFunction)
3208-
return false;
3206+
// For distributed decls, check there's no async/no-async overloads,
3207+
// since those are more fragile in distribution than we'd want distributed calls to be.
3208+
//
3209+
// A remote call is always 'async throws', and we can always record
3210+
// an async throws "accessor" (see AccessibleFunction.cpp) as such.
3211+
// This means, if we allowed async/no-async overloads of functions,
3212+
// we'd have to store the precise "it was not throwing" information,
3213+
// but we'll _never_ make use of such because all remote calls are
3214+
// necessarily going to async to the actor in the recipient process,
3215+
// and for the remote caller, they are always as-if-async.
3216+
//
3217+
// By banning such overloads, which may be useful in local APIs,
3218+
// but too fragile in distributed APIs, we allow a remote 'v2' version
3219+
// of an implementation to add or remove `async` to their implementation
3220+
// without breaking calls which were made on previous 'v1' versions of
3221+
// the same interface; Callers are never broken this way, and rollouts
3222+
// are simpler.
3223+
//
3224+
// The restriction on overloads is not a problem for distributed calls,
3225+
// as we don't have a vast swab of APIs which must compatibly get async
3226+
// versions, as that is what the async overloading aimed to address.
3227+
//
3228+
// Note also, that overloading on throws is already illegal anyway.
3229+
if (sig1.IsDistributed || sig2.IsDistributed) {
3230+
if (sig1.IsAsyncFunction != sig2.IsAsyncFunction)
3231+
return true;
3232+
} else {
3233+
// Otherwise one is an async function and the other is not, they don't conflict.
3234+
if (sig1.IsAsyncFunction != sig2.IsAsyncFunction)
3235+
return false;
3236+
}
32093237

32103238
// If one is a macro and the other is not, they can't conflict.
32113239
if (sig1.IsMacro != sig2.IsMacro)
@@ -3458,6 +3486,8 @@ OverloadSignature ValueDecl::getOverloadSignature() const {
34583486
signature.IsFunction = true;
34593487
if (func->hasAsync())
34603488
signature.IsAsyncFunction = true;
3489+
if (func->isDistributed())
3490+
signature.IsDistributed = true;
34613491
}
34623492

34633493
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
@@ -830,6 +830,16 @@ CheckRedeclarationRequest::evaluate(Evaluator &eval, ValueDecl *current,
830830
wouldBeSwift5Redeclaration = false;
831831
}
832832

833+
// Distributed declarations cannot be overloaded on async-ness only,
834+
// because it'd cause problems with the always async distributed thunks.
835+
// Provide an extra diagnostic if this is the case we're facing.
836+
bool diagnoseDistributedAsyncOverload = false;
837+
if (auto func = dyn_cast<AbstractFunctionDecl>(other)) {
838+
diagnoseDistributedAsyncOverload = func->isDistributed();
839+
} else if (auto var = dyn_cast<VarDecl>(other)) {
840+
diagnoseDistributedAsyncOverload = var->isDistributed();
841+
}
842+
833843
// If this isn't a redeclaration in the current version of Swift, but
834844
// would be in Swift 5 mode, emit a warning instead of an error.
835845
if (wouldBeSwift5Redeclaration) {
@@ -936,7 +946,13 @@ CheckRedeclarationRequest::evaluate(Evaluator &eval, ValueDecl *current,
936946
} else {
937947
ctx.Diags.diagnoseWithNotes(
938948
current->diagnose(diag::invalid_redecl, current), [&]() {
939-
other->diagnose(diag::invalid_redecl_prev, other);
949+
950+
// Add a specialized note about the 'other' overload
951+
if (diagnoseDistributedAsyncOverload) {
952+
other->diagnose(diag::distributed_func_cannot_overload_on_async_only, other);
953+
} else {
954+
other->diagnose(diag::invalid_redecl_prev, other);
955+
}
940956
});
941957

942958
current->setInvalid();

lib/Sema/TypeCheckDistributed.cpp

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

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

781726
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)