Merge pull request #17653 from jckarter/no-unapplied-mutating-method-references-4.2
[4.2] Sema: Diagnose completely unapplied references to mutating methods.
diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def
index 47addcd..e1b37d9 100644
--- a/include/swift/AST/DiagnosticsSema.def
+++ b/include/swift/AST/DiagnosticsSema.def
@@ -2702,12 +2702,19 @@
// Partial application of foreign functions not supported
ERROR(partial_application_of_function_invalid,none,
"partial application of %select{"
- "function with 'inout' parameters|"
"'mutating' method|"
"'super.init' initializer chain|"
"'self.init' initializer delegation"
"}0 is not allowed",
(unsigned))
+WARNING(partial_application_of_function_invalid_swift4,none,
+ "partial application of %select{"
+ "'mutating' method|"
+ "'super.init' initializer chain|"
+ "'self.init' initializer delegation"
+ "}0 is not allowed; calling the function has undefined behavior and will "
+ "be an error in future Swift versions",
+ (unsigned))
ERROR(self_assignment_var,none,
"assigning a variable to itself", ())
diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp
index 5a6af26..a5cb986 100644
--- a/lib/Sema/MiscDiagnostics.cpp
+++ b/lib/Sema/MiscDiagnostics.cpp
@@ -86,14 +86,18 @@
// Selector for the partial_application_of_function_invalid diagnostic
// message.
struct PartialApplication {
- unsigned level : 29;
enum : unsigned {
- Function,
MutatingMethod,
SuperInit,
SelfInit,
};
- unsigned kind : 3;
+ enum : unsigned {
+ Error,
+ CompatibilityWarning,
+ };
+ unsigned compatibilityWarning: 1;
+ unsigned kind : 2;
+ unsigned level : 29;
};
// Partial applications of functions that are not permitted. This is
@@ -104,49 +108,18 @@
~DiagnoseWalker() override {
for (auto &unapplied : InvalidPartialApplications) {
unsigned kind = unapplied.second.kind;
- TC.diagnose(unapplied.first->getLoc(),
- diag::partial_application_of_function_invalid,
- kind);
+ if (unapplied.second.compatibilityWarning) {
+ TC.diagnose(unapplied.first->getLoc(),
+ diag::partial_application_of_function_invalid_swift4,
+ kind);
+ } else {
+ TC.diagnose(unapplied.first->getLoc(),
+ diag::partial_application_of_function_invalid,
+ kind);
+ }
}
}
- /// If this is an application of a function that cannot be partially
- /// applied, arrange for us to check that it gets fully applied.
- void recordUnsupportedPartialApply(ApplyExpr *expr, Expr *fnExpr) {
-
- if (isa<OtherConstructorDeclRefExpr>(fnExpr)) {
- auto kind = expr->getArg()->isSuperExpr()
- ? PartialApplication::SuperInit
- : PartialApplication::SelfInit;
-
- // Partial applications of delegated initializers aren't allowed, and
- // don't really make sense to begin with.
- InvalidPartialApplications.insert({ expr, {1, kind} });
- return;
- }
-
- auto fnDeclRef = dyn_cast<DeclRefExpr>(fnExpr);
- if (!fnDeclRef)
- return;
-
- auto fn = dyn_cast<FuncDecl>(fnDeclRef->getDecl());
- if (!fn)
- return;
-
- unsigned kind =
- fn->isInstanceMember() ? PartialApplication::MutatingMethod
- : PartialApplication::Function;
-
- // Functions with inout parameters cannot be partially applied.
- if (!expr->getArg()->getType()->isMaterializable()) {
- // We need to apply all argument clauses.
- InvalidPartialApplications.insert({
- fnExpr, {fn->getNumParameterLists(), kind}
- });
- }
- }
-
- /// This method is called in post-order over the AST to validate that
/// methods are fully applied when they can't support partial application.
void checkInvalidPartialApplication(Expr *E) {
if (auto AE = dyn_cast<ApplyExpr>(E)) {
@@ -157,8 +130,18 @@
fnExpr = dotSyntaxExpr->getRHS();
// Check to see if this is a potentially unsupported partial
- // application.
- recordUnsupportedPartialApply(AE, fnExpr);
+ // application of a constructor delegation.
+ if (isa<OtherConstructorDeclRefExpr>(fnExpr)) {
+ auto kind = AE->getArg()->isSuperExpr()
+ ? PartialApplication::SuperInit
+ : PartialApplication::SelfInit;
+
+ // Partial applications of delegated initializers aren't allowed, and
+ // don't really make sense to begin with.
+ InvalidPartialApplications.insert(
+ {E, {PartialApplication::Error, kind, 1}});
+ return;
+ }
// If this is adding a level to an active partial application, advance
// it to the next level.
@@ -172,11 +155,36 @@
InvalidPartialApplications.erase(foundApplication);
if (level > 1) {
// We have remaining argument clauses.
- InvalidPartialApplications.insert({ AE, {level - 1, kind} });
+ // Partial applications were always diagnosed in Swift 4 and before,
+ // so there's no need to preserve the compatibility warning bit.
+ InvalidPartialApplications.insert(
+ {AE, {PartialApplication::Error, kind, level - 1}});
}
return;
}
+
+ /// If this is a reference to a mutating method, it cannot be partially
+ /// applied or even referenced without full application, so arrange for
+ /// us to check that it gets fully applied.
+ auto fnDeclRef = dyn_cast<DeclRefExpr>(E);
+ if (!fnDeclRef)
+ return;
+ auto fn = dyn_cast<FuncDecl>(fnDeclRef->getDecl());
+ if (!fn || !fn->isInstanceMember() || !fn->isMutating())
+ return;
+
+ // Swift 4 and earlier failed to diagnose a reference to a mutating method
+ // without any applications at all, which would get miscompiled into a
+ // function with undefined behavior. Warn for source compatibility.
+ auto errorBehavior = TC.Context.LangOpts.isSwiftVersionAtLeast(5)
+ ? PartialApplication::Error
+ : PartialApplication::CompatibilityWarning;
+
+ InvalidPartialApplications.insert(
+ {fnDeclRef, {errorBehavior,
+ PartialApplication::MutatingMethod,
+ fn->getNumParameterLists()}});
}
// Not interested in going outside a basic expression.
diff --git a/test/Compatibility/members.swift b/test/Compatibility/members.swift
index 1805106..6af2274 100644
--- a/test/Compatibility/members.swift
+++ b/test/Compatibility/members.swift
@@ -7,5 +7,4 @@
func g0(_: (inout X) -> (Float) -> ()) {}
-// This becomes an error in Swift 4 mode -- probably a bug
-g0(X.f1)
+g0(X.f1) // expected-warning{{partial application of 'mutating' method}}
diff --git a/test/Constraints/members.swift b/test/Constraints/members.swift
index 752e33a..4192a11 100644
--- a/test/Constraints/members.swift
+++ b/test/Constraints/members.swift
@@ -1,4 +1,4 @@
-// RUN: %target-typecheck-verify-swift -swift-version 4
+// RUN: %target-typecheck-verify-swift -swift-version 5
////
// Members of structs
@@ -28,7 +28,7 @@
_ = x.f0(i)
x.f0(i).f1(i)
-g0(X.f1)
+g0(X.f1) // expected-error{{partial application of 'mutating' method}}
_ = x.f0(x.f2(1))
_ = x.f0(1).f2(i)
diff --git a/test/Constraints/mutating_members.swift b/test/Constraints/mutating_members.swift
new file mode 100644
index 0000000..dea386b
--- /dev/null
+++ b/test/Constraints/mutating_members.swift
@@ -0,0 +1,22 @@
+// RUN: %target-swift-frontend -typecheck -verify -swift-version 5 %s
+
+struct Foo {
+ mutating func boom() {}
+}
+
+let x = Foo.boom // expected-error{{partial application of 'mutating' method}}
+var y = Foo()
+let z0 = y.boom // expected-error{{partial application of 'mutating' method}}
+let z1 = Foo.boom(&y) // expected-error{{partial application of 'mutating' method}}
+
+func fromLocalContext() -> (inout Foo) -> () -> () {
+ return Foo.boom // expected-error{{partial application of 'mutating' method}}
+}
+func fromLocalContext2(x: inout Foo, y: Bool) -> () -> () {
+ if y {
+ return x.boom // expected-error{{partial application of 'mutating' method}}
+ } else {
+ return Foo.boom(&x) // expected-error{{partial application of 'mutating' method}}
+ }
+}
+
diff --git a/test/Constraints/mutating_members_compat.swift b/test/Constraints/mutating_members_compat.swift
new file mode 100644
index 0000000..e29fd34
--- /dev/null
+++ b/test/Constraints/mutating_members_compat.swift
@@ -0,0 +1,22 @@
+// RUN: %target-swift-frontend -typecheck -verify -swift-version 4 %s
+
+struct Foo {
+ mutating func boom() {}
+}
+
+let x = Foo.boom // expected-warning{{partial application of 'mutating' method}}
+var y = Foo()
+let z0 = y.boom // expected-error{{partial application of 'mutating' method}}
+let z1 = Foo.boom(&y) // expected-error{{partial application of 'mutating' method}}
+
+func fromLocalContext() -> (inout Foo) -> () -> () {
+ return Foo.boom // expected-warning{{partial application of 'mutating' method}}
+}
+func fromLocalContext2(x: inout Foo, y: Bool) -> () -> () {
+ if y {
+ return x.boom // expected-error{{partial application of 'mutating' method}}
+ } else {
+ return Foo.boom(&x) // expected-error{{partial application of 'mutating' method}}
+ }
+}
+