Skip to content

Commit 28fc7b0

Browse files
committed
Sema: Diagnose completely unapplied references to mutating methods.
The currying behavior of method references completely breaks in the face of `inout` semantics, even moreso with exclusivity enforcement, but we failed to diagnose these references in Swift 4 and previous versions. Raise a compatibility warning when these references are found in Swift 4 code, or error in Swift 5 and later. Simplify the partial application logic here slightly too now that standalone functions do not allow currying. Addresses rdar://problem/41361334 | SR-8074.
1 parent 1d6ec73 commit 28fc7b0

File tree

6 files changed

+108
-51
lines changed

6 files changed

+108
-51
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2799,12 +2799,19 @@ NOTE(ambiguous_because_of_trailing_closure,none,
27992799
// Partial application of foreign functions not supported
28002800
ERROR(partial_application_of_function_invalid,none,
28012801
"partial application of %select{"
2802-
"function with 'inout' parameters|"
28032802
"'mutating' method|"
28042803
"'super.init' initializer chain|"
28052804
"'self.init' initializer delegation"
28062805
"}0 is not allowed",
28072806
(unsigned))
2807+
WARNING(partial_application_of_function_invalid_swift4,none,
2808+
"partial application of %select{"
2809+
"'mutating' method|"
2810+
"'super.init' initializer chain|"
2811+
"'self.init' initializer delegation"
2812+
"}0 is not allowed; calling the function has undefined behavior and will "
2813+
"be an error in future Swift versions",
2814+
(unsigned))
28082815

28092816
ERROR(self_assignment_var,none,
28102817
"assigning a variable to itself", ())

lib/Sema/MiscDiagnostics.cpp

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,16 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
8787
// message.
8888
struct PartialApplication {
8989
enum : unsigned {
90-
Function,
9190
MutatingMethod,
9291
SuperInit,
9392
SelfInit,
9493
};
95-
// 'kind' before 'level' is better for code gen.
96-
unsigned kind : 3;
94+
enum : unsigned {
95+
Error,
96+
CompatibilityWarning,
97+
};
98+
unsigned compatibilityWarning: 1;
99+
unsigned kind : 2;
97100
unsigned level : 29;
98101
};
99102

@@ -105,49 +108,18 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
105108
~DiagnoseWalker() override {
106109
for (auto &unapplied : InvalidPartialApplications) {
107110
unsigned kind = unapplied.second.kind;
108-
TC.diagnose(unapplied.first->getLoc(),
109-
diag::partial_application_of_function_invalid,
110-
kind);
111-
}
112-
}
113-
114-
/// If this is an application of a function that cannot be partially
115-
/// applied, arrange for us to check that it gets fully applied.
116-
void recordUnsupportedPartialApply(ApplyExpr *expr, Expr *fnExpr) {
117-
118-
if (isa<OtherConstructorDeclRefExpr>(fnExpr)) {
119-
auto kind = expr->getArg()->isSuperExpr()
120-
? PartialApplication::SuperInit
121-
: PartialApplication::SelfInit;
122-
123-
// Partial applications of delegated initializers aren't allowed, and
124-
// don't really make sense to begin with.
125-
InvalidPartialApplications.insert({ expr, {kind, 1} });
126-
return;
127-
}
128-
129-
auto fnDeclRef = dyn_cast<DeclRefExpr>(fnExpr);
130-
if (!fnDeclRef)
131-
return;
132-
133-
auto fn = dyn_cast<FuncDecl>(fnDeclRef->getDecl());
134-
if (!fn)
135-
return;
136-
137-
unsigned kind =
138-
fn->isInstanceMember() ? PartialApplication::MutatingMethod
139-
: PartialApplication::Function;
140-
141-
// Functions with inout parameters cannot be partially applied.
142-
if (!expr->getArg()->getType()->isMaterializable()) {
143-
// We need to apply all argument clauses.
144-
InvalidPartialApplications.insert({
145-
fnExpr, {kind, fn->getNumParameterLists()}
146-
});
111+
if (unapplied.second.compatibilityWarning) {
112+
TC.diagnose(unapplied.first->getLoc(),
113+
diag::partial_application_of_function_invalid_swift4,
114+
kind);
115+
} else {
116+
TC.diagnose(unapplied.first->getLoc(),
117+
diag::partial_application_of_function_invalid,
118+
kind);
119+
}
147120
}
148121
}
149122

150-
/// This method is called in post-order over the AST to validate that
151123
/// methods are fully applied when they can't support partial application.
152124
void checkInvalidPartialApplication(Expr *E) {
153125
if (auto AE = dyn_cast<ApplyExpr>(E)) {
@@ -158,8 +130,18 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
158130
fnExpr = dotSyntaxExpr->getRHS();
159131

160132
// Check to see if this is a potentially unsupported partial
161-
// application.
162-
recordUnsupportedPartialApply(AE, fnExpr);
133+
// application of a constructor delegation.
134+
if (isa<OtherConstructorDeclRefExpr>(fnExpr)) {
135+
auto kind = AE->getArg()->isSuperExpr()
136+
? PartialApplication::SuperInit
137+
: PartialApplication::SelfInit;
138+
139+
// Partial applications of delegated initializers aren't allowed, and
140+
// don't really make sense to begin with.
141+
InvalidPartialApplications.insert(
142+
{E, {PartialApplication::Error, kind, 1}});
143+
return;
144+
}
163145

164146
// If this is adding a level to an active partial application, advance
165147
// it to the next level.
@@ -173,11 +155,36 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
173155
InvalidPartialApplications.erase(foundApplication);
174156
if (level > 1) {
175157
// We have remaining argument clauses.
176-
InvalidPartialApplications.insert({ AE, {kind, level - 1} });
158+
// Partial applications were always diagnosed in Swift 4 and before,
159+
// so there's no need to preserve the compatibility warning bit.
160+
InvalidPartialApplications.insert(
161+
{AE, {PartialApplication::Error, kind, level - 1}});
177162
}
178163
return;
179164
}
165+
166+
/// If this is a reference to a mutating method, it cannot be partially
167+
/// applied or even referenced without full application, so arrange for
168+
/// us to check that it gets fully applied.
169+
auto fnDeclRef = dyn_cast<DeclRefExpr>(E);
170+
if (!fnDeclRef)
171+
return;
172+
173+
auto fn = dyn_cast<FuncDecl>(fnDeclRef->getDecl());
174+
if (!fn || !fn->isInstanceMember() || !fn->isMutating())
175+
return;
180176

177+
// Swift 4 and earlier failed to diagnose a reference to a mutating method
178+
// without any applications at all, which would get miscompiled into a
179+
// function with undefined behavior. Warn for source compatibility.
180+
auto errorBehavior = TC.Context.LangOpts.isSwiftVersionAtLeast(5)
181+
? PartialApplication::Error
182+
: PartialApplication::CompatibilityWarning;
183+
184+
InvalidPartialApplications.insert(
185+
{fnDeclRef, {errorBehavior,
186+
PartialApplication::MutatingMethod,
187+
fn->getNumParameterLists()}});
181188
}
182189

183190
// Not interested in going outside a basic expression.

test/Compatibility/members.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ struct X {
77

88
func g0(_: (inout X) -> (Float) -> ()) {}
99

10-
// This becomes an error in Swift 4 mode -- probably a bug
11-
g0(X.f1)
10+
g0(X.f1) // expected-warning{{partial application of 'mutating' method}}

test/Constraints/members.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift -swift-version 4
1+
// RUN: %target-typecheck-verify-swift -swift-version 5
22

33
////
44
// Members of structs
@@ -28,7 +28,7 @@ func g0(_: (inout X) -> (Float) -> ()) {}
2828
_ = x.f0(i)
2929
x.f0(i).f1(i)
3030

31-
g0(X.f1)
31+
g0(X.f1) // expected-error{{partial application of 'mutating' method}}
3232

3333
_ = x.f0(x.f2(1))
3434
_ = x.f0(1).f2(i)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-swift-frontend -typecheck -verify -swift-version 5 %s
2+
3+
struct Foo {
4+
mutating func boom() {}
5+
}
6+
7+
let x = Foo.boom // expected-error{{partial application of 'mutating' method}}
8+
var y = Foo()
9+
let z0 = y.boom // expected-error{{partial application of 'mutating' method}}
10+
let z1 = Foo.boom(&y) // expected-error{{partial application of 'mutating' method}}
11+
12+
func fromLocalContext() -> (inout Foo) -> () -> () {
13+
return Foo.boom // expected-error{{partial application of 'mutating' method}}
14+
}
15+
func fromLocalContext2(x: inout Foo, y: Bool) -> () -> () {
16+
if y {
17+
return x.boom // expected-error{{partial application of 'mutating' method}}
18+
} else {
19+
return Foo.boom(&x) // expected-error{{partial application of 'mutating' method}}
20+
}
21+
}
22+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-swift-frontend -typecheck -verify -swift-version 4 %s
2+
3+
struct Foo {
4+
mutating func boom() {}
5+
}
6+
7+
let x = Foo.boom // expected-warning{{partial application of 'mutating' method}}
8+
var y = Foo()
9+
let z0 = y.boom // expected-error{{partial application of 'mutating' method}}
10+
let z1 = Foo.boom(&y) // expected-error{{partial application of 'mutating' method}}
11+
12+
func fromLocalContext() -> (inout Foo) -> () -> () {
13+
return Foo.boom // expected-warning{{partial application of 'mutating' method}}
14+
}
15+
func fromLocalContext2(x: inout Foo, y: Bool) -> () -> () {
16+
if y {
17+
return x.boom // expected-error{{partial application of 'mutating' method}}
18+
} else {
19+
return Foo.boom(&x) // expected-error{{partial application of 'mutating' method}}
20+
}
21+
}
22+

0 commit comments

Comments
 (0)