[CSDiagnostics] Add custom diagnostic for invalid @autoclosure forwarding
Suggest to add `()` (form a call) to correctly forward argument function
originated from `@autoclosure` parameter to function parameter itself
marked as `@autoclosure`.
diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def
index 639cee3..cc4c1db 100644
--- a/include/swift/AST/DiagnosticsSema.def
+++ b/include/swift/AST/DiagnosticsSema.def
@@ -1081,6 +1081,8 @@
"%select{local function|closure}0 that captures "
"%select{context|generic parameters|dynamic Self type}1",
(bool, unsigned))
+ERROR(invalid_autoclosure_forwarding,none,
+ "add () to forward @autoclosure parameter", ())
//------------------------------------------------------------------------------
// MARK: Type Check Declarations
diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp
index 6843d90..d8c1ed4 100644
--- a/lib/Sema/CSDiagnostics.cpp
+++ b/lib/Sema/CSDiagnostics.cpp
@@ -1206,3 +1206,19 @@
return false;
}
+
+bool AutoClosureForwardingFailure::diagnoseAsError() {
+ auto path = getLocator()->getPath();
+ assert(!path.empty());
+
+ auto &last = path.back();
+ assert(last.getKind() == ConstraintLocator::ApplyArgToParam);
+
+ // We need a raw anchor here because `getAnchor()` is simplified
+ // to the argument expression.
+ auto *argExpr = getArgumentExpr(getRawAnchor(), last.getValue());
+ emitDiagnostic(argExpr->getLoc(), diag::invalid_autoclosure_forwarding)
+ .highlight(argExpr->getSourceRange())
+ .fixItInsertAfter(argExpr->getEndLoc(), "()");
+ return true;
+}
diff --git a/lib/Sema/CSDiagnostics.h b/lib/Sema/CSDiagnostics.h
index b14b31e..777db6d 100644
--- a/lib/Sema/CSDiagnostics.h
+++ b/lib/Sema/CSDiagnostics.h
@@ -39,6 +39,9 @@
ConstraintSystem &CS;
ConstraintLocator *Locator;
+ /// The original anchor before any simplification.
+ Expr *RawAnchor;
+ /// Simplified anchor associated with the given locator.
Expr *Anchor;
/// Indicates whether locator could be simplified
/// down to anchor expression.
@@ -47,7 +50,7 @@
public:
FailureDiagnostic(Expr *expr, ConstraintSystem &cs,
ConstraintLocator *locator)
- : E(expr), CS(cs), Locator(locator) {
+ : E(expr), CS(cs), Locator(locator), RawAnchor(locator->getAnchor()) {
std::tie(Anchor, HasComplexLocator) = computeAnchor();
}
@@ -78,6 +81,8 @@
Expr *getParentExpr() const { return E; }
+ Expr *getRawAnchor() const { return RawAnchor; }
+
Expr *getAnchor() const { return Anchor; }
ConstraintLocator *getLocator() const { return Locator; }
@@ -592,6 +597,16 @@
}
};
+/// Diagnose situations when @autoclosure argument is passed to @autoclosure
+/// parameter directly without calling it first.
+class AutoClosureForwardingFailure final : public FailureDiagnostic {
+public:
+ AutoClosureForwardingFailure(ConstraintSystem &cs, ConstraintLocator *locator)
+ : FailureDiagnostic(nullptr, cs, locator) {}
+
+ bool diagnoseAsError() override;
+};
+
} // end namespace constraints
} // end namespace swift
diff --git a/lib/Sema/CSFix.cpp b/lib/Sema/CSFix.cpp
index b94b8e9..9cc21f3 100644
--- a/lib/Sema/CSFix.cpp
+++ b/lib/Sema/CSFix.cpp
@@ -204,7 +204,9 @@
}
bool AutoClosureForwarding::diagnose(Expr *root, bool asNote) const {
- return false;
+ auto failure =
+ AutoClosureForwardingFailure(getConstraintSystem(), getLocator());
+ return failure.diagnose(asNote);
}
AutoClosureForwarding *AutoClosureForwarding::create(ConstraintSystem &cs,
diff --git a/test/Compatibility/attr_autoclosure.swift b/test/Compatibility/attr_autoclosure.swift
index b0ec084..ce26b8c 100644
--- a/test/Compatibility/attr_autoclosure.swift
+++ b/test/Compatibility/attr_autoclosure.swift
@@ -37,11 +37,28 @@
}
}
-func passAutoClosureToSubscript(_ fn: @autoclosure () -> Int) {
+func passAutoClosureToSubscriptAndMember(_ fn: @autoclosure () -> Int) {
struct S {
+ func bar(_: Int, _ fun: @autoclosure () -> Int) {}
+
subscript(_ fn: @autoclosure () -> Int) -> Int { return fn() }
+
+ static func foo(_: @autoclosure () -> Int) {}
}
let s = S()
+ let _ = s.bar(42, fn) // Ok
let _ = s[fn] // Ok
+ let _ = S.foo(fn) // Ok
+}
+
+func passAutoClosureToEnumCase(_ fn: @escaping @autoclosure () -> Int) {
+ enum E {
+ case baz(@autoclosure () -> Int)
+ }
+
+ let _: E = .baz(42) // Ok
+ // FIXME: This line type-checks correctly but causes a crash
+ // somewhere SILGen if `fn` doesn't have `@escaping`.
+ let _: E = .baz(fn) // Ok
}
diff --git a/test/attr/attr_autoclosure.swift b/test/attr/attr_autoclosure.swift
index 3801fd4..4ab1bf1 100644
--- a/test/attr/attr_autoclosure.swift
+++ b/test/attr/attr_autoclosure.swift
@@ -1,4 +1,4 @@
-// RUN: %target-typecheck-verify-swift
+// RUN: %target-typecheck-verify-swift -swift-version 5
// Simple case.
var fn : @autoclosure () -> Int = 4 // expected-error {{'@autoclosure' may only be used on parameters}} expected-error {{cannot convert value of type 'Int' to specified type '() -> Int'}}
@@ -167,13 +167,37 @@
// These are all arguably invalid; the autoclosure should have to be called.
// But as long as we allow them, we shouldn't crash.
func passNonThrowingToNonThrowingAC(_ fn: @autoclosure () -> Int) {
- takesAutoclosure(fn)
+ takesAutoclosure(fn) // expected-error {{add () to forward @autoclosure parameter}} {{22-22=()}}
}
func passNonThrowingToThrowingAC(_ fn: @autoclosure () -> Int) {
- takesThrowingAutoclosure(fn)
+ takesThrowingAutoclosure(fn) // expected-error {{add () to forward @autoclosure parameter}} {{30-30=()}}
}
func passThrowingToThrowingAC(_ fn: @autoclosure () throws -> Int) {
- takesThrowingAutoclosure(fn)
+ takesThrowingAutoclosure(fn) // expected-error {{add () to forward @autoclosure parameter}} {{30-30=()}}
+}
+
+func passAutoClosureToSubscriptAndMember(_ fn: @autoclosure () -> Int) {
+ struct S {
+ func bar(_: Int, _ fun: @autoclosure () -> Int) {}
+
+ subscript(_ fn: @autoclosure () -> Int) -> Int { return fn() }
+
+ static func foo(_ fn: @autoclosure () -> Int) {}
+ }
+
+ let s = S()
+ let _ = s.bar(42, fn) // expected-error {{add () to forward @autoclosure parameter}} {{23-23=()}}
+ let _ = s[fn] // expected-error {{add () to forward @autoclosure parameter}} {{15-15=()}}
+ let _ = S.foo(fn) // expected-error {{add () to forward @autoclosure parameter}} {{19-19=()}}
+}
+
+func passAutoClosureToEnumCase(_ fn: @autoclosure () -> Int) {
+ enum E {
+ case baz(@autoclosure () -> Int)
+ }
+
+ let _: E = .baz(42) // Ok
+ let _: E = .baz(fn) // expected-error {{add () to forward @autoclosure parameter}} {{21-21=()}}
}
// rdar://problem/20591571 - Various type inference problems with @autoclosure