Skip to content

Commit 6079032

Browse files
authored
Merge pull request #17653 from jckarter/no-unapplied-mutating-method-references-4.2
[4.2] Sema: Diagnose completely unapplied references to mutating methods.
2 parents b3408e8 + 3f454cd commit 6079032

File tree

6 files changed

+109
-51
lines changed

6 files changed

+109
-51
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2702,12 +2702,19 @@ NOTE(ambiguous_because_of_trailing_closure,none,
27022702
// Partial application of foreign functions not supported
27032703
ERROR(partial_application_of_function_invalid,none,
27042704
"partial application of %select{"
2705-
"function with 'inout' parameters|"
27062705
"'mutating' method|"
27072706
"'super.init' initializer chain|"
27082707
"'self.init' initializer delegation"
27092708
"}0 is not allowed",
27102709
(unsigned))
2710+
WARNING(partial_application_of_function_invalid_swift4,none,
2711+
"partial application of %select{"
2712+
"'mutating' method|"
2713+
"'super.init' initializer chain|"
2714+
"'self.init' initializer delegation"
2715+
"}0 is not allowed; calling the function has undefined behavior and will "
2716+
"be an error in future Swift versions",
2717+
(unsigned))
27112718

27122719
ERROR(self_assignment_var,none,
27132720
"assigning a variable to itself", ())

lib/Sema/MiscDiagnostics.cpp

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,18 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
8686
// Selector for the partial_application_of_function_invalid diagnostic
8787
// message.
8888
struct PartialApplication {
89-
unsigned level : 29;
9089
enum : unsigned {
91-
Function,
9290
MutatingMethod,
9391
SuperInit,
9492
SelfInit,
9593
};
96-
unsigned kind : 3;
94+
enum : unsigned {
95+
Error,
96+
CompatibilityWarning,
97+
};
98+
unsigned compatibilityWarning: 1;
99+
unsigned kind : 2;
100+
unsigned level : 29;
97101
};
98102

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

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

159132
// Check to see if this is a potentially unsupported partial
160-
// application.
161-
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+
}
162145

163146
// If this is adding a level to an active partial application, advance
164147
// it to the next level.
@@ -172,11 +155,36 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
172155
InvalidPartialApplications.erase(foundApplication);
173156
if (level > 1) {
174157
// We have remaining argument clauses.
175-
InvalidPartialApplications.insert({ AE, {level - 1, kind} });
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}});
176162
}
177163
return;
178164
}
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;
179176

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()}});
180188
}
181189

182190
// 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)